1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Ptrace test for Memory Protection Key registers
4 *
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_alloc
21 #define __NR_pkey_alloc 384
22 #endif
23
24 #ifndef __NR_pkey_free
25 #define __NR_pkey_free 385
26 #endif
27
28 #ifndef NT_PPC_PKEY
29 #define NT_PPC_PKEY 0x110
30 #endif
31
32 #ifndef PKEY_DISABLE_EXECUTE
33 #define PKEY_DISABLE_EXECUTE 0x4
34 #endif
35
36 #define AMR_BITS_PER_PKEY 2
37 #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
42 static const char core_pattern_file[] = "/proc/sys/kernel/core_pattern";
43
44 static const char user_write[] = "[User Write (Running)]";
45 static const char core_read_running[] = "[Core Read (Running)]";
46
47 /* Information shared between the parent and the child. */
48 struct 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
sys_pkey_alloc(unsigned long flags,unsigned long init_access_rights)64 static 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
sys_pkey_free(int pkey)69 static int sys_pkey_free(int pkey)
70 {
71 return syscall(__NR_pkey_free, pkey);
72 }
73
increase_core_file_limit(void)74 static 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
child(struct shared_info * info)110 static 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 else
144 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 mtspr(SPRN_AMR, info->amr);
154
155 /*
156 * We won't use pkey3. This tests whether the kernel restores the UAMOR
157 * 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. */
try_core_file(const char * filename,struct shared_info * info,pid_t pid)174 static 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
next_note(Elf64_Nhdr * nhdr)188 static 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
check_core_file(struct shared_info * info,Elf64_Ehdr * ehdr,off_t core_size)195 static 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 the
214 * 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
parent(struct shared_info * info,pid_t pid)256 static 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 them
266 * to the child.
267 */
268 ret = ptrace_read_regs(pid, NT_PPC_PKEY, regs, 3);
269 PARENT_SKIP_IF_UNSUPPORTED(ret, &info->child_sync);
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 mmaping 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
write_core_pattern(const char * core_pattern)349 static int write_core_pattern(const char *core_pattern)
350 {
351 size_t len = strlen(core_pattern), ret;
352 FILE *f;
353
354 f = fopen(core_pattern_file, "w");
355 if (!f) {
356 perror("Error writing to core_pattern file");
357 return TEST_FAIL;
358 }
359
360 ret = fwrite(core_pattern, 1, len, f);
361 fclose(f);
362 if (ret != len) {
363 perror("Error writing to core_pattern file");
364 return TEST_FAIL;
365 }
366
367 return TEST_PASS;
368 }
369
setup_core_pattern(char ** core_pattern_,bool * changed_)370 static int setup_core_pattern(char **core_pattern_, bool *changed_)
371 {
372 FILE *f;
373 char *core_pattern;
374 int ret;
375
376 core_pattern = malloc(PATH_MAX);
377 if (!core_pattern) {
378 perror("Error allocating memory");
379 return TEST_FAIL;
380 }
381
382 f = fopen(core_pattern_file, "r");
383 if (!f) {
384 perror("Error opening core_pattern file");
385 ret = TEST_FAIL;
386 goto out;
387 }
388
389 ret = fread(core_pattern, 1, PATH_MAX, f);
390 fclose(f);
391 if (!ret) {
392 perror("Error reading core_pattern file");
393 ret = TEST_FAIL;
394 goto out;
395 }
396
397 /* Check whether we can predict the name of the core file. */
398 if (!strcmp(core_pattern, "core") || !strcmp(core_pattern, "core.%p"))
399 *changed_ = false;
400 else {
401 ret = write_core_pattern("core-pkey.%p");
402 if (ret)
403 goto out;
404
405 *changed_ = true;
406 }
407
408 *core_pattern_ = core_pattern;
409 ret = TEST_PASS;
410
411 out:
412 if (ret)
413 free(core_pattern);
414
415 return ret;
416 }
417
core_pkey(void)418 static int core_pkey(void)
419 {
420 char *core_pattern;
421 bool changed_core_pattern;
422 struct shared_info *info;
423 int shm_id;
424 int ret;
425 pid_t pid;
426
427 ret = setup_core_pattern(&core_pattern, &changed_core_pattern);
428 if (ret)
429 return ret;
430
431 shm_id = shmget(IPC_PRIVATE, sizeof(*info), 0777 | IPC_CREAT);
432 info = shmat(shm_id, NULL, 0);
433
434 ret = init_child_sync(&info->child_sync);
435 if (ret)
436 return ret;
437
438 pid = fork();
439 if (pid < 0) {
440 perror("fork() failed");
441 ret = TEST_FAIL;
442 } else if (pid == 0)
443 ret = child(info);
444 else
445 ret = parent(info, pid);
446
447 shmdt(info);
448
449 if (pid) {
450 destroy_child_sync(&info->child_sync);
451 shmctl(shm_id, IPC_RMID, NULL);
452
453 if (changed_core_pattern)
454 write_core_pattern(core_pattern);
455 }
456
457 free(core_pattern);
458
459 return ret;
460 }
461
main(int argc,char * argv[])462 int main(int argc, char *argv[])
463 {
464 return test_harness(core_pkey, "core_pkey");
465 }
466