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