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
30static volatile sig_atomic_t perm_pkey, rest_pkey;
31static volatile sig_atomic_t rights, fault_count;
32static volatile unsigned int *volatile fault_addr;
33static pthread_barrier_t iteration_barrier;
34
35static 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
91struct region {
92	unsigned long rights;
93	unsigned int *base;
94	size_t size;
95};
96
97static 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
140static 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
227static 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
240static 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
330int main(void)
331{
332	return test_harness(test, "pkey_siginfo");
333}
334