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
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(sync: &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(flags: 0, PKEY_DISABLE_EXECUTE);
126 if (pkey1 < 0) {
127 pkey1 = sys_pkey_alloc(flags: 0, init_access_rights: 0);
128 FAIL_IF(pkey1 < 0);
129
130 disable_execute = false;
131 }
132
133 pkey2 = sys_pkey_alloc(flags: 0, init_access_rights: 0);
134 FAIL_IF(pkey2 < 0);
135
136 pkey3 = sys_pkey_alloc(flags: 0, init_access_rights: 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 set_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(pkey: 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(p: 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
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 them
266 * to the child.
267 */
268 ret = ptrace_read_regs(child: pid, NT_PPC_PKEY, regs, n: 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(sync: &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(buf: filename[0], PATH_MAX, fmt: "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(buf: filename[1], PATH_MAX - ret - 1, fmt: "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: 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, ehdr: 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_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_: &core_pattern, changed_: &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(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 else
432 ret = parent(info, pid);
433
434 shmdt(info);
435
436 if (pid) {
437 destroy_child_sync(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

source code of linux/tools/testing/selftests/powerpc/ptrace/core-pkey.c