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(&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	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(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 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(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	else
432		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