| // 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"); |
| } |