File Index
Files and directories
Sign in
FILE

pkey_exec_prot.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/powerpc/mm/pkey_exec_prot.c C
  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, PKEY_UNRESTRICTED);
 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}
~/ Read-only file preview.