| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright 2020, Sandipan Das, IBM Corp.
- *
- * Test if the signal information reports the correct memory protection
- * key upon getting a key access violation fault for a page that was
- * attempted to be protected by two different keys from two competing
- * threads at the same time.
- */
- #define _GNU_SOURCE
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <signal.h>
- #include <unistd.h>
- #include <pthread.h>
- #include <sys/mman.h>
- #include "pkeys.h"
- #define PPC_INST_NOP 0x60000000
- #define PPC_INST_BLR 0x4e800020
- #define PROT_RWX (PROT_READ | PROT_WRITE | PROT_EXEC)
- #define NUM_ITERATIONS 1000000
- static volatile sig_atomic_t perm_pkey, rest_pkey;
- static volatile sig_atomic_t rights, fault_count;
- static volatile unsigned int *volatile fault_addr;
- static pthread_barrier_t iteration_barrier;
- static void segv_handler(int signum, siginfo_t *sinfo, void *ctx)
- {
- void *pgstart;
- size_t pgsize;
- int pkey;
- pkey = siginfo_pkey(sinfo);
- /* Check if this fault originated from a pkey access violation */
- if (sinfo->si_code != SEGV_PKUERR) {
- sigsafe_err("got a fault for an unexpected reason\n");
- _exit(1);
- }
- /* Check if this fault originated from the expected address */
- if (sinfo->si_addr != (void *) fault_addr) {
- sigsafe_err("got a fault for an unexpected address\n");
- _exit(1);
- }
- /* Check if this fault originated from the restrictive pkey */
- if (pkey != rest_pkey) {
- sigsafe_err("got a fault for an unexpected pkey\n");
- _exit(1);
- }
- /* Check if too many faults have occurred for the same iteration */
- if (fault_count > 0) {
- sigsafe_err("got too many faults for the same address\n");
- _exit(1);
- }
- pgsize = getpagesize();
- pgstart = (void *) ((unsigned long) fault_addr & ~(pgsize - 1));
- /*
- * If the current fault occurred due to lack of execute rights,
- * reassociate the page with the exec-only pkey since execute
- * rights cannot be changed directly for the faulting pkey as
- * IAMR is inaccessible from userspace.
- *
- * Otherwise, if the current fault occurred due to lack of
- * read-write rights, change the AMR permission bits for the
- * pkey.
- *
- * This will let the test continue.
- */
- if (rights == PKEY_DISABLE_EXECUTE &&
- mprotect(pgstart, pgsize, PROT_EXEC))
- _exit(1);
- else
- pkey_set_rights(pkey, 0);
- fault_count++;
- }
- struct region {
- unsigned long rights;
- unsigned int *base;
- size_t size;
- };
- static void *protect(void *p)
- {
- unsigned long rights;
- unsigned int *base;
- size_t size;
- int tid, i;
- tid = gettid();
- base = ((struct region *) p)->base;
- size = ((struct region *) p)->size;
- FAIL_IF_EXIT(!base);
- /* No read, write and execute restrictions */
- rights = 0;
- printf("tid %d, pkey permissions are %s\n", tid, pkey_rights(rights));
- /* Allocate the permissive pkey */
- perm_pkey = sys_pkey_alloc(0, rights);
- FAIL_IF_EXIT(perm_pkey < 0);
- /*
- * Repeatedly try to protect the common region with a permissive
- * pkey
- */
- for (i = 0; i < NUM_ITERATIONS; i++) {
- /*
- * Wait until the other thread has finished allocating the
- * restrictive pkey or until the next iteration has begun
- */
- pthread_barrier_wait(&iteration_barrier);
- /* Try to associate the permissive pkey with the region */
- FAIL_IF_EXIT(sys_pkey_mprotect(base, size, PROT_RWX,
- perm_pkey));
- }
- /* Free the permissive pkey */
- sys_pkey_free(perm_pkey);
- return NULL;
- }
- static void *protect_access(void *p)
- {
- size_t size, numinsns;
- unsigned int *base;
- int tid, i;
- tid = gettid();
- base = ((struct region *) p)->base;
- size = ((struct region *) p)->size;
- rights = ((struct region *) p)->rights;
- numinsns = size / sizeof(base[0]);
- FAIL_IF_EXIT(!base);
- /* Allocate the restrictive pkey */
- rest_pkey = sys_pkey_alloc(0, rights);
- FAIL_IF_EXIT(rest_pkey < 0);
- printf("tid %d, pkey permissions are %s\n", tid, pkey_rights(rights));
- printf("tid %d, %s randomly in range [%p, %p]\n", tid,
- (rights == PKEY_DISABLE_EXECUTE) ? "execute" :
- (rights == PKEY_DISABLE_WRITE) ? "write" : "read",
- base, base + numinsns);
- /*
- * Repeatedly try to protect the common region with a restrictive
- * pkey and read, write or execute from it
- */
- for (i = 0; i < NUM_ITERATIONS; i++) {
- /*
- * Wait until the other thread has finished allocating the
- * permissive pkey or until the next iteration has begun
- */
- pthread_barrier_wait(&iteration_barrier);
- /* Try to associate the restrictive pkey with the region */
- FAIL_IF_EXIT(sys_pkey_mprotect(base, size, PROT_RWX,
- rest_pkey));
- /* Choose a random instruction word address from the region */
- fault_addr = base + (rand() % numinsns);
- fault_count = 0;
- switch (rights) {
- /* Read protection test */
- case PKEY_DISABLE_ACCESS:
- /*
- * Read an instruction word from the region and
- * verify if it has not been overwritten to
- * something unexpected
- */
- FAIL_IF_EXIT(*fault_addr != PPC_INST_NOP &&
- *fault_addr != PPC_INST_BLR);
- break;
- /* Write protection test */
- case PKEY_DISABLE_WRITE:
- /*
- * Write an instruction word to the region and
- * verify if the overwrite has succeeded
- */
- *fault_addr = PPC_INST_BLR;
- FAIL_IF_EXIT(*fault_addr != PPC_INST_BLR);
- break;
- /* Execute protection test */
- case PKEY_DISABLE_EXECUTE:
- /* Jump to the region and execute instructions */
- asm volatile(
- "mtctr %0; bctrl"
- : : "r"(fault_addr) : "ctr", "lr");
- break;
- }
- /*
- * Restore the restrictions originally imposed by the
- * restrictive pkey as the signal handler would have
- * cleared out the corresponding AMR bits
- */
- pkey_set_rights(rest_pkey, rights);
- }
- /* Free restrictive pkey */
- sys_pkey_free(rest_pkey);
- return NULL;
- }
- static void reset_pkeys(unsigned long rights)
- {
- int pkeys[NR_PKEYS], i;
- /* Exhaustively allocate all available pkeys */
- for (i = 0; i < NR_PKEYS; i++)
- pkeys[i] = sys_pkey_alloc(0, rights);
- /* Free all allocated pkeys */
- for (i = 0; i < NR_PKEYS; i++)
- sys_pkey_free(pkeys[i]);
- }
- static int test(void)
- {
- pthread_t prot_thread, pacc_thread;
- struct sigaction act;
- pthread_attr_t attr;
- size_t numinsns;
- struct region r;
- int ret, i;
- srand(time(NULL));
- ret = pkeys_unsupported();
- if (ret)
- return ret;
- /* Allocate the region */
- r.size = getpagesize();
- r.base = mmap(NULL, r.size, PROT_RWX,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- FAIL_IF(r.base == MAP_FAILED);
- /*
- * Fill the region with no-ops with a branch at the end
- * for returning to the caller
- */
- numinsns = r.size / sizeof(r.base[0]);
- for (i = 0; i < numinsns - 1; i++)
- r.base[i] = PPC_INST_NOP;
- r.base[i] = PPC_INST_BLR;
- /* Setup SIGSEGV handler */
- act.sa_handler = 0;
- act.sa_sigaction = segv_handler;
- FAIL_IF(sigprocmask(SIG_SETMASK, 0, &act.sa_mask) != 0);
- act.sa_flags = SA_SIGINFO;
- act.sa_restorer = 0;
- FAIL_IF(sigaction(SIGSEGV, &act, NULL) != 0);
- /*
- * For these tests, the parent process should clear all bits of
- * AMR and IAMR, i.e. impose no restrictions, for all available
- * pkeys. This will be the base for the initial AMR and IAMR
- * values for all the test thread pairs.
- *
- * If the AMR and IAMR bits of all available pkeys are cleared
- * before running the tests and a fault is generated when
- * attempting to read, write or execute instructions from a
- * pkey protected region, the pkey responsible for this must be
- * the one from the protect-and-access thread since the other
- * one is fully permissive. Despite that, if the pkey reported
- * by siginfo is not the restrictive pkey, then there must be a
- * kernel bug.
- */
- reset_pkeys(0);
- /* Setup barrier for protect and protect-and-access threads */
- FAIL_IF(pthread_attr_init(&attr) != 0);
- FAIL_IF(pthread_barrier_init(&iteration_barrier, NULL, 2) != 0);
- /* Setup and start protect and protect-and-read threads */
- puts("starting thread pair (protect, protect-and-read)");
- r.rights = PKEY_DISABLE_ACCESS;
- FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0);
- FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0);
- FAIL_IF(pthread_join(prot_thread, NULL) != 0);
- FAIL_IF(pthread_join(pacc_thread, NULL) != 0);
- /* Setup and start protect and protect-and-write threads */
- puts("starting thread pair (protect, protect-and-write)");
- r.rights = PKEY_DISABLE_WRITE;
- FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0);
- FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0);
- FAIL_IF(pthread_join(prot_thread, NULL) != 0);
- FAIL_IF(pthread_join(pacc_thread, NULL) != 0);
- /* Setup and start protect and protect-and-execute threads */
- puts("starting thread pair (protect, protect-and-execute)");
- r.rights = PKEY_DISABLE_EXECUTE;
- FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0);
- FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0);
- FAIL_IF(pthread_join(prot_thread, NULL) != 0);
- FAIL_IF(pthread_join(pacc_thread, NULL) != 0);
- /* Cleanup */
- FAIL_IF(pthread_attr_destroy(&attr) != 0);
- FAIL_IF(pthread_barrier_destroy(&iteration_barrier) != 0);
- munmap(r.base, r.size);
- return 0;
- }
- int main(void)
- {
- return test_harness(test, "pkey_siginfo");
- }
|