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
18#include "pkeys.h"
19
20#define PPC_INST_NOP	0x60000000
21#define PPC_INST_TRAP	0x7fe00008
22#define PPC_INST_BLR	0x4e800020
23
24static volatile sig_atomic_t fault_pkey, fault_code, fault_type;
25static volatile sig_atomic_t remaining_faults;
26static volatile unsigned int *fault_addr;
27static unsigned long pgsize, numinsns;
28static unsigned int *insns;
29
30static void trap_handler(int signum, siginfo_t *sinfo, void *ctx)
31{
32	/* Check if this fault originated from the expected address */
33	if (sinfo->si_addr != (void *) fault_addr)
34		sigsafe_err("got a fault for an unexpected address\n");
35
36	_exit(1);
37}
38
39static void segv_handler(int signum, siginfo_t *sinfo, void *ctx)
40{
41	int signal_pkey;
42
43	signal_pkey = siginfo_pkey(sinfo);
44	fault_code = sinfo->si_code;
45
46	/* Check if this fault originated from the expected address */
47	if (sinfo->si_addr != (void *) fault_addr) {
48		sigsafe_err("got a fault for an unexpected address\n");
49		_exit(1);
50	}
51
52	/* Check if too many faults have occurred for a single test case */
53	if (!remaining_faults) {
54		sigsafe_err("got too many faults for the same address\n");
55		_exit(1);
56	}
57
58
59	/* Restore permissions in order to continue */
60	switch (fault_code) {
61	case SEGV_ACCERR:
62		if (mprotect(insns, pgsize, PROT_READ | PROT_WRITE)) {
63			sigsafe_err("failed to set access permissions\n");
64			_exit(1);
65		}
66		break;
67	case SEGV_PKUERR:
68		if (signal_pkey != fault_pkey) {
69			sigsafe_err("got a fault for an unexpected pkey\n");
70			_exit(1);
71		}
72
73		switch (fault_type) {
74		case PKEY_DISABLE_ACCESS:
75			pkey_set_rights(fault_pkey, 0);
76			break;
77		case PKEY_DISABLE_EXECUTE:
78			/*
79			 * Reassociate the exec-only pkey with the region
80			 * to be able to continue. Unlike AMR, we cannot
81			 * set IAMR directly from userspace to restore the
82			 * permissions.
83			 */
84			if (mprotect(insns, pgsize, PROT_EXEC)) {
85				sigsafe_err("failed to set execute permissions\n");
86				_exit(1);
87			}
88			break;
89		default:
90			sigsafe_err("got a fault with an unexpected type\n");
91			_exit(1);
92		}
93		break;
94	default:
95		sigsafe_err("got a fault with an unexpected code\n");
96		_exit(1);
97	}
98
99	remaining_faults--;
100}
101
102static int test(void)
103{
104	struct sigaction segv_act, trap_act;
105	unsigned long rights;
106	int pkey, ret, i;
107
108	ret = pkeys_unsupported();
109	if (ret)
110		return ret;
111
112	/* Setup SIGSEGV handler */
113	segv_act.sa_handler = 0;
114	segv_act.sa_sigaction = segv_handler;
115	FAIL_IF(sigprocmask(SIG_SETMASK, 0, &segv_act.sa_mask) != 0);
116	segv_act.sa_flags = SA_SIGINFO;
117	segv_act.sa_restorer = 0;
118	FAIL_IF(sigaction(SIGSEGV, &segv_act, NULL) != 0);
119
120	/* Setup SIGTRAP handler */
121	trap_act.sa_handler = 0;
122	trap_act.sa_sigaction = trap_handler;
123	FAIL_IF(sigprocmask(SIG_SETMASK, 0, &trap_act.sa_mask) != 0);
124	trap_act.sa_flags = SA_SIGINFO;
125	trap_act.sa_restorer = 0;
126	FAIL_IF(sigaction(SIGTRAP, &trap_act, NULL) != 0);
127
128	/* Setup executable region */
129	pgsize = getpagesize();
130	numinsns = pgsize / sizeof(unsigned int);
131	insns = (unsigned int *) mmap(NULL, pgsize, PROT_READ | PROT_WRITE,
132				      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
133	FAIL_IF(insns == MAP_FAILED);
134
135	/* Write the instruction words */
136	for (i = 1; i < numinsns - 1; i++)
137		insns[i] = PPC_INST_NOP;
138
139	/*
140	 * Set the first instruction as an unconditional trap. If
141	 * the last write to this address succeeds, this should
142	 * get overwritten by a no-op.
143	 */
144	insns[0] = PPC_INST_TRAP;
145
146	/*
147	 * Later, to jump to the executable region, we use a branch
148	 * and link instruction (bctrl) which sets the return address
149	 * automatically in LR. Use that to return back.
150	 */
151	insns[numinsns - 1] = PPC_INST_BLR;
152
153	/* Allocate a pkey that restricts execution */
154	rights = PKEY_DISABLE_EXECUTE;
155	pkey = sys_pkey_alloc(0, rights);
156	FAIL_IF(pkey < 0);
157
158	/*
159	 * Pick the first instruction's address from the executable
160	 * region.
161	 */
162	fault_addr = insns;
163
164	/* The following two cases will avoid SEGV_PKUERR */
165	fault_type = -1;
166	fault_pkey = -1;
167
168	/*
169	 * Read an instruction word from the address when AMR bits
170	 * are not set i.e. the pkey permits both read and write
171	 * access.
172	 *
173	 * This should not generate a fault as having PROT_EXEC
174	 * implies PROT_READ on GNU systems. The pkey currently
175	 * restricts execution only based on the IAMR bits. The
176	 * AMR bits are cleared.
177	 */
178	remaining_faults = 0;
179	FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
180	printf("read from %p, pkey permissions are %s\n", fault_addr,
181	       pkey_rights(rights));
182	i = *fault_addr;
183	FAIL_IF(remaining_faults != 0);
184
185	/*
186	 * Write an instruction word to the address when AMR bits
187	 * are not set i.e. the pkey permits both read and write
188	 * access.
189	 *
190	 * This should generate an access fault as having just
191	 * PROT_EXEC also restricts writes. The pkey currently
192	 * restricts execution only based on the IAMR bits. The
193	 * AMR bits are cleared.
194	 */
195	remaining_faults = 1;
196	FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
197	printf("write to %p, pkey permissions are %s\n", fault_addr,
198	       pkey_rights(rights));
199	*fault_addr = PPC_INST_TRAP;
200	FAIL_IF(remaining_faults != 0 || fault_code != SEGV_ACCERR);
201
202	/* The following three cases will generate SEGV_PKUERR */
203	rights |= PKEY_DISABLE_ACCESS;
204	fault_type = PKEY_DISABLE_ACCESS;
205	fault_pkey = pkey;
206
207	/*
208	 * Read an instruction word from the address when AMR bits
209	 * are set i.e. the pkey permits neither read nor write
210	 * access.
211	 *
212	 * This should generate a pkey fault based on AMR bits only
213	 * as having PROT_EXEC implicitly allows reads.
214	 */
215	remaining_faults = 1;
216	FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
217	pkey_set_rights(pkey, rights);
218	printf("read from %p, pkey permissions are %s\n", fault_addr,
219	       pkey_rights(rights));
220	i = *fault_addr;
221	FAIL_IF(remaining_faults != 0 || fault_code != SEGV_PKUERR);
222
223	/*
224	 * Write an instruction word to the address when AMR bits
225	 * are set i.e. the pkey permits neither read nor write
226	 * access.
227	 *
228	 * This should generate two faults. First, a pkey fault
229	 * based on AMR bits and then an access fault since
230	 * PROT_EXEC does not allow writes.
231	 */
232	remaining_faults = 2;
233	FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
234	pkey_set_rights(pkey, rights);
235	printf("write to %p, pkey permissions are %s\n", fault_addr,
236	       pkey_rights(rights));
237	*fault_addr = PPC_INST_NOP;
238	FAIL_IF(remaining_faults != 0 || fault_code != SEGV_ACCERR);
239
240	/* Free the current pkey */
241	sys_pkey_free(pkey);
242
243	rights = 0;
244	do {
245		/*
246		 * Allocate pkeys with all valid combinations of read,
247		 * write and execute restrictions.
248		 */
249		pkey = sys_pkey_alloc(0, rights);
250		FAIL_IF(pkey < 0);
251
252		/*
253		 * Jump to the executable region. AMR bits may or may not
254		 * be set but they should not affect execution.
255		 *
256		 * This should generate pkey faults based on IAMR bits which
257		 * may be set to restrict execution.
258		 *
259		 * The first iteration also checks if the overwrite of the
260		 * first instruction word from a trap to a no-op succeeded.
261		 */
262		fault_pkey = pkey;
263		fault_type = -1;
264		remaining_faults = 0;
265		if (rights & PKEY_DISABLE_EXECUTE) {
266			fault_type = PKEY_DISABLE_EXECUTE;
267			remaining_faults = 1;
268		}
269
270		FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
271		printf("execute at %p, pkey permissions are %s\n", fault_addr,
272		       pkey_rights(rights));
273		asm volatile("mtctr	%0; bctrl" : : "r"(insns));
274		FAIL_IF(remaining_faults != 0);
275		if (rights & PKEY_DISABLE_EXECUTE)
276			FAIL_IF(fault_code != SEGV_PKUERR);
277
278		/* Free the current pkey */
279		sys_pkey_free(pkey);
280
281		/* Find next valid combination of pkey rights */
282		rights = next_pkey_rights(rights);
283	} while (rights);
284
285	/* Cleanup */
286	munmap((void *) insns, pgsize);
287
288	return 0;
289}
290
291int main(void)
292{
293	return test_harness(test, "pkey_exec_prot");
294}
295