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 | |
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 | |
69 | static int sys_pkey_free(int pkey) |
70 | { |
71 | return syscall(__NR_pkey_free, pkey); |
72 | } |
73 | |
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 | |
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(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. */ |
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 | |
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 | |
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(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 | |
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(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 | |
349 | static 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 | |
363 | static 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 | |
405 | static 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 | |
449 | int main(int argc, char *argv[]) |
450 | { |
451 | return test_harness(core_pkey, "core_pkey" ); |
452 | } |
453 | |