// SPDX-License-Identifier: GPL-2.0+ /* * Ptrace test for Memory Protection Key registers * * Copyright (C) 2015 Anshuman Khandual, IBM Corporation. * Copyright (C) 2018 IBM Corporation. */ #include <limits.h> #include <linux/kernel.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/time.h> #include <sys/resource.h> #include <fcntl.h> #include <unistd.h> #include "ptrace.h" #include "child.h" #ifndef __NR_pkey_alloc #define __NR_pkey_alloc 384 #endif #ifndef __NR_pkey_free #define __NR_pkey_free 385 #endif #ifndef NT_PPC_PKEY #define NT_PPC_PKEY 0x110 #endif #ifndef PKEY_DISABLE_EXECUTE #define PKEY_DISABLE_EXECUTE 0x4 #endif #define AMR_BITS_PER_PKEY 2 #define PKEY_REG_BITS (sizeof(u64) * 8) #define pkeyshift(pkey) (PKEY_REG_BITS - ((pkey + 1) * AMR_BITS_PER_PKEY)) #define CORE_FILE_LIMIT (5 * 1024 * 1024) /* 5 MB should be enough */ static const char core_pattern_file[] = "/proc/sys/kernel/core_pattern"; static const char user_write[] = "[User Write (Running)]"; static const char core_read_running[] = "[Core Read (Running)]"; /* Information shared between the parent and the child. */ struct shared_info { struct child_sync child_sync; /* AMR value the parent expects to read in the core file. */ unsigned long amr; /* IAMR value the parent expects to read in the core file. */ unsigned long iamr; /* UAMOR value the parent expects to read in the core file. */ unsigned long uamor; /* When the child crashed. */ time_t core_time; }; static int sys_pkey_alloc(unsigned long flags, unsigned long init_access_rights) { return syscall(__NR_pkey_alloc, flags, init_access_rights); } static int sys_pkey_free(int pkey) { return syscall(__NR_pkey_free, pkey); } static int increase_core_file_limit(void) { struct rlimit rlim; int ret; ret = getrlimit(RLIMIT_CORE, &rlim); FAIL_IF(ret); if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < CORE_FILE_LIMIT) { rlim.rlim_cur = CORE_FILE_LIMIT; if (rlim.rlim_max != RLIM_INFINITY && rlim.rlim_max < CORE_FILE_LIMIT) rlim.rlim_max = CORE_FILE_LIMIT; ret = setrlimit(RLIMIT_CORE, &rlim); FAIL_IF(ret); } ret = getrlimit(RLIMIT_FSIZE, &rlim); FAIL_IF(ret); if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < CORE_FILE_LIMIT) { rlim.rlim_cur = CORE_FILE_LIMIT; if (rlim.rlim_max != RLIM_INFINITY && rlim.rlim_max < CORE_FILE_LIMIT) rlim.rlim_max = CORE_FILE_LIMIT; ret = setrlimit(RLIMIT_FSIZE, &rlim); FAIL_IF(ret); } return TEST_PASS; } static int child(struct shared_info *info) { bool disable_execute = true; int pkey1, pkey2, pkey3; int *ptr, ret; /* Wait until parent fills out the initial register values. */ ret = wait_parent(&info->child_sync); if (ret) return ret; ret = increase_core_file_limit(); FAIL_IF(ret); /* Get some pkeys so that we can change their bits in the AMR. */ pkey1 = sys_pkey_alloc(0, PKEY_DISABLE_EXECUTE); if (pkey1 < 0) { pkey1 = sys_pkey_alloc(0, 0); FAIL_IF(pkey1 < 0); disable_execute = false; } pkey2 = sys_pkey_alloc(0, 0); FAIL_IF(pkey2 < 0); pkey3 = sys_pkey_alloc(0, 0); FAIL_IF(pkey3 < 0); info->amr |= 3ul << pkeyshift(pkey1) | 2ul << pkeyshift(pkey2); if (disable_execute) info->iamr |= 1ul << pkeyshift(pkey1); else info->iamr &= ~(1ul << pkeyshift(pkey1)); info->iamr &= ~(1ul << pkeyshift(pkey2) | 1ul << pkeyshift(pkey3)); info->uamor |= 3ul << pkeyshift(pkey1) | 3ul << pkeyshift(pkey2); printf("%-30s AMR: %016lx pkey1: %d pkey2: %d pkey3: %d\n", user_write, info->amr, pkey1, pkey2, pkey3); set_amr(info->amr); /* * We won't use pkey3. This tests whether the kernel restores the UAMOR * permissions after a key is freed. */ sys_pkey_free(pkey3); info->core_time = time(NULL); /* Crash. */ ptr = 0; *ptr = 1; /* Shouldn't get here. */ FAIL_IF(true); return TEST_FAIL; } /* Return file size if filename exists and pass sanity check, or zero if not. */ static off_t try_core_file(const char *filename, struct shared_info *info, pid_t pid) { struct stat buf; int ret; ret = stat(filename, &buf); if (ret == -1) return TEST_FAIL; /* Make sure we're not using a stale core file. */ return buf.st_mtime >= info->core_time ? buf.st_size : TEST_FAIL; } static Elf64_Nhdr *next_note(Elf64_Nhdr *nhdr) { return (void *) nhdr + sizeof(*nhdr) + __ALIGN_KERNEL(nhdr->n_namesz, 4) + __ALIGN_KERNEL(nhdr->n_descsz, 4); } static int check_core_file(struct shared_info *info, Elf64_Ehdr *ehdr, off_t core_size) { unsigned long *regs; Elf64_Phdr *phdr; Elf64_Nhdr *nhdr; size_t phdr_size; void *p = ehdr, *note; int ret; ret = memcmp(ehdr->e_ident, ELFMAG, SELFMAG); FAIL_IF(ret); FAIL_IF(ehdr->e_type != ET_CORE); FAIL_IF(ehdr->e_machine != EM_PPC64); FAIL_IF(ehdr->e_phoff == 0 || ehdr->e_phnum == 0); /* * e_phnum is at most 65535 so calculating the size of the * program header cannot overflow. */ phdr_size = sizeof(*phdr) * ehdr->e_phnum; /* Sanity check the program header table location. */ FAIL_IF(ehdr->e_phoff + phdr_size < ehdr->e_phoff); FAIL_IF(ehdr->e_phoff + phdr_size > core_size); /* Find the PT_NOTE segment. */ for (phdr = p + ehdr->e_phoff; (void *) phdr < p + ehdr->e_phoff + phdr_size; phdr += ehdr->e_phentsize) if (phdr->p_type == PT_NOTE) break; FAIL_IF((void *) phdr >= p + ehdr->e_phoff + phdr_size); /* Find the NT_PPC_PKEY note. */ for (nhdr = p + phdr->p_offset; (void *) nhdr < p + phdr->p_offset + phdr->p_filesz; nhdr = next_note(nhdr)) if (nhdr->n_type == NT_PPC_PKEY) break; FAIL_IF((void *) nhdr >= p + phdr->p_offset + phdr->p_filesz); FAIL_IF(nhdr->n_descsz == 0); p = nhdr; note = p + sizeof(*nhdr) + __ALIGN_KERNEL(nhdr->n_namesz, 4); regs = (unsigned long *) note; printf("%-30s AMR: %016lx IAMR: %016lx UAMOR: %016lx\n", core_read_running, regs[0], regs[1], regs[2]); FAIL_IF(regs[0] != info->amr); FAIL_IF(regs[1] != info->iamr); FAIL_IF(regs[2] != info->uamor); return TEST_PASS; } static int parent(struct shared_info *info, pid_t pid) { char *filenames, *filename[3]; int fd, i, ret, status; unsigned long regs[3]; off_t core_size; void *core; /* * Get the initial values for AMR, IAMR and UAMOR and communicate them * to the child. */ ret = ptrace_read_regs(pid, NT_PPC_PKEY, regs, 3); PARENT_SKIP_IF_UNSUPPORTED(ret, &info->child_sync, "PKEYs not supported"); PARENT_FAIL_IF(ret, &info->child_sync); info->amr = regs[0]; info->iamr = regs[1]; info->uamor = regs[2]; /* Wake up child so that it can set itself up. */ ret = prod_child(&info->child_sync); PARENT_FAIL_IF(ret, &info->child_sync); ret = wait(&status); if (ret != pid) { printf("Child's exit status not captured\n"); return TEST_FAIL; } else if (!WIFSIGNALED(status) || !WCOREDUMP(status)) { printf("Child didn't dump core\n"); return TEST_FAIL; } /* Construct array of core file names to try. */ filename[0] = filenames = malloc(PATH_MAX); if (!filenames) { perror("Error allocating memory"); return TEST_FAIL; } ret = snprintf(filename[0], PATH_MAX, "core-pkey.%d", pid); if (ret < 0 || ret >= PATH_MAX) { ret = TEST_FAIL; goto out; } filename[1] = filename[0] + ret + 1; ret = snprintf(filename[1], PATH_MAX - ret - 1, "core.%d", pid); if (ret < 0 || ret >= PATH_MAX - ret - 1) { ret = TEST_FAIL; goto out; } filename[2] = "core"; for (i = 0; i < 3; i++) { core_size = try_core_file(filename[i], info, pid); if (core_size != TEST_FAIL) break; } if (i == 3) { printf("Couldn't find core file\n"); ret = TEST_FAIL; goto out; } fd = open(filename[i], O_RDONLY); if (fd == -1) { perror("Error opening core file"); ret = TEST_FAIL; goto out; } core = mmap(NULL, core_size, PROT_READ, MAP_PRIVATE, fd, 0); if (core == (void *) -1) { perror("Error mmapping core file"); ret = TEST_FAIL; goto out; } ret = check_core_file(info, core, core_size); munmap(core, core_size); close(fd); unlink(filename[i]); out: free(filenames); return ret; } static int write_core_pattern(const char *core_pattern) { int err; err = write_file(core_pattern_file, core_pattern, strlen(core_pattern)); if (err) { SKIP_IF_MSG(err == -EPERM, "Try with root privileges"); perror("Error writing to core_pattern file"); return TEST_FAIL; } return TEST_PASS; } static int setup_core_pattern(char **core_pattern_, bool *changed_) { char *core_pattern; size_t len; int ret; core_pattern = malloc(PATH_MAX); if (!core_pattern) { perror("Error allocating memory"); return TEST_FAIL; } ret = read_file(core_pattern_file, core_pattern, PATH_MAX - 1, &len); if (ret) { perror("Error reading core_pattern file"); ret = TEST_FAIL; goto out; } core_pattern[len] = '\0'; /* Check whether we can predict the name of the core file. */ if (!strcmp(core_pattern, "core") || !strcmp(core_pattern, "core.%p")) *changed_ = false; else { ret = write_core_pattern("core-pkey.%p"); if (ret) goto out; *changed_ = true; } *core_pattern_ = core_pattern; ret = TEST_PASS; out: if (ret) free(core_pattern); return ret; } static int core_pkey(void) { char *core_pattern; bool changed_core_pattern; struct shared_info *info; int shm_id; int ret; pid_t pid; ret = setup_core_pattern(&core_pattern, &changed_core_pattern); if (ret) return ret; shm_id = shmget(IPC_PRIVATE, sizeof(*info), 0777 | IPC_CREAT); info = shmat(shm_id, NULL, 0); ret = init_child_sync(&info->child_sync); if (ret) return ret; pid = fork(); if (pid < 0) { perror("fork() failed"); ret = TEST_FAIL; } else if (pid == 0) ret = child(info); else ret = parent(info, pid); shmdt(info); if (pid) { destroy_child_sync(&info->child_sync); shmctl(shm_id, IPC_RMID, NULL); if (changed_core_pattern) write_core_pattern(core_pattern); } free(core_pattern); return ret; } int main(int argc, char *argv[]) { return test_harness(core_pkey, "core_pkey"); }