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