162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci/* 462306a36Sopenharmony_ci * Copyright 2020, Sandipan Das, IBM Corp. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Test if the signal information reports the correct memory protection 762306a36Sopenharmony_ci * key upon getting a key access violation fault for a page that was 862306a36Sopenharmony_ci * attempted to be protected by two different keys from two competing 962306a36Sopenharmony_ci * threads at the same time. 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#define _GNU_SOURCE 1362306a36Sopenharmony_ci#include <stdio.h> 1462306a36Sopenharmony_ci#include <stdlib.h> 1562306a36Sopenharmony_ci#include <string.h> 1662306a36Sopenharmony_ci#include <signal.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <unistd.h> 1962306a36Sopenharmony_ci#include <pthread.h> 2062306a36Sopenharmony_ci#include <sys/mman.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#include "pkeys.h" 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#define PPC_INST_NOP 0x60000000 2562306a36Sopenharmony_ci#define PPC_INST_BLR 0x4e800020 2662306a36Sopenharmony_ci#define PROT_RWX (PROT_READ | PROT_WRITE | PROT_EXEC) 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define NUM_ITERATIONS 1000000 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic volatile sig_atomic_t perm_pkey, rest_pkey; 3162306a36Sopenharmony_cistatic volatile sig_atomic_t rights, fault_count; 3262306a36Sopenharmony_cistatic volatile unsigned int *volatile fault_addr; 3362306a36Sopenharmony_cistatic pthread_barrier_t iteration_barrier; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic void segv_handler(int signum, siginfo_t *sinfo, void *ctx) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci void *pgstart; 3862306a36Sopenharmony_ci size_t pgsize; 3962306a36Sopenharmony_ci int pkey; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci pkey = siginfo_pkey(sinfo); 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci /* Check if this fault originated from a pkey access violation */ 4462306a36Sopenharmony_ci if (sinfo->si_code != SEGV_PKUERR) { 4562306a36Sopenharmony_ci sigsafe_err("got a fault for an unexpected reason\n"); 4662306a36Sopenharmony_ci _exit(1); 4762306a36Sopenharmony_ci } 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci /* Check if this fault originated from the expected address */ 5062306a36Sopenharmony_ci if (sinfo->si_addr != (void *) fault_addr) { 5162306a36Sopenharmony_ci sigsafe_err("got a fault for an unexpected address\n"); 5262306a36Sopenharmony_ci _exit(1); 5362306a36Sopenharmony_ci } 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci /* Check if this fault originated from the restrictive pkey */ 5662306a36Sopenharmony_ci if (pkey != rest_pkey) { 5762306a36Sopenharmony_ci sigsafe_err("got a fault for an unexpected pkey\n"); 5862306a36Sopenharmony_ci _exit(1); 5962306a36Sopenharmony_ci } 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci /* Check if too many faults have occurred for the same iteration */ 6262306a36Sopenharmony_ci if (fault_count > 0) { 6362306a36Sopenharmony_ci sigsafe_err("got too many faults for the same address\n"); 6462306a36Sopenharmony_ci _exit(1); 6562306a36Sopenharmony_ci } 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci pgsize = getpagesize(); 6862306a36Sopenharmony_ci pgstart = (void *) ((unsigned long) fault_addr & ~(pgsize - 1)); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci /* 7162306a36Sopenharmony_ci * If the current fault occurred due to lack of execute rights, 7262306a36Sopenharmony_ci * reassociate the page with the exec-only pkey since execute 7362306a36Sopenharmony_ci * rights cannot be changed directly for the faulting pkey as 7462306a36Sopenharmony_ci * IAMR is inaccessible from userspace. 7562306a36Sopenharmony_ci * 7662306a36Sopenharmony_ci * Otherwise, if the current fault occurred due to lack of 7762306a36Sopenharmony_ci * read-write rights, change the AMR permission bits for the 7862306a36Sopenharmony_ci * pkey. 7962306a36Sopenharmony_ci * 8062306a36Sopenharmony_ci * This will let the test continue. 8162306a36Sopenharmony_ci */ 8262306a36Sopenharmony_ci if (rights == PKEY_DISABLE_EXECUTE && 8362306a36Sopenharmony_ci mprotect(pgstart, pgsize, PROT_EXEC)) 8462306a36Sopenharmony_ci _exit(1); 8562306a36Sopenharmony_ci else 8662306a36Sopenharmony_ci pkey_set_rights(pkey, 0); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci fault_count++; 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistruct region { 9262306a36Sopenharmony_ci unsigned long rights; 9362306a36Sopenharmony_ci unsigned int *base; 9462306a36Sopenharmony_ci size_t size; 9562306a36Sopenharmony_ci}; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic void *protect(void *p) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci unsigned long rights; 10062306a36Sopenharmony_ci unsigned int *base; 10162306a36Sopenharmony_ci size_t size; 10262306a36Sopenharmony_ci int tid, i; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci tid = gettid(); 10562306a36Sopenharmony_ci base = ((struct region *) p)->base; 10662306a36Sopenharmony_ci size = ((struct region *) p)->size; 10762306a36Sopenharmony_ci FAIL_IF_EXIT(!base); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci /* No read, write and execute restrictions */ 11062306a36Sopenharmony_ci rights = 0; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci printf("tid %d, pkey permissions are %s\n", tid, pkey_rights(rights)); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci /* Allocate the permissive pkey */ 11562306a36Sopenharmony_ci perm_pkey = sys_pkey_alloc(0, rights); 11662306a36Sopenharmony_ci FAIL_IF_EXIT(perm_pkey < 0); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci /* 11962306a36Sopenharmony_ci * Repeatedly try to protect the common region with a permissive 12062306a36Sopenharmony_ci * pkey 12162306a36Sopenharmony_ci */ 12262306a36Sopenharmony_ci for (i = 0; i < NUM_ITERATIONS; i++) { 12362306a36Sopenharmony_ci /* 12462306a36Sopenharmony_ci * Wait until the other thread has finished allocating the 12562306a36Sopenharmony_ci * restrictive pkey or until the next iteration has begun 12662306a36Sopenharmony_ci */ 12762306a36Sopenharmony_ci pthread_barrier_wait(&iteration_barrier); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci /* Try to associate the permissive pkey with the region */ 13062306a36Sopenharmony_ci FAIL_IF_EXIT(sys_pkey_mprotect(base, size, PROT_RWX, 13162306a36Sopenharmony_ci perm_pkey)); 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci /* Free the permissive pkey */ 13562306a36Sopenharmony_ci sys_pkey_free(perm_pkey); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci return NULL; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic void *protect_access(void *p) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci size_t size, numinsns; 14362306a36Sopenharmony_ci unsigned int *base; 14462306a36Sopenharmony_ci int tid, i; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci tid = gettid(); 14762306a36Sopenharmony_ci base = ((struct region *) p)->base; 14862306a36Sopenharmony_ci size = ((struct region *) p)->size; 14962306a36Sopenharmony_ci rights = ((struct region *) p)->rights; 15062306a36Sopenharmony_ci numinsns = size / sizeof(base[0]); 15162306a36Sopenharmony_ci FAIL_IF_EXIT(!base); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci /* Allocate the restrictive pkey */ 15462306a36Sopenharmony_ci rest_pkey = sys_pkey_alloc(0, rights); 15562306a36Sopenharmony_ci FAIL_IF_EXIT(rest_pkey < 0); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci printf("tid %d, pkey permissions are %s\n", tid, pkey_rights(rights)); 15862306a36Sopenharmony_ci printf("tid %d, %s randomly in range [%p, %p]\n", tid, 15962306a36Sopenharmony_ci (rights == PKEY_DISABLE_EXECUTE) ? "execute" : 16062306a36Sopenharmony_ci (rights == PKEY_DISABLE_WRITE) ? "write" : "read", 16162306a36Sopenharmony_ci base, base + numinsns); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci /* 16462306a36Sopenharmony_ci * Repeatedly try to protect the common region with a restrictive 16562306a36Sopenharmony_ci * pkey and read, write or execute from it 16662306a36Sopenharmony_ci */ 16762306a36Sopenharmony_ci for (i = 0; i < NUM_ITERATIONS; i++) { 16862306a36Sopenharmony_ci /* 16962306a36Sopenharmony_ci * Wait until the other thread has finished allocating the 17062306a36Sopenharmony_ci * permissive pkey or until the next iteration has begun 17162306a36Sopenharmony_ci */ 17262306a36Sopenharmony_ci pthread_barrier_wait(&iteration_barrier); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci /* Try to associate the restrictive pkey with the region */ 17562306a36Sopenharmony_ci FAIL_IF_EXIT(sys_pkey_mprotect(base, size, PROT_RWX, 17662306a36Sopenharmony_ci rest_pkey)); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci /* Choose a random instruction word address from the region */ 17962306a36Sopenharmony_ci fault_addr = base + (rand() % numinsns); 18062306a36Sopenharmony_ci fault_count = 0; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci switch (rights) { 18362306a36Sopenharmony_ci /* Read protection test */ 18462306a36Sopenharmony_ci case PKEY_DISABLE_ACCESS: 18562306a36Sopenharmony_ci /* 18662306a36Sopenharmony_ci * Read an instruction word from the region and 18762306a36Sopenharmony_ci * verify if it has not been overwritten to 18862306a36Sopenharmony_ci * something unexpected 18962306a36Sopenharmony_ci */ 19062306a36Sopenharmony_ci FAIL_IF_EXIT(*fault_addr != PPC_INST_NOP && 19162306a36Sopenharmony_ci *fault_addr != PPC_INST_BLR); 19262306a36Sopenharmony_ci break; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci /* Write protection test */ 19562306a36Sopenharmony_ci case PKEY_DISABLE_WRITE: 19662306a36Sopenharmony_ci /* 19762306a36Sopenharmony_ci * Write an instruction word to the region and 19862306a36Sopenharmony_ci * verify if the overwrite has succeeded 19962306a36Sopenharmony_ci */ 20062306a36Sopenharmony_ci *fault_addr = PPC_INST_BLR; 20162306a36Sopenharmony_ci FAIL_IF_EXIT(*fault_addr != PPC_INST_BLR); 20262306a36Sopenharmony_ci break; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci /* Execute protection test */ 20562306a36Sopenharmony_ci case PKEY_DISABLE_EXECUTE: 20662306a36Sopenharmony_ci /* Jump to the region and execute instructions */ 20762306a36Sopenharmony_ci asm volatile( 20862306a36Sopenharmony_ci "mtctr %0; bctrl" 20962306a36Sopenharmony_ci : : "r"(fault_addr) : "ctr", "lr"); 21062306a36Sopenharmony_ci break; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci /* 21462306a36Sopenharmony_ci * Restore the restrictions originally imposed by the 21562306a36Sopenharmony_ci * restrictive pkey as the signal handler would have 21662306a36Sopenharmony_ci * cleared out the corresponding AMR bits 21762306a36Sopenharmony_ci */ 21862306a36Sopenharmony_ci pkey_set_rights(rest_pkey, rights); 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci /* Free restrictive pkey */ 22262306a36Sopenharmony_ci sys_pkey_free(rest_pkey); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci return NULL; 22562306a36Sopenharmony_ci} 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_cistatic void reset_pkeys(unsigned long rights) 22862306a36Sopenharmony_ci{ 22962306a36Sopenharmony_ci int pkeys[NR_PKEYS], i; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci /* Exhaustively allocate all available pkeys */ 23262306a36Sopenharmony_ci for (i = 0; i < NR_PKEYS; i++) 23362306a36Sopenharmony_ci pkeys[i] = sys_pkey_alloc(0, rights); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci /* Free all allocated pkeys */ 23662306a36Sopenharmony_ci for (i = 0; i < NR_PKEYS; i++) 23762306a36Sopenharmony_ci sys_pkey_free(pkeys[i]); 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_cistatic int test(void) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci pthread_t prot_thread, pacc_thread; 24362306a36Sopenharmony_ci struct sigaction act; 24462306a36Sopenharmony_ci pthread_attr_t attr; 24562306a36Sopenharmony_ci size_t numinsns; 24662306a36Sopenharmony_ci struct region r; 24762306a36Sopenharmony_ci int ret, i; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci srand(time(NULL)); 25062306a36Sopenharmony_ci ret = pkeys_unsupported(); 25162306a36Sopenharmony_ci if (ret) 25262306a36Sopenharmony_ci return ret; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci /* Allocate the region */ 25562306a36Sopenharmony_ci r.size = getpagesize(); 25662306a36Sopenharmony_ci r.base = mmap(NULL, r.size, PROT_RWX, 25762306a36Sopenharmony_ci MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 25862306a36Sopenharmony_ci FAIL_IF(r.base == MAP_FAILED); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci /* 26162306a36Sopenharmony_ci * Fill the region with no-ops with a branch at the end 26262306a36Sopenharmony_ci * for returning to the caller 26362306a36Sopenharmony_ci */ 26462306a36Sopenharmony_ci numinsns = r.size / sizeof(r.base[0]); 26562306a36Sopenharmony_ci for (i = 0; i < numinsns - 1; i++) 26662306a36Sopenharmony_ci r.base[i] = PPC_INST_NOP; 26762306a36Sopenharmony_ci r.base[i] = PPC_INST_BLR; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci /* Setup SIGSEGV handler */ 27062306a36Sopenharmony_ci act.sa_handler = 0; 27162306a36Sopenharmony_ci act.sa_sigaction = segv_handler; 27262306a36Sopenharmony_ci FAIL_IF(sigprocmask(SIG_SETMASK, 0, &act.sa_mask) != 0); 27362306a36Sopenharmony_ci act.sa_flags = SA_SIGINFO; 27462306a36Sopenharmony_ci act.sa_restorer = 0; 27562306a36Sopenharmony_ci FAIL_IF(sigaction(SIGSEGV, &act, NULL) != 0); 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci /* 27862306a36Sopenharmony_ci * For these tests, the parent process should clear all bits of 27962306a36Sopenharmony_ci * AMR and IAMR, i.e. impose no restrictions, for all available 28062306a36Sopenharmony_ci * pkeys. This will be the base for the initial AMR and IAMR 28162306a36Sopenharmony_ci * values for all the test thread pairs. 28262306a36Sopenharmony_ci * 28362306a36Sopenharmony_ci * If the AMR and IAMR bits of all available pkeys are cleared 28462306a36Sopenharmony_ci * before running the tests and a fault is generated when 28562306a36Sopenharmony_ci * attempting to read, write or execute instructions from a 28662306a36Sopenharmony_ci * pkey protected region, the pkey responsible for this must be 28762306a36Sopenharmony_ci * the one from the protect-and-access thread since the other 28862306a36Sopenharmony_ci * one is fully permissive. Despite that, if the pkey reported 28962306a36Sopenharmony_ci * by siginfo is not the restrictive pkey, then there must be a 29062306a36Sopenharmony_ci * kernel bug. 29162306a36Sopenharmony_ci */ 29262306a36Sopenharmony_ci reset_pkeys(0); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci /* Setup barrier for protect and protect-and-access threads */ 29562306a36Sopenharmony_ci FAIL_IF(pthread_attr_init(&attr) != 0); 29662306a36Sopenharmony_ci FAIL_IF(pthread_barrier_init(&iteration_barrier, NULL, 2) != 0); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci /* Setup and start protect and protect-and-read threads */ 29962306a36Sopenharmony_ci puts("starting thread pair (protect, protect-and-read)"); 30062306a36Sopenharmony_ci r.rights = PKEY_DISABLE_ACCESS; 30162306a36Sopenharmony_ci FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0); 30262306a36Sopenharmony_ci FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0); 30362306a36Sopenharmony_ci FAIL_IF(pthread_join(prot_thread, NULL) != 0); 30462306a36Sopenharmony_ci FAIL_IF(pthread_join(pacc_thread, NULL) != 0); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci /* Setup and start protect and protect-and-write threads */ 30762306a36Sopenharmony_ci puts("starting thread pair (protect, protect-and-write)"); 30862306a36Sopenharmony_ci r.rights = PKEY_DISABLE_WRITE; 30962306a36Sopenharmony_ci FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0); 31062306a36Sopenharmony_ci FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0); 31162306a36Sopenharmony_ci FAIL_IF(pthread_join(prot_thread, NULL) != 0); 31262306a36Sopenharmony_ci FAIL_IF(pthread_join(pacc_thread, NULL) != 0); 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci /* Setup and start protect and protect-and-execute threads */ 31562306a36Sopenharmony_ci puts("starting thread pair (protect, protect-and-execute)"); 31662306a36Sopenharmony_ci r.rights = PKEY_DISABLE_EXECUTE; 31762306a36Sopenharmony_ci FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0); 31862306a36Sopenharmony_ci FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0); 31962306a36Sopenharmony_ci FAIL_IF(pthread_join(prot_thread, NULL) != 0); 32062306a36Sopenharmony_ci FAIL_IF(pthread_join(pacc_thread, NULL) != 0); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci /* Cleanup */ 32362306a36Sopenharmony_ci FAIL_IF(pthread_attr_destroy(&attr) != 0); 32462306a36Sopenharmony_ci FAIL_IF(pthread_barrier_destroy(&iteration_barrier) != 0); 32562306a36Sopenharmony_ci munmap(r.base, r.size); 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci return 0; 32862306a36Sopenharmony_ci} 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ciint main(void) 33162306a36Sopenharmony_ci{ 33262306a36Sopenharmony_ci return test_harness(test, "pkey_siginfo"); 33362306a36Sopenharmony_ci} 334