453 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Ptrace test for Memory Protection Key registers4 *5 * Copyright (C) 2015 Anshuman Khandual, IBM Corporation.6 * Copyright (C) 2018 IBM Corporation.7 */8#include <limits.h>9#include <linux/kernel.h>10#include <sys/mman.h>11#include <sys/types.h>12#include <sys/stat.h>13#include <sys/time.h>14#include <sys/resource.h>15#include <fcntl.h>16#include <unistd.h>17#include "ptrace.h"18#include "child.h"19 20#ifndef __NR_pkey_alloc21#define __NR_pkey_alloc 38422#endif23 24#ifndef __NR_pkey_free25#define __NR_pkey_free 38526#endif27 28#ifndef NT_PPC_PKEY29#define NT_PPC_PKEY 0x11030#endif31 32#ifndef PKEY_DISABLE_EXECUTE33#define PKEY_DISABLE_EXECUTE 0x434#endif35 36#define AMR_BITS_PER_PKEY 237#define PKEY_REG_BITS (sizeof(u64) * 8)38#define pkeyshift(pkey) (PKEY_REG_BITS - ((pkey + 1) * AMR_BITS_PER_PKEY))39 40#define CORE_FILE_LIMIT (5 * 1024 * 1024) /* 5 MB should be enough */41 42static const char core_pattern_file[] = "/proc/sys/kernel/core_pattern";43 44static const char user_write[] = "[User Write (Running)]";45static const char core_read_running[] = "[Core Read (Running)]";46 47/* Information shared between the parent and the child. */48struct shared_info {49 struct child_sync child_sync;50 51 /* AMR value the parent expects to read in the core file. */52 unsigned long amr;53 54 /* IAMR value the parent expects to read in the core file. */55 unsigned long iamr;56 57 /* UAMOR value the parent expects to read in the core file. */58 unsigned long uamor;59 60 /* When the child crashed. */61 time_t core_time;62};63 64static int sys_pkey_alloc(unsigned long flags, unsigned long init_access_rights)65{66 return syscall(__NR_pkey_alloc, flags, init_access_rights);67}68 69static int sys_pkey_free(int pkey)70{71 return syscall(__NR_pkey_free, pkey);72}73 74static int increase_core_file_limit(void)75{76 struct rlimit rlim;77 int ret;78 79 ret = getrlimit(RLIMIT_CORE, &rlim);80 FAIL_IF(ret);81 82 if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < CORE_FILE_LIMIT) {83 rlim.rlim_cur = CORE_FILE_LIMIT;84 85 if (rlim.rlim_max != RLIM_INFINITY &&86 rlim.rlim_max < CORE_FILE_LIMIT)87 rlim.rlim_max = CORE_FILE_LIMIT;88 89 ret = setrlimit(RLIMIT_CORE, &rlim);90 FAIL_IF(ret);91 }92 93 ret = getrlimit(RLIMIT_FSIZE, &rlim);94 FAIL_IF(ret);95 96 if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < CORE_FILE_LIMIT) {97 rlim.rlim_cur = CORE_FILE_LIMIT;98 99 if (rlim.rlim_max != RLIM_INFINITY &&100 rlim.rlim_max < CORE_FILE_LIMIT)101 rlim.rlim_max = CORE_FILE_LIMIT;102 103 ret = setrlimit(RLIMIT_FSIZE, &rlim);104 FAIL_IF(ret);105 }106 107 return TEST_PASS;108}109 110static int child(struct shared_info *info)111{112 bool disable_execute = true;113 int pkey1, pkey2, pkey3;114 int *ptr, ret;115 116 /* Wait until parent fills out the initial register values. */117 ret = wait_parent(&info->child_sync);118 if (ret)119 return ret;120 121 ret = increase_core_file_limit();122 FAIL_IF(ret);123 124 /* Get some pkeys so that we can change their bits in the AMR. */125 pkey1 = sys_pkey_alloc(0, PKEY_DISABLE_EXECUTE);126 if (pkey1 < 0) {127 pkey1 = sys_pkey_alloc(0, 0);128 FAIL_IF(pkey1 < 0);129 130 disable_execute = false;131 }132 133 pkey2 = sys_pkey_alloc(0, 0);134 FAIL_IF(pkey2 < 0);135 136 pkey3 = sys_pkey_alloc(0, 0);137 FAIL_IF(pkey3 < 0);138 139 info->amr |= 3ul << pkeyshift(pkey1) | 2ul << pkeyshift(pkey2);140 141 if (disable_execute)142 info->iamr |= 1ul << pkeyshift(pkey1);143 else144 info->iamr &= ~(1ul << pkeyshift(pkey1));145 146 info->iamr &= ~(1ul << pkeyshift(pkey2) | 1ul << pkeyshift(pkey3));147 148 info->uamor |= 3ul << pkeyshift(pkey1) | 3ul << pkeyshift(pkey2);149 150 printf("%-30s AMR: %016lx pkey1: %d pkey2: %d pkey3: %d\n",151 user_write, info->amr, pkey1, pkey2, pkey3);152 153 set_amr(info->amr);154 155 /*156 * We won't use pkey3. This tests whether the kernel restores the UAMOR157 * permissions after a key is freed.158 */159 sys_pkey_free(pkey3);160 161 info->core_time = time(NULL);162 163 /* Crash. */164 ptr = 0;165 *ptr = 1;166 167 /* Shouldn't get here. */168 FAIL_IF(true);169 170 return TEST_FAIL;171}172 173/* Return file size if filename exists and pass sanity check, or zero if not. */174static off_t try_core_file(const char *filename, struct shared_info *info,175 pid_t pid)176{177 struct stat buf;178 int ret;179 180 ret = stat(filename, &buf);181 if (ret == -1)182 return TEST_FAIL;183 184 /* Make sure we're not using a stale core file. */185 return buf.st_mtime >= info->core_time ? buf.st_size : TEST_FAIL;186}187 188static Elf64_Nhdr *next_note(Elf64_Nhdr *nhdr)189{190 return (void *) nhdr + sizeof(*nhdr) +191 __ALIGN_KERNEL(nhdr->n_namesz, 4) +192 __ALIGN_KERNEL(nhdr->n_descsz, 4);193}194 195static int check_core_file(struct shared_info *info, Elf64_Ehdr *ehdr,196 off_t core_size)197{198 unsigned long *regs;199 Elf64_Phdr *phdr;200 Elf64_Nhdr *nhdr;201 size_t phdr_size;202 void *p = ehdr, *note;203 int ret;204 205 ret = memcmp(ehdr->e_ident, ELFMAG, SELFMAG);206 FAIL_IF(ret);207 208 FAIL_IF(ehdr->e_type != ET_CORE);209 FAIL_IF(ehdr->e_machine != EM_PPC64);210 FAIL_IF(ehdr->e_phoff == 0 || ehdr->e_phnum == 0);211 212 /*213 * e_phnum is at most 65535 so calculating the size of the214 * program header cannot overflow.215 */216 phdr_size = sizeof(*phdr) * ehdr->e_phnum;217 218 /* Sanity check the program header table location. */219 FAIL_IF(ehdr->e_phoff + phdr_size < ehdr->e_phoff);220 FAIL_IF(ehdr->e_phoff + phdr_size > core_size);221 222 /* Find the PT_NOTE segment. */223 for (phdr = p + ehdr->e_phoff;224 (void *) phdr < p + ehdr->e_phoff + phdr_size;225 phdr += ehdr->e_phentsize)226 if (phdr->p_type == PT_NOTE)227 break;228 229 FAIL_IF((void *) phdr >= p + ehdr->e_phoff + phdr_size);230 231 /* Find the NT_PPC_PKEY note. */232 for (nhdr = p + phdr->p_offset;233 (void *) nhdr < p + phdr->p_offset + phdr->p_filesz;234 nhdr = next_note(nhdr))235 if (nhdr->n_type == NT_PPC_PKEY)236 break;237 238 FAIL_IF((void *) nhdr >= p + phdr->p_offset + phdr->p_filesz);239 FAIL_IF(nhdr->n_descsz == 0);240 241 p = nhdr;242 note = p + sizeof(*nhdr) + __ALIGN_KERNEL(nhdr->n_namesz, 4);243 244 regs = (unsigned long *) note;245 246 printf("%-30s AMR: %016lx IAMR: %016lx UAMOR: %016lx\n",247 core_read_running, regs[0], regs[1], regs[2]);248 249 FAIL_IF(regs[0] != info->amr);250 FAIL_IF(regs[1] != info->iamr);251 FAIL_IF(regs[2] != info->uamor);252 253 return TEST_PASS;254}255 256static int parent(struct shared_info *info, pid_t pid)257{258 char *filenames, *filename[3];259 int fd, i, ret, status;260 unsigned long regs[3];261 off_t core_size;262 void *core;263 264 /*265 * Get the initial values for AMR, IAMR and UAMOR and communicate them266 * to the child.267 */268 ret = ptrace_read_regs(pid, NT_PPC_PKEY, regs, 3);269 PARENT_SKIP_IF_UNSUPPORTED(ret, &info->child_sync, "PKEYs not supported");270 PARENT_FAIL_IF(ret, &info->child_sync);271 272 info->amr = regs[0];273 info->iamr = regs[1];274 info->uamor = regs[2];275 276 /* Wake up child so that it can set itself up. */277 ret = prod_child(&info->child_sync);278 PARENT_FAIL_IF(ret, &info->child_sync);279 280 ret = wait(&status);281 if (ret != pid) {282 printf("Child's exit status not captured\n");283 return TEST_FAIL;284 } else if (!WIFSIGNALED(status) || !WCOREDUMP(status)) {285 printf("Child didn't dump core\n");286 return TEST_FAIL;287 }288 289 /* Construct array of core file names to try. */290 291 filename[0] = filenames = malloc(PATH_MAX);292 if (!filenames) {293 perror("Error allocating memory");294 return TEST_FAIL;295 }296 297 ret = snprintf(filename[0], PATH_MAX, "core-pkey.%d", pid);298 if (ret < 0 || ret >= PATH_MAX) {299 ret = TEST_FAIL;300 goto out;301 }302 303 filename[1] = filename[0] + ret + 1;304 ret = snprintf(filename[1], PATH_MAX - ret - 1, "core.%d", pid);305 if (ret < 0 || ret >= PATH_MAX - ret - 1) {306 ret = TEST_FAIL;307 goto out;308 }309 filename[2] = "core";310 311 for (i = 0; i < 3; i++) {312 core_size = try_core_file(filename[i], info, pid);313 if (core_size != TEST_FAIL)314 break;315 }316 317 if (i == 3) {318 printf("Couldn't find core file\n");319 ret = TEST_FAIL;320 goto out;321 }322 323 fd = open(filename[i], O_RDONLY);324 if (fd == -1) {325 perror("Error opening core file");326 ret = TEST_FAIL;327 goto out;328 }329 330 core = mmap(NULL, core_size, PROT_READ, MAP_PRIVATE, fd, 0);331 if (core == (void *) -1) {332 perror("Error mmapping core file");333 ret = TEST_FAIL;334 goto out;335 }336 337 ret = check_core_file(info, core, core_size);338 339 munmap(core, core_size);340 close(fd);341 unlink(filename[i]);342 343 out:344 free(filenames);345 346 return ret;347}348 349static int write_core_pattern(const char *core_pattern)350{351 int err;352 353 err = write_file(core_pattern_file, core_pattern, strlen(core_pattern));354 if (err) {355 SKIP_IF_MSG(err == -EPERM, "Try with root privileges");356 perror("Error writing to core_pattern file");357 return TEST_FAIL;358 }359 360 return TEST_PASS;361}362 363static int setup_core_pattern(char **core_pattern_, bool *changed_)364{365 char *core_pattern;366 size_t len;367 int ret;368 369 core_pattern = malloc(PATH_MAX);370 if (!core_pattern) {371 perror("Error allocating memory");372 return TEST_FAIL;373 }374 375 ret = read_file(core_pattern_file, core_pattern, PATH_MAX - 1, &len);376 if (ret) {377 perror("Error reading core_pattern file");378 ret = TEST_FAIL;379 goto out;380 }381 382 core_pattern[len] = '\0';383 384 /* Check whether we can predict the name of the core file. */385 if (!strcmp(core_pattern, "core") || !strcmp(core_pattern, "core.%p"))386 *changed_ = false;387 else {388 ret = write_core_pattern("core-pkey.%p");389 if (ret)390 goto out;391 392 *changed_ = true;393 }394 395 *core_pattern_ = core_pattern;396 ret = TEST_PASS;397 398 out:399 if (ret)400 free(core_pattern);401 402 return ret;403}404 405static int core_pkey(void)406{407 char *core_pattern;408 bool changed_core_pattern;409 struct shared_info *info;410 int shm_id;411 int ret;412 pid_t pid;413 414 ret = setup_core_pattern(&core_pattern, &changed_core_pattern);415 if (ret)416 return ret;417 418 shm_id = shmget(IPC_PRIVATE, sizeof(*info), 0777 | IPC_CREAT);419 info = shmat(shm_id, NULL, 0);420 421 ret = init_child_sync(&info->child_sync);422 if (ret)423 return ret;424 425 pid = fork();426 if (pid < 0) {427 perror("fork() failed");428 ret = TEST_FAIL;429 } else if (pid == 0)430 ret = child(info);431 else432 ret = parent(info, pid);433 434 shmdt(info);435 436 if (pid) {437 destroy_child_sync(&info->child_sync);438 shmctl(shm_id, IPC_RMID, NULL);439 440 if (changed_core_pattern)441 write_core_pattern(core_pattern);442 }443 444 free(core_pattern);445 446 return ret;447}448 449int main(int argc, char *argv[])450{451 return test_harness(core_pkey, "core_pkey");452}453