xref: /linux/tools/testing/selftests/powerpc/mm/pkey_exec_prot.c (revision 1f9bb31e58118e14ab66239796f2fbe633e1ad32)
1 // SPDX-License-Identifier: GPL-2.0+
2 
3 /*
4  * Copyright 2020, Sandipan Das, IBM Corp.
5  *
6  * Test if applying execute protection on pages using memory
7  * protection keys works as expected.
8  */
9 
10 #define _GNU_SOURCE
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <signal.h>
15 
16 #include <unistd.h>
17 #include <sys/mman.h>
18 
19 #include "reg.h"
20 #include "utils.h"
21 
22 /*
23  * Older versions of libc use the Intel-specific access rights.
24  * Hence, override the definitions as they might be incorrect.
25  */
26 #undef PKEY_DISABLE_ACCESS
27 #define PKEY_DISABLE_ACCESS	0x3
28 
29 #undef PKEY_DISABLE_WRITE
30 #define PKEY_DISABLE_WRITE	0x2
31 
32 #undef PKEY_DISABLE_EXECUTE
33 #define PKEY_DISABLE_EXECUTE	0x4
34 
35 /* Older versions of libc do not not define this */
36 #ifndef SEGV_PKUERR
37 #define SEGV_PKUERR	4
38 #endif
39 
40 #define SI_PKEY_OFFSET	0x20
41 
42 #define SYS_pkey_mprotect	386
43 #define SYS_pkey_alloc		384
44 #define SYS_pkey_free		385
45 
46 #define PKEY_BITS_PER_PKEY	2
47 #define NR_PKEYS		32
48 #define PKEY_BITS_MASK		((1UL << PKEY_BITS_PER_PKEY) - 1)
49 
50 #define PPC_INST_NOP	0x60000000
51 #define PPC_INST_TRAP	0x7fe00008
52 #define PPC_INST_BLR	0x4e800020
53 
54 #define sigsafe_err(msg)	({ \
55 		ssize_t nbytes __attribute__((unused)); \
56 		nbytes = write(STDERR_FILENO, msg, strlen(msg)); })
57 
58 static inline unsigned long pkeyreg_get(void)
59 {
60 	return mfspr(SPRN_AMR);
61 }
62 
63 static inline void pkeyreg_set(unsigned long amr)
64 {
65 	set_amr(amr);
66 }
67 
68 static void pkey_set_rights(int pkey, unsigned long rights)
69 {
70 	unsigned long amr, shift;
71 
72 	shift = (NR_PKEYS - pkey - 1) * PKEY_BITS_PER_PKEY;
73 	amr = pkeyreg_get();
74 	amr &= ~(PKEY_BITS_MASK << shift);
75 	amr |= (rights & PKEY_BITS_MASK) << shift;
76 	pkeyreg_set(amr);
77 }
78 
79 static int sys_pkey_mprotect(void *addr, size_t len, int prot, int pkey)
80 {
81 	return syscall(SYS_pkey_mprotect, addr, len, prot, pkey);
82 }
83 
84 static int sys_pkey_alloc(unsigned long flags, unsigned long rights)
85 {
86 	return syscall(SYS_pkey_alloc, flags, rights);
87 }
88 
89 static int sys_pkey_free(int pkey)
90 {
91 	return syscall(SYS_pkey_free, pkey);
92 }
93 
94 static volatile sig_atomic_t fault_pkey, fault_code, fault_type;
95 static volatile sig_atomic_t remaining_faults;
96 static volatile unsigned int *fault_addr;
97 static unsigned long pgsize, numinsns;
98 static unsigned int *insns;
99 
100 static void trap_handler(int signum, siginfo_t *sinfo, void *ctx)
101 {
102 	/* Check if this fault originated from the expected address */
103 	if (sinfo->si_addr != (void *) fault_addr)
104 		sigsafe_err("got a fault for an unexpected address\n");
105 
106 	_exit(1);
107 }
108 
109 static void segv_handler(int signum, siginfo_t *sinfo, void *ctx)
110 {
111 	int signal_pkey;
112 
113 	/*
114 	 * In older versions of libc, siginfo_t does not have si_pkey as
115 	 * a member.
116 	 */
117 #ifdef si_pkey
118 	signal_pkey = sinfo->si_pkey;
119 #else
120 	signal_pkey = *((int *)(((char *) sinfo) + SI_PKEY_OFFSET));
121 #endif
122 
123 	fault_code = sinfo->si_code;
124 
125 	/* Check if this fault originated from the expected address */
126 	if (sinfo->si_addr != (void *) fault_addr) {
127 		sigsafe_err("got a fault for an unexpected address\n");
128 		_exit(1);
129 	}
130 
131 	/* Check if too many faults have occurred for a single test case */
132 	if (!remaining_faults) {
133 		sigsafe_err("got too many faults for the same address\n");
134 		_exit(1);
135 	}
136 
137 
138 	/* Restore permissions in order to continue */
139 	switch (fault_code) {
140 	case SEGV_ACCERR:
141 		if (mprotect(insns, pgsize, PROT_READ | PROT_WRITE)) {
142 			sigsafe_err("failed to set access permissions\n");
143 			_exit(1);
144 		}
145 		break;
146 	case SEGV_PKUERR:
147 		if (signal_pkey != fault_pkey) {
148 			sigsafe_err("got a fault for an unexpected pkey\n");
149 			_exit(1);
150 		}
151 
152 		switch (fault_type) {
153 		case PKEY_DISABLE_ACCESS:
154 			pkey_set_rights(fault_pkey, 0);
155 			break;
156 		case PKEY_DISABLE_EXECUTE:
157 			/*
158 			 * Reassociate the exec-only pkey with the region
159 			 * to be able to continue. Unlike AMR, we cannot
160 			 * set IAMR directly from userspace to restore the
161 			 * permissions.
162 			 */
163 			if (mprotect(insns, pgsize, PROT_EXEC)) {
164 				sigsafe_err("failed to set execute permissions\n");
165 				_exit(1);
166 			}
167 			break;
168 		default:
169 			sigsafe_err("got a fault with an unexpected type\n");
170 			_exit(1);
171 		}
172 		break;
173 	default:
174 		sigsafe_err("got a fault with an unexpected code\n");
175 		_exit(1);
176 	}
177 
178 	remaining_faults--;
179 }
180 
181 static int pkeys_unsupported(void)
182 {
183 	bool hash_mmu = false;
184 	int pkey;
185 
186 	/* Protection keys are currently supported on Hash MMU only */
187 	FAIL_IF(using_hash_mmu(&hash_mmu));
188 	SKIP_IF(!hash_mmu);
189 
190 	/* Check if the system call is supported */
191 	pkey = sys_pkey_alloc(0, 0);
192 	SKIP_IF(pkey < 0);
193 	sys_pkey_free(pkey);
194 
195 	return 0;
196 }
197 
198 static int test(void)
199 {
200 	struct sigaction segv_act, trap_act;
201 	int pkey, ret, i;
202 
203 	ret = pkeys_unsupported();
204 	if (ret)
205 		return ret;
206 
207 	/* Setup SIGSEGV handler */
208 	segv_act.sa_handler = 0;
209 	segv_act.sa_sigaction = segv_handler;
210 	FAIL_IF(sigprocmask(SIG_SETMASK, 0, &segv_act.sa_mask) != 0);
211 	segv_act.sa_flags = SA_SIGINFO;
212 	segv_act.sa_restorer = 0;
213 	FAIL_IF(sigaction(SIGSEGV, &segv_act, NULL) != 0);
214 
215 	/* Setup SIGTRAP handler */
216 	trap_act.sa_handler = 0;
217 	trap_act.sa_sigaction = trap_handler;
218 	FAIL_IF(sigprocmask(SIG_SETMASK, 0, &trap_act.sa_mask) != 0);
219 	trap_act.sa_flags = SA_SIGINFO;
220 	trap_act.sa_restorer = 0;
221 	FAIL_IF(sigaction(SIGTRAP, &trap_act, NULL) != 0);
222 
223 	/* Setup executable region */
224 	pgsize = getpagesize();
225 	numinsns = pgsize / sizeof(unsigned int);
226 	insns = (unsigned int *) mmap(NULL, pgsize, PROT_READ | PROT_WRITE,
227 				      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
228 	FAIL_IF(insns == MAP_FAILED);
229 
230 	/* Write the instruction words */
231 	for (i = 1; i < numinsns - 1; i++)
232 		insns[i] = PPC_INST_NOP;
233 
234 	/*
235 	 * Set the first instruction as an unconditional trap. If
236 	 * the last write to this address succeeds, this should
237 	 * get overwritten by a no-op.
238 	 */
239 	insns[0] = PPC_INST_TRAP;
240 
241 	/*
242 	 * Later, to jump to the executable region, we use a branch
243 	 * and link instruction (bctrl) which sets the return address
244 	 * automatically in LR. Use that to return back.
245 	 */
246 	insns[numinsns - 1] = PPC_INST_BLR;
247 
248 	/* Allocate a pkey that restricts execution */
249 	pkey = sys_pkey_alloc(0, PKEY_DISABLE_EXECUTE);
250 	FAIL_IF(pkey < 0);
251 
252 	/*
253 	 * Pick the first instruction's address from the executable
254 	 * region.
255 	 */
256 	fault_addr = insns;
257 
258 	/* The following two cases will avoid SEGV_PKUERR */
259 	fault_type = -1;
260 	fault_pkey = -1;
261 
262 	/*
263 	 * Read an instruction word from the address when AMR bits
264 	 * are not set i.e. the pkey permits both read and write
265 	 * access.
266 	 *
267 	 * This should not generate a fault as having PROT_EXEC
268 	 * implies PROT_READ on GNU systems. The pkey currently
269 	 * restricts execution only based on the IAMR bits. The
270 	 * AMR bits are cleared.
271 	 */
272 	remaining_faults = 0;
273 	FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
274 	printf("read from %p, pkey is execute-disabled, access-enabled\n",
275 	       (void *) fault_addr);
276 	i = *fault_addr;
277 	FAIL_IF(remaining_faults != 0);
278 
279 	/*
280 	 * Write an instruction word to the address when AMR bits
281 	 * are not set i.e. the pkey permits both read and write
282 	 * access.
283 	 *
284 	 * This should generate an access fault as having just
285 	 * PROT_EXEC also restricts writes. The pkey currently
286 	 * restricts execution only based on the IAMR bits. The
287 	 * AMR bits are cleared.
288 	 */
289 	remaining_faults = 1;
290 	FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
291 	printf("write to %p, pkey is execute-disabled, access-enabled\n",
292 	       (void *) fault_addr);
293 	*fault_addr = PPC_INST_TRAP;
294 	FAIL_IF(remaining_faults != 0 || fault_code != SEGV_ACCERR);
295 
296 	/* The following three cases will generate SEGV_PKUERR */
297 	fault_type = PKEY_DISABLE_ACCESS;
298 	fault_pkey = pkey;
299 
300 	/*
301 	 * Read an instruction word from the address when AMR bits
302 	 * are set i.e. the pkey permits neither read nor write
303 	 * access.
304 	 *
305 	 * This should generate a pkey fault based on AMR bits only
306 	 * as having PROT_EXEC implicitly allows reads.
307 	 */
308 	remaining_faults = 1;
309 	FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
310 	printf("read from %p, pkey is execute-disabled, access-disabled\n",
311 	       (void *) fault_addr);
312 	pkey_set_rights(pkey, PKEY_DISABLE_ACCESS);
313 	i = *fault_addr;
314 	FAIL_IF(remaining_faults != 0 || fault_code != SEGV_PKUERR);
315 
316 	/*
317 	 * Write an instruction word to the address when AMR bits
318 	 * are set i.e. the pkey permits neither read nor write
319 	 * access.
320 	 *
321 	 * This should generate two faults. First, a pkey fault
322 	 * based on AMR bits and then an access fault since
323 	 * PROT_EXEC does not allow writes.
324 	 */
325 	remaining_faults = 2;
326 	FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
327 	printf("write to %p, pkey is execute-disabled, access-disabled\n",
328 	       (void *) fault_addr);
329 	pkey_set_rights(pkey, PKEY_DISABLE_ACCESS);
330 	*fault_addr = PPC_INST_NOP;
331 	FAIL_IF(remaining_faults != 0 || fault_code != SEGV_ACCERR);
332 
333 	/*
334 	 * Jump to the executable region when AMR bits are set i.e.
335 	 * the pkey permits neither read nor write access.
336 	 *
337 	 * This should generate a pkey fault based on IAMR bits which
338 	 * are set to not permit execution. AMR bits should not affect
339 	 * execution.
340 	 *
341 	 * This also checks if the overwrite of the first instruction
342 	 * word from a trap to a no-op succeeded.
343 	 */
344 	fault_addr = insns;
345 	fault_type = PKEY_DISABLE_EXECUTE;
346 	fault_pkey = pkey;
347 	remaining_faults = 1;
348 	FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
349 	pkey_set_rights(pkey, PKEY_DISABLE_ACCESS);
350 	printf("execute at %p, pkey is execute-disabled, access-disabled\n",
351 	       (void *) fault_addr);
352 	asm volatile("mtctr	%0; bctrl" : : "r"(insns));
353 	FAIL_IF(remaining_faults != 0 || fault_code != SEGV_PKUERR);
354 
355 	/*
356 	 * Free the current pkey and allocate a new one that is
357 	 * fully permissive.
358 	 */
359 	sys_pkey_free(pkey);
360 	pkey = sys_pkey_alloc(0, 0);
361 
362 	/*
363 	 * Jump to the executable region when AMR bits are not set
364 	 * i.e. the pkey permits read and write access.
365 	 *
366 	 * This should not generate any faults as the IAMR bits are
367 	 * also not set and hence will the pkey will not restrict
368 	 * execution.
369 	 */
370 	fault_pkey = pkey;
371 	remaining_faults = 0;
372 	FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
373 	printf("execute at %p, pkey is execute-enabled, access-enabled\n",
374 	       (void *) fault_addr);
375 	asm volatile("mtctr	%0; bctrl" : : "r"(insns));
376 	FAIL_IF(remaining_faults != 0);
377 
378 	/* Cleanup */
379 	munmap((void *) insns, pgsize);
380 	sys_pkey_free(pkey);
381 
382 	return 0;
383 }
384 
385 int main(void)
386 {
387 	test_harness(test, "pkey_exec_prot");
388 }
389