brintos

brintos / linux-shallow public Read only

0
0
Text · 9.0 KiB · 2db76e5 Raw
334 lines · c
1// SPDX-License-Identifier: GPL-2.02 3/*4 * Copyright 2020, Sandipan Das, IBM Corp.5 *6 * Test if the signal information reports the correct memory protection7 * key upon getting a key access violation fault for a page that was8 * attempted to be protected by two different keys from two competing9 * threads at the same time.10 */11 12#define _GNU_SOURCE13#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	0x6000000025#define PPC_INST_BLR	0x4e80002026#define PROT_RWX	(PROT_READ | PROT_WRITE | PROT_EXEC)27 28#define NUM_ITERATIONS	100000029 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 execute73	 * rights cannot be changed directly for the faulting pkey as74	 * IAMR is inaccessible from userspace.75	 *76	 * Otherwise, if the current fault occurred due to lack of77	 * read-write rights, change the AMR permission bits for the78	 * 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	else86		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 permissive120	 * pkey121	 */122	for (i = 0; i < NUM_ITERATIONS; i++) {123		/*124		 * Wait until the other thread has finished allocating the125		 * restrictive pkey or until the next iteration has begun126		 */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 restrictive165	 * pkey and read, write or execute from it166	 */167	for (i = 0; i < NUM_ITERATIONS; i++) {168		/*169		 * Wait until the other thread has finished allocating the170		 * permissive pkey or until the next iteration has begun171		 */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 and187			 * verify if it has not been overwritten to188			 * something unexpected189			 */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 and198			 * verify if the overwrite has succeeded199			 */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 the215		 * restrictive pkey as the signal handler would have216		 * cleared out the corresponding AMR bits217		 */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 end262	 * for returning to the caller263	 */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 of279	 * AMR and IAMR, i.e. impose no restrictions, for all available280	 * pkeys. This will be the base for the initial AMR and IAMR281	 * values for all the test thread pairs.282	 *283	 * If the AMR and IAMR bits of all available pkeys are cleared284	 * before running the tests and a fault is generated when285	 * attempting to read, write or execute instructions from a286	 * pkey protected region, the pkey responsible for this must be287	 * the one from the protect-and-access thread since the other288	 * one is fully permissive. Despite that, if the pkey reported289	 * by siginfo is not the restrictive pkey, then there must be a290	 * 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