brintos

brintos / linux-shallow public Read only

0
0
Text · 7.9 KiB · 0af4f02 Raw
295 lines · 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 memory7 * protection keys works as expected.8 */9 10#define _GNU_SOURCE11#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	0x6000000021#define PPC_INST_TRAP	0x7fe0000822#define PPC_INST_BLR	0x4e80002023 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 region80			 * to be able to continue. Unlike AMR, we cannot81			 * set IAMR directly from userspace to restore the82			 * 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. If141	 * the last write to this address succeeds, this should142	 * 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 branch148	 * and link instruction (bctrl) which sets the return address149	 * 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 executable160	 * 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 bits170	 * are not set i.e. the pkey permits both read and write171	 * access.172	 *173	 * This should not generate a fault as having PROT_EXEC174	 * implies PROT_READ on GNU systems. The pkey currently175	 * restricts execution only based on the IAMR bits. The176	 * 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 bits187	 * are not set i.e. the pkey permits both read and write188	 * access.189	 *190	 * This should generate an access fault as having just191	 * PROT_EXEC also restricts writes. The pkey currently192	 * restricts execution only based on the IAMR bits. The193	 * 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 bits209	 * are set i.e. the pkey permits neither read nor write210	 * access.211	 *212	 * This should generate a pkey fault based on AMR bits only213	 * 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 bits225	 * are set i.e. the pkey permits neither read nor write226	 * access.227	 *228	 * This should generate two faults. First, a pkey fault229	 * based on AMR bits and then an access fault since230	 * 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 not254		 * be set but they should not affect execution.255		 *256		 * This should generate pkey faults based on IAMR bits which257		 * may be set to restrict execution.258		 *259		 * The first iteration also checks if the overwrite of the260		 * 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