1 // SPDX-License-Identifier: GPL-2.0 2 3 /* 4 * Copyright 2020, Sandipan Das, IBM Corp. 5 * 6 * Test if the signal information reports the correct memory protection 7 * key upon getting a key access violation fault for a page that was 8 * attempted to be protected by two different keys from two competing 9 * threads at the same time. 10 */ 11 12 #define _GNU_SOURCE 13 #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 0x60000000 25 #define PPC_INST_BLR 0x4e800020 26 #define PROT_RWX (PROT_READ | PROT_WRITE | PROT_EXEC) 27 28 #define NUM_ITERATIONS 1000000 29 30 static volatile sig_atomic_t perm_pkey, rest_pkey; 31 static volatile sig_atomic_t rights, fault_count; 32 static volatile unsigned int *volatile fault_addr; 33 static pthread_barrier_t iteration_barrier; 34 35 static 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 execute 73 * rights cannot be changed directly for the faulting pkey as 74 * IAMR is inaccessible from userspace. 75 * 76 * Otherwise, if the current fault occurred due to lack of 77 * read-write rights, change the AMR permission bits for the 78 * 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 else 86 pkey_set_rights(pkey, 0); 87 88 fault_count++; 89 } 90 91 struct region { 92 unsigned long rights; 93 unsigned int *base; 94 size_t size; 95 }; 96 97 static 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 permissive 120 * pkey 121 */ 122 for (i = 0; i < NUM_ITERATIONS; i++) { 123 /* 124 * Wait until the other thread has finished allocating the 125 * restrictive pkey or until the next iteration has begun 126 */ 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 140 static 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 restrictive 165 * pkey and read, write or execute from it 166 */ 167 for (i = 0; i < NUM_ITERATIONS; i++) { 168 /* 169 * Wait until the other thread has finished allocating the 170 * permissive pkey or until the next iteration has begun 171 */ 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 and 187 * verify if it has not been overwritten to 188 * something unexpected 189 */ 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 and 198 * verify if the overwrite has succeeded 199 */ 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 the 215 * restrictive pkey as the signal handler would have 216 * cleared out the corresponding AMR bits 217 */ 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 227 static 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 240 static 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 end 262 * for returning to the caller 263 */ 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 of 279 * AMR and IAMR, i.e. impose no restrictions, for all available 280 * pkeys. This will be the base for the initial AMR and IAMR 281 * values for all the test thread pairs. 282 * 283 * If the AMR and IAMR bits of all available pkeys are cleared 284 * before running the tests and a fault is generated when 285 * attempting to read, write or execute instructions from a 286 * pkey protected region, the pkey responsible for this must be 287 * the one from the protect-and-access thread since the other 288 * one is fully permissive. Despite that, if the pkey reported 289 * by siginfo is not the restrictive pkey, then there must be a 290 * 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 330 int main(void) 331 { 332 return test_harness(test, "pkey_siginfo"); 333 } 334
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.