1c27f2fd1SSandipan Das // SPDX-License-Identifier: GPL-2.0
2c27f2fd1SSandipan Das 
3c27f2fd1SSandipan Das /*
4c27f2fd1SSandipan Das  * Copyright 2020, Sandipan Das, IBM Corp.
5c27f2fd1SSandipan Das  *
6c27f2fd1SSandipan Das  * Test if the signal information reports the correct memory protection
7c27f2fd1SSandipan Das  * key upon getting a key access violation fault for a page that was
8c27f2fd1SSandipan Das  * attempted to be protected by two different keys from two competing
9c27f2fd1SSandipan Das  * threads at the same time.
10c27f2fd1SSandipan Das  */
11c27f2fd1SSandipan Das 
12c27f2fd1SSandipan Das #define _GNU_SOURCE
13c27f2fd1SSandipan Das #include <stdio.h>
14c27f2fd1SSandipan Das #include <stdlib.h>
15c27f2fd1SSandipan Das #include <string.h>
16c27f2fd1SSandipan Das #include <signal.h>
17c27f2fd1SSandipan Das 
18c27f2fd1SSandipan Das #include <unistd.h>
19c27f2fd1SSandipan Das #include <pthread.h>
20c27f2fd1SSandipan Das #include <sys/mman.h>
21c27f2fd1SSandipan Das 
22c27f2fd1SSandipan Das #include "pkeys.h"
23c27f2fd1SSandipan Das 
24c27f2fd1SSandipan Das #define PPC_INST_NOP	0x60000000
25c27f2fd1SSandipan Das #define PPC_INST_BLR	0x4e800020
26c27f2fd1SSandipan Das #define PROT_RWX	(PROT_READ | PROT_WRITE | PROT_EXEC)
27c27f2fd1SSandipan Das 
28c27f2fd1SSandipan Das #define NUM_ITERATIONS	1000000
29c27f2fd1SSandipan Das 
30c27f2fd1SSandipan Das static volatile sig_atomic_t perm_pkey, rest_pkey;
31c27f2fd1SSandipan Das static volatile sig_atomic_t rights, fault_count;
32c27f2fd1SSandipan Das static volatile unsigned int *volatile fault_addr;
33c27f2fd1SSandipan Das static pthread_barrier_t iteration_barrier;
34c27f2fd1SSandipan Das 
segv_handler(int signum,siginfo_t * sinfo,void * ctx)35c27f2fd1SSandipan Das static void segv_handler(int signum, siginfo_t *sinfo, void *ctx)
36c27f2fd1SSandipan Das {
37c27f2fd1SSandipan Das 	void *pgstart;
38c27f2fd1SSandipan Das 	size_t pgsize;
39c27f2fd1SSandipan Das 	int pkey;
40c27f2fd1SSandipan Das 
41c27f2fd1SSandipan Das 	pkey = siginfo_pkey(sinfo);
42c27f2fd1SSandipan Das 
43c27f2fd1SSandipan Das 	/* Check if this fault originated from a pkey access violation */
44c27f2fd1SSandipan Das 	if (sinfo->si_code != SEGV_PKUERR) {
45c27f2fd1SSandipan Das 		sigsafe_err("got a fault for an unexpected reason\n");
46c27f2fd1SSandipan Das 		_exit(1);
47c27f2fd1SSandipan Das 	}
48c27f2fd1SSandipan Das 
49c27f2fd1SSandipan Das 	/* Check if this fault originated from the expected address */
50c27f2fd1SSandipan Das 	if (sinfo->si_addr != (void *) fault_addr) {
51c27f2fd1SSandipan Das 		sigsafe_err("got a fault for an unexpected address\n");
52c27f2fd1SSandipan Das 		_exit(1);
53c27f2fd1SSandipan Das 	}
54c27f2fd1SSandipan Das 
55c27f2fd1SSandipan Das 	/* Check if this fault originated from the restrictive pkey */
56c27f2fd1SSandipan Das 	if (pkey != rest_pkey) {
57c27f2fd1SSandipan Das 		sigsafe_err("got a fault for an unexpected pkey\n");
58c27f2fd1SSandipan Das 		_exit(1);
59c27f2fd1SSandipan Das 	}
60c27f2fd1SSandipan Das 
61c27f2fd1SSandipan Das 	/* Check if too many faults have occurred for the same iteration */
62c27f2fd1SSandipan Das 	if (fault_count > 0) {
63c27f2fd1SSandipan Das 		sigsafe_err("got too many faults for the same address\n");
64c27f2fd1SSandipan Das 		_exit(1);
65c27f2fd1SSandipan Das 	}
66c27f2fd1SSandipan Das 
67c27f2fd1SSandipan Das 	pgsize = getpagesize();
68c27f2fd1SSandipan Das 	pgstart = (void *) ((unsigned long) fault_addr & ~(pgsize - 1));
69c27f2fd1SSandipan Das 
70c27f2fd1SSandipan Das 	/*
71c27f2fd1SSandipan Das 	 * If the current fault occurred due to lack of execute rights,
72c27f2fd1SSandipan Das 	 * reassociate the page with the exec-only pkey since execute
73c27f2fd1SSandipan Das 	 * rights cannot be changed directly for the faulting pkey as
74c27f2fd1SSandipan Das 	 * IAMR is inaccessible from userspace.
75c27f2fd1SSandipan Das 	 *
76c27f2fd1SSandipan Das 	 * Otherwise, if the current fault occurred due to lack of
77c27f2fd1SSandipan Das 	 * read-write rights, change the AMR permission bits for the
78c27f2fd1SSandipan Das 	 * pkey.
79c27f2fd1SSandipan Das 	 *
80c27f2fd1SSandipan Das 	 * This will let the test continue.
81c27f2fd1SSandipan Das 	 */
82c27f2fd1SSandipan Das 	if (rights == PKEY_DISABLE_EXECUTE &&
83c27f2fd1SSandipan Das 	    mprotect(pgstart, pgsize, PROT_EXEC))
84c27f2fd1SSandipan Das 		_exit(1);
85c27f2fd1SSandipan Das 	else
86c27f2fd1SSandipan Das 		pkey_set_rights(pkey, 0);
87c27f2fd1SSandipan Das 
88c27f2fd1SSandipan Das 	fault_count++;
89c27f2fd1SSandipan Das }
90c27f2fd1SSandipan Das 
91c27f2fd1SSandipan Das struct region {
92c27f2fd1SSandipan Das 	unsigned long rights;
93c27f2fd1SSandipan Das 	unsigned int *base;
94c27f2fd1SSandipan Das 	size_t size;
95c27f2fd1SSandipan Das };
96c27f2fd1SSandipan Das 
protect(void * p)97c27f2fd1SSandipan Das static void *protect(void *p)
98c27f2fd1SSandipan Das {
99c27f2fd1SSandipan Das 	unsigned long rights;
100c27f2fd1SSandipan Das 	unsigned int *base;
101c27f2fd1SSandipan Das 	size_t size;
102c27f2fd1SSandipan Das 	int tid, i;
103c27f2fd1SSandipan Das 
104c27f2fd1SSandipan Das 	tid = gettid();
105c27f2fd1SSandipan Das 	base = ((struct region *) p)->base;
106c27f2fd1SSandipan Das 	size = ((struct region *) p)->size;
107c27f2fd1SSandipan Das 	FAIL_IF_EXIT(!base);
108c27f2fd1SSandipan Das 
109c27f2fd1SSandipan Das 	/* No read, write and execute restrictions */
110c27f2fd1SSandipan Das 	rights = 0;
111c27f2fd1SSandipan Das 
112c27f2fd1SSandipan Das 	printf("tid %d, pkey permissions are %s\n", tid, pkey_rights(rights));
113c27f2fd1SSandipan Das 
114c27f2fd1SSandipan Das 	/* Allocate the permissive pkey */
115c27f2fd1SSandipan Das 	perm_pkey = sys_pkey_alloc(0, rights);
116c27f2fd1SSandipan Das 	FAIL_IF_EXIT(perm_pkey < 0);
117c27f2fd1SSandipan Das 
118c27f2fd1SSandipan Das 	/*
119c27f2fd1SSandipan Das 	 * Repeatedly try to protect the common region with a permissive
120c27f2fd1SSandipan Das 	 * pkey
121c27f2fd1SSandipan Das 	 */
122c27f2fd1SSandipan Das 	for (i = 0; i < NUM_ITERATIONS; i++) {
123c27f2fd1SSandipan Das 		/*
124c27f2fd1SSandipan Das 		 * Wait until the other thread has finished allocating the
125c27f2fd1SSandipan Das 		 * restrictive pkey or until the next iteration has begun
126c27f2fd1SSandipan Das 		 */
127c27f2fd1SSandipan Das 		pthread_barrier_wait(&iteration_barrier);
128c27f2fd1SSandipan Das 
129c27f2fd1SSandipan Das 		/* Try to associate the permissive pkey with the region */
130c27f2fd1SSandipan Das 		FAIL_IF_EXIT(sys_pkey_mprotect(base, size, PROT_RWX,
131c27f2fd1SSandipan Das 					       perm_pkey));
132c27f2fd1SSandipan Das 	}
133c27f2fd1SSandipan Das 
134c27f2fd1SSandipan Das 	/* Free the permissive pkey */
135c27f2fd1SSandipan Das 	sys_pkey_free(perm_pkey);
136c27f2fd1SSandipan Das 
137c27f2fd1SSandipan Das 	return NULL;
138c27f2fd1SSandipan Das }
139c27f2fd1SSandipan Das 
protect_access(void * p)140c27f2fd1SSandipan Das static void *protect_access(void *p)
141c27f2fd1SSandipan Das {
142c27f2fd1SSandipan Das 	size_t size, numinsns;
143c27f2fd1SSandipan Das 	unsigned int *base;
144c27f2fd1SSandipan Das 	int tid, i;
145c27f2fd1SSandipan Das 
146c27f2fd1SSandipan Das 	tid = gettid();
147c27f2fd1SSandipan Das 	base = ((struct region *) p)->base;
148c27f2fd1SSandipan Das 	size = ((struct region *) p)->size;
149c27f2fd1SSandipan Das 	rights = ((struct region *) p)->rights;
150c27f2fd1SSandipan Das 	numinsns = size / sizeof(base[0]);
151c27f2fd1SSandipan Das 	FAIL_IF_EXIT(!base);
152c27f2fd1SSandipan Das 
153c27f2fd1SSandipan Das 	/* Allocate the restrictive pkey */
154c27f2fd1SSandipan Das 	rest_pkey = sys_pkey_alloc(0, rights);
155c27f2fd1SSandipan Das 	FAIL_IF_EXIT(rest_pkey < 0);
156c27f2fd1SSandipan Das 
157c27f2fd1SSandipan Das 	printf("tid %d, pkey permissions are %s\n", tid, pkey_rights(rights));
158c27f2fd1SSandipan Das 	printf("tid %d, %s randomly in range [%p, %p]\n", tid,
159c27f2fd1SSandipan Das 	       (rights == PKEY_DISABLE_EXECUTE) ? "execute" :
160c27f2fd1SSandipan Das 	       (rights == PKEY_DISABLE_WRITE)  ? "write" : "read",
161c27f2fd1SSandipan Das 	       base, base + numinsns);
162c27f2fd1SSandipan Das 
163c27f2fd1SSandipan Das 	/*
164c27f2fd1SSandipan Das 	 * Repeatedly try to protect the common region with a restrictive
165c27f2fd1SSandipan Das 	 * pkey and read, write or execute from it
166c27f2fd1SSandipan Das 	 */
167c27f2fd1SSandipan Das 	for (i = 0; i < NUM_ITERATIONS; i++) {
168c27f2fd1SSandipan Das 		/*
169c27f2fd1SSandipan Das 		 * Wait until the other thread has finished allocating the
170c27f2fd1SSandipan Das 		 * permissive pkey or until the next iteration has begun
171c27f2fd1SSandipan Das 		 */
172c27f2fd1SSandipan Das 		pthread_barrier_wait(&iteration_barrier);
173c27f2fd1SSandipan Das 
174c27f2fd1SSandipan Das 		/* Try to associate the restrictive pkey with the region */
175c27f2fd1SSandipan Das 		FAIL_IF_EXIT(sys_pkey_mprotect(base, size, PROT_RWX,
176c27f2fd1SSandipan Das 					       rest_pkey));
177c27f2fd1SSandipan Das 
178c27f2fd1SSandipan Das 		/* Choose a random instruction word address from the region */
179c27f2fd1SSandipan Das 		fault_addr = base + (rand() % numinsns);
180c27f2fd1SSandipan Das 		fault_count = 0;
181c27f2fd1SSandipan Das 
182c27f2fd1SSandipan Das 		switch (rights) {
183c27f2fd1SSandipan Das 		/* Read protection test */
184c27f2fd1SSandipan Das 		case PKEY_DISABLE_ACCESS:
185c27f2fd1SSandipan Das 			/*
186c27f2fd1SSandipan Das 			 * Read an instruction word from the region and
187c27f2fd1SSandipan Das 			 * verify if it has not been overwritten to
188c27f2fd1SSandipan Das 			 * something unexpected
189c27f2fd1SSandipan Das 			 */
190c27f2fd1SSandipan Das 			FAIL_IF_EXIT(*fault_addr != PPC_INST_NOP &&
191c27f2fd1SSandipan Das 				     *fault_addr != PPC_INST_BLR);
192c27f2fd1SSandipan Das 			break;
193c27f2fd1SSandipan Das 
194c27f2fd1SSandipan Das 		/* Write protection test */
195c27f2fd1SSandipan Das 		case PKEY_DISABLE_WRITE:
196c27f2fd1SSandipan Das 			/*
197c27f2fd1SSandipan Das 			 * Write an instruction word to the region and
198c27f2fd1SSandipan Das 			 * verify if the overwrite has succeeded
199c27f2fd1SSandipan Das 			 */
200c27f2fd1SSandipan Das 			*fault_addr = PPC_INST_BLR;
201c27f2fd1SSandipan Das 			FAIL_IF_EXIT(*fault_addr != PPC_INST_BLR);
202c27f2fd1SSandipan Das 			break;
203c27f2fd1SSandipan Das 
204c27f2fd1SSandipan Das 		/* Execute protection test */
205c27f2fd1SSandipan Das 		case PKEY_DISABLE_EXECUTE:
206c27f2fd1SSandipan Das 			/* Jump to the region and execute instructions */
207c27f2fd1SSandipan Das 			asm volatile(
208c27f2fd1SSandipan Das 				"mtctr	%0; bctrl"
209c27f2fd1SSandipan Das 				: : "r"(fault_addr) : "ctr", "lr");
210c27f2fd1SSandipan Das 			break;
211c27f2fd1SSandipan Das 		}
212c27f2fd1SSandipan Das 
213c27f2fd1SSandipan Das 		/*
214c27f2fd1SSandipan Das 		 * Restore the restrictions originally imposed by the
215c27f2fd1SSandipan Das 		 * restrictive pkey as the signal handler would have
216c27f2fd1SSandipan Das 		 * cleared out the corresponding AMR bits
217c27f2fd1SSandipan Das 		 */
218c27f2fd1SSandipan Das 		pkey_set_rights(rest_pkey, rights);
219c27f2fd1SSandipan Das 	}
220c27f2fd1SSandipan Das 
221c27f2fd1SSandipan Das 	/* Free restrictive pkey */
222c27f2fd1SSandipan Das 	sys_pkey_free(rest_pkey);
223c27f2fd1SSandipan Das 
224c27f2fd1SSandipan Das 	return NULL;
225c27f2fd1SSandipan Das }
226c27f2fd1SSandipan Das 
reset_pkeys(unsigned long rights)227c27f2fd1SSandipan Das static void reset_pkeys(unsigned long rights)
228c27f2fd1SSandipan Das {
229c27f2fd1SSandipan Das 	int pkeys[NR_PKEYS], i;
230c27f2fd1SSandipan Das 
231c27f2fd1SSandipan Das 	/* Exhaustively allocate all available pkeys */
232c27f2fd1SSandipan Das 	for (i = 0; i < NR_PKEYS; i++)
233c27f2fd1SSandipan Das 		pkeys[i] = sys_pkey_alloc(0, rights);
234c27f2fd1SSandipan Das 
235c27f2fd1SSandipan Das 	/* Free all allocated pkeys */
236c27f2fd1SSandipan Das 	for (i = 0; i < NR_PKEYS; i++)
237c27f2fd1SSandipan Das 		sys_pkey_free(pkeys[i]);
238c27f2fd1SSandipan Das }
239c27f2fd1SSandipan Das 
test(void)240c27f2fd1SSandipan Das static int test(void)
241c27f2fd1SSandipan Das {
242c27f2fd1SSandipan Das 	pthread_t prot_thread, pacc_thread;
243c27f2fd1SSandipan Das 	struct sigaction act;
244c27f2fd1SSandipan Das 	pthread_attr_t attr;
245c27f2fd1SSandipan Das 	size_t numinsns;
246c27f2fd1SSandipan Das 	struct region r;
247c27f2fd1SSandipan Das 	int ret, i;
248c27f2fd1SSandipan Das 
249c27f2fd1SSandipan Das 	srand(time(NULL));
250c27f2fd1SSandipan Das 	ret = pkeys_unsupported();
251c27f2fd1SSandipan Das 	if (ret)
252c27f2fd1SSandipan Das 		return ret;
253c27f2fd1SSandipan Das 
254c27f2fd1SSandipan Das 	/* Allocate the region */
255c27f2fd1SSandipan Das 	r.size = getpagesize();
256c27f2fd1SSandipan Das 	r.base = mmap(NULL, r.size, PROT_RWX,
257c27f2fd1SSandipan Das 		      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
258c27f2fd1SSandipan Das 	FAIL_IF(r.base == MAP_FAILED);
259c27f2fd1SSandipan Das 
260c27f2fd1SSandipan Das 	/*
261c27f2fd1SSandipan Das 	 * Fill the region with no-ops with a branch at the end
262c27f2fd1SSandipan Das 	 * for returning to the caller
263c27f2fd1SSandipan Das 	 */
264c27f2fd1SSandipan Das 	numinsns = r.size / sizeof(r.base[0]);
265c27f2fd1SSandipan Das 	for (i = 0; i < numinsns - 1; i++)
266c27f2fd1SSandipan Das 		r.base[i] = PPC_INST_NOP;
267c27f2fd1SSandipan Das 	r.base[i] = PPC_INST_BLR;
268c27f2fd1SSandipan Das 
269c27f2fd1SSandipan Das 	/* Setup SIGSEGV handler */
270c27f2fd1SSandipan Das 	act.sa_handler = 0;
271c27f2fd1SSandipan Das 	act.sa_sigaction = segv_handler;
272c27f2fd1SSandipan Das 	FAIL_IF(sigprocmask(SIG_SETMASK, 0, &act.sa_mask) != 0);
273c27f2fd1SSandipan Das 	act.sa_flags = SA_SIGINFO;
274c27f2fd1SSandipan Das 	act.sa_restorer = 0;
275c27f2fd1SSandipan Das 	FAIL_IF(sigaction(SIGSEGV, &act, NULL) != 0);
276c27f2fd1SSandipan Das 
277c27f2fd1SSandipan Das 	/*
278c27f2fd1SSandipan Das 	 * For these tests, the parent process should clear all bits of
279c27f2fd1SSandipan Das 	 * AMR and IAMR, i.e. impose no restrictions, for all available
280c27f2fd1SSandipan Das 	 * pkeys. This will be the base for the initial AMR and IAMR
281c27f2fd1SSandipan Das 	 * values for all the test thread pairs.
282c27f2fd1SSandipan Das 	 *
283c27f2fd1SSandipan Das 	 * If the AMR and IAMR bits of all available pkeys are cleared
284c27f2fd1SSandipan Das 	 * before running the tests and a fault is generated when
285c27f2fd1SSandipan Das 	 * attempting to read, write or execute instructions from a
286c27f2fd1SSandipan Das 	 * pkey protected region, the pkey responsible for this must be
287c27f2fd1SSandipan Das 	 * the one from the protect-and-access thread since the other
288c27f2fd1SSandipan Das 	 * one is fully permissive. Despite that, if the pkey reported
289c27f2fd1SSandipan Das 	 * by siginfo is not the restrictive pkey, then there must be a
290c27f2fd1SSandipan Das 	 * kernel bug.
291c27f2fd1SSandipan Das 	 */
292c27f2fd1SSandipan Das 	reset_pkeys(0);
293c27f2fd1SSandipan Das 
294c27f2fd1SSandipan Das 	/* Setup barrier for protect and protect-and-access threads */
295c27f2fd1SSandipan Das 	FAIL_IF(pthread_attr_init(&attr) != 0);
296c27f2fd1SSandipan Das 	FAIL_IF(pthread_barrier_init(&iteration_barrier, NULL, 2) != 0);
297c27f2fd1SSandipan Das 
298c27f2fd1SSandipan Das 	/* Setup and start protect and protect-and-read threads */
299c27f2fd1SSandipan Das 	puts("starting thread pair (protect, protect-and-read)");
300c27f2fd1SSandipan Das 	r.rights = PKEY_DISABLE_ACCESS;
301c27f2fd1SSandipan Das 	FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0);
302c27f2fd1SSandipan Das 	FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0);
303c27f2fd1SSandipan Das 	FAIL_IF(pthread_join(prot_thread, NULL) != 0);
304c27f2fd1SSandipan Das 	FAIL_IF(pthread_join(pacc_thread, NULL) != 0);
305c27f2fd1SSandipan Das 
306c27f2fd1SSandipan Das 	/* Setup and start protect and protect-and-write threads */
307c27f2fd1SSandipan Das 	puts("starting thread pair (protect, protect-and-write)");
308c27f2fd1SSandipan Das 	r.rights = PKEY_DISABLE_WRITE;
309c27f2fd1SSandipan Das 	FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0);
310c27f2fd1SSandipan Das 	FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0);
311c27f2fd1SSandipan Das 	FAIL_IF(pthread_join(prot_thread, NULL) != 0);
312c27f2fd1SSandipan Das 	FAIL_IF(pthread_join(pacc_thread, NULL) != 0);
313c27f2fd1SSandipan Das 
314c27f2fd1SSandipan Das 	/* Setup and start protect and protect-and-execute threads */
315c27f2fd1SSandipan Das 	puts("starting thread pair (protect, protect-and-execute)");
316c27f2fd1SSandipan Das 	r.rights = PKEY_DISABLE_EXECUTE;
317c27f2fd1SSandipan Das 	FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0);
318c27f2fd1SSandipan Das 	FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0);
319c27f2fd1SSandipan Das 	FAIL_IF(pthread_join(prot_thread, NULL) != 0);
320c27f2fd1SSandipan Das 	FAIL_IF(pthread_join(pacc_thread, NULL) != 0);
321c27f2fd1SSandipan Das 
322c27f2fd1SSandipan Das 	/* Cleanup */
323c27f2fd1SSandipan Das 	FAIL_IF(pthread_attr_destroy(&attr) != 0);
324c27f2fd1SSandipan Das 	FAIL_IF(pthread_barrier_destroy(&iteration_barrier) != 0);
325c27f2fd1SSandipan Das 	munmap(r.base, r.size);
326c27f2fd1SSandipan Das 
327c27f2fd1SSandipan Das 	return 0;
328c27f2fd1SSandipan Das }
329c27f2fd1SSandipan Das 
main(void)330c27f2fd1SSandipan Das int main(void)
331c27f2fd1SSandipan Das {
332*92a5e1fdSSandipan Das 	return test_harness(test, "pkey_siginfo");
333c27f2fd1SSandipan Das }
334