1 // SPDX-License-Identifier: GPL-2.0+ 2 3 /* 4 * Copyright 2020, Sandipan Das, IBM Corp. 5 * 6 * Test if applying execute protection on pages using memory 7 * protection keys works as expected. 8 */ 9 10 #define _GNU_SOURCE 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <signal.h> 15 16 #include <unistd.h> 17 #include <sys/mman.h> 18 19 #include "reg.h" 20 #include "utils.h" 21 22 /* 23 * Older versions of libc use the Intel-specific access rights. 24 * Hence, override the definitions as they might be incorrect. 25 */ 26 #undef PKEY_DISABLE_ACCESS 27 #define PKEY_DISABLE_ACCESS 0x3 28 29 #undef PKEY_DISABLE_WRITE 30 #define PKEY_DISABLE_WRITE 0x2 31 32 #undef PKEY_DISABLE_EXECUTE 33 #define PKEY_DISABLE_EXECUTE 0x4 34 35 /* Older versions of libc do not not define this */ 36 #ifndef SEGV_PKUERR 37 #define SEGV_PKUERR 4 38 #endif 39 40 #define SI_PKEY_OFFSET 0x20 41 42 #define SYS_pkey_mprotect 386 43 #define SYS_pkey_alloc 384 44 #define SYS_pkey_free 385 45 46 #define PKEY_BITS_PER_PKEY 2 47 #define NR_PKEYS 32 48 #define PKEY_BITS_MASK ((1UL << PKEY_BITS_PER_PKEY) - 1) 49 50 #define PPC_INST_NOP 0x60000000 51 #define PPC_INST_TRAP 0x7fe00008 52 #define PPC_INST_BLR 0x4e800020 53 54 #define sigsafe_err(msg) ({ \ 55 ssize_t nbytes __attribute__((unused)); \ 56 nbytes = write(STDERR_FILENO, msg, strlen(msg)); }) 57 58 static inline unsigned long pkeyreg_get(void) 59 { 60 return mfspr(SPRN_AMR); 61 } 62 63 static inline void pkeyreg_set(unsigned long amr) 64 { 65 set_amr(amr); 66 } 67 68 static void pkey_set_rights(int pkey, unsigned long rights) 69 { 70 unsigned long amr, shift; 71 72 shift = (NR_PKEYS - pkey - 1) * PKEY_BITS_PER_PKEY; 73 amr = pkeyreg_get(); 74 amr &= ~(PKEY_BITS_MASK << shift); 75 amr |= (rights & PKEY_BITS_MASK) << shift; 76 pkeyreg_set(amr); 77 } 78 79 static int sys_pkey_mprotect(void *addr, size_t len, int prot, int pkey) 80 { 81 return syscall(SYS_pkey_mprotect, addr, len, prot, pkey); 82 } 83 84 static int sys_pkey_alloc(unsigned long flags, unsigned long rights) 85 { 86 return syscall(SYS_pkey_alloc, flags, rights); 87 } 88 89 static int sys_pkey_free(int pkey) 90 { 91 return syscall(SYS_pkey_free, pkey); 92 } 93 94 static volatile sig_atomic_t fault_pkey, fault_code, fault_type; 95 static volatile sig_atomic_t remaining_faults; 96 static volatile unsigned int *fault_addr; 97 static unsigned long pgsize, numinsns; 98 static unsigned int *insns; 99 100 static void trap_handler(int signum, siginfo_t *sinfo, void *ctx) 101 { 102 /* Check if this fault originated from the expected address */ 103 if (sinfo->si_addr != (void *) fault_addr) 104 sigsafe_err("got a fault for an unexpected address\n"); 105 106 _exit(1); 107 } 108 109 static void segv_handler(int signum, siginfo_t *sinfo, void *ctx) 110 { 111 int signal_pkey; 112 113 /* 114 * In older versions of libc, siginfo_t does not have si_pkey as 115 * a member. 116 */ 117 #ifdef si_pkey 118 signal_pkey = sinfo->si_pkey; 119 #else 120 signal_pkey = *((int *)(((char *) sinfo) + SI_PKEY_OFFSET)); 121 #endif 122 123 fault_code = sinfo->si_code; 124 125 /* Check if this fault originated from the expected address */ 126 if (sinfo->si_addr != (void *) fault_addr) { 127 sigsafe_err("got a fault for an unexpected address\n"); 128 _exit(1); 129 } 130 131 /* Check if too many faults have occurred for a single test case */ 132 if (!remaining_faults) { 133 sigsafe_err("got too many faults for the same address\n"); 134 _exit(1); 135 } 136 137 138 /* Restore permissions in order to continue */ 139 switch (fault_code) { 140 case SEGV_ACCERR: 141 if (mprotect(insns, pgsize, PROT_READ | PROT_WRITE)) { 142 sigsafe_err("failed to set access permissions\n"); 143 _exit(1); 144 } 145 break; 146 case SEGV_PKUERR: 147 if (signal_pkey != fault_pkey) { 148 sigsafe_err("got a fault for an unexpected pkey\n"); 149 _exit(1); 150 } 151 152 switch (fault_type) { 153 case PKEY_DISABLE_ACCESS: 154 pkey_set_rights(fault_pkey, 0); 155 break; 156 case PKEY_DISABLE_EXECUTE: 157 /* 158 * Reassociate the exec-only pkey with the region 159 * to be able to continue. Unlike AMR, we cannot 160 * set IAMR directly from userspace to restore the 161 * permissions. 162 */ 163 if (mprotect(insns, pgsize, PROT_EXEC)) { 164 sigsafe_err("failed to set execute permissions\n"); 165 _exit(1); 166 } 167 break; 168 default: 169 sigsafe_err("got a fault with an unexpected type\n"); 170 _exit(1); 171 } 172 break; 173 default: 174 sigsafe_err("got a fault with an unexpected code\n"); 175 _exit(1); 176 } 177 178 remaining_faults--; 179 } 180 181 static int pkeys_unsupported(void) 182 { 183 bool hash_mmu = false; 184 int pkey; 185 186 /* Protection keys are currently supported on Hash MMU only */ 187 FAIL_IF(using_hash_mmu(&hash_mmu)); 188 SKIP_IF(!hash_mmu); 189 190 /* Check if the system call is supported */ 191 pkey = sys_pkey_alloc(0, 0); 192 SKIP_IF(pkey < 0); 193 sys_pkey_free(pkey); 194 195 return 0; 196 } 197 198 static int test(void) 199 { 200 struct sigaction segv_act, trap_act; 201 int pkey, ret, i; 202 203 ret = pkeys_unsupported(); 204 if (ret) 205 return ret; 206 207 /* Setup SIGSEGV handler */ 208 segv_act.sa_handler = 0; 209 segv_act.sa_sigaction = segv_handler; 210 FAIL_IF(sigprocmask(SIG_SETMASK, 0, &segv_act.sa_mask) != 0); 211 segv_act.sa_flags = SA_SIGINFO; 212 segv_act.sa_restorer = 0; 213 FAIL_IF(sigaction(SIGSEGV, &segv_act, NULL) != 0); 214 215 /* Setup SIGTRAP handler */ 216 trap_act.sa_handler = 0; 217 trap_act.sa_sigaction = trap_handler; 218 FAIL_IF(sigprocmask(SIG_SETMASK, 0, &trap_act.sa_mask) != 0); 219 trap_act.sa_flags = SA_SIGINFO; 220 trap_act.sa_restorer = 0; 221 FAIL_IF(sigaction(SIGTRAP, &trap_act, NULL) != 0); 222 223 /* Setup executable region */ 224 pgsize = getpagesize(); 225 numinsns = pgsize / sizeof(unsigned int); 226 insns = (unsigned int *) mmap(NULL, pgsize, PROT_READ | PROT_WRITE, 227 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 228 FAIL_IF(insns == MAP_FAILED); 229 230 /* Write the instruction words */ 231 for (i = 1; i < numinsns - 1; i++) 232 insns[i] = PPC_INST_NOP; 233 234 /* 235 * Set the first instruction as an unconditional trap. If 236 * the last write to this address succeeds, this should 237 * get overwritten by a no-op. 238 */ 239 insns[0] = PPC_INST_TRAP; 240 241 /* 242 * Later, to jump to the executable region, we use a branch 243 * and link instruction (bctrl) which sets the return address 244 * automatically in LR. Use that to return back. 245 */ 246 insns[numinsns - 1] = PPC_INST_BLR; 247 248 /* Allocate a pkey that restricts execution */ 249 pkey = sys_pkey_alloc(0, PKEY_DISABLE_EXECUTE); 250 FAIL_IF(pkey < 0); 251 252 /* 253 * Pick the first instruction's address from the executable 254 * region. 255 */ 256 fault_addr = insns; 257 258 /* The following two cases will avoid SEGV_PKUERR */ 259 fault_type = -1; 260 fault_pkey = -1; 261 262 /* 263 * Read an instruction word from the address when AMR bits 264 * are not set i.e. the pkey permits both read and write 265 * access. 266 * 267 * This should not generate a fault as having PROT_EXEC 268 * implies PROT_READ on GNU systems. The pkey currently 269 * restricts execution only based on the IAMR bits. The 270 * AMR bits are cleared. 271 */ 272 remaining_faults = 0; 273 FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0); 274 printf("read from %p, pkey is execute-disabled, access-enabled\n", 275 (void *) fault_addr); 276 i = *fault_addr; 277 FAIL_IF(remaining_faults != 0); 278 279 /* 280 * Write an instruction word to the address when AMR bits 281 * are not set i.e. the pkey permits both read and write 282 * access. 283 * 284 * This should generate an access fault as having just 285 * PROT_EXEC also restricts writes. The pkey currently 286 * restricts execution only based on the IAMR bits. The 287 * AMR bits are cleared. 288 */ 289 remaining_faults = 1; 290 FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0); 291 printf("write to %p, pkey is execute-disabled, access-enabled\n", 292 (void *) fault_addr); 293 *fault_addr = PPC_INST_TRAP; 294 FAIL_IF(remaining_faults != 0 || fault_code != SEGV_ACCERR); 295 296 /* The following three cases will generate SEGV_PKUERR */ 297 fault_type = PKEY_DISABLE_ACCESS; 298 fault_pkey = pkey; 299 300 /* 301 * Read an instruction word from the address when AMR bits 302 * are set i.e. the pkey permits neither read nor write 303 * access. 304 * 305 * This should generate a pkey fault based on AMR bits only 306 * as having PROT_EXEC implicitly allows reads. 307 */ 308 remaining_faults = 1; 309 FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0); 310 printf("read from %p, pkey is execute-disabled, access-disabled\n", 311 (void *) fault_addr); 312 pkey_set_rights(pkey, PKEY_DISABLE_ACCESS); 313 i = *fault_addr; 314 FAIL_IF(remaining_faults != 0 || fault_code != SEGV_PKUERR); 315 316 /* 317 * Write an instruction word to the address when AMR bits 318 * are set i.e. the pkey permits neither read nor write 319 * access. 320 * 321 * This should generate two faults. First, a pkey fault 322 * based on AMR bits and then an access fault since 323 * PROT_EXEC does not allow writes. 324 */ 325 remaining_faults = 2; 326 FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0); 327 printf("write to %p, pkey is execute-disabled, access-disabled\n", 328 (void *) fault_addr); 329 pkey_set_rights(pkey, PKEY_DISABLE_ACCESS); 330 *fault_addr = PPC_INST_NOP; 331 FAIL_IF(remaining_faults != 0 || fault_code != SEGV_ACCERR); 332 333 /* 334 * Jump to the executable region when AMR bits are set i.e. 335 * the pkey permits neither read nor write access. 336 * 337 * This should generate a pkey fault based on IAMR bits which 338 * are set to not permit execution. AMR bits should not affect 339 * execution. 340 * 341 * This also checks if the overwrite of the first instruction 342 * word from a trap to a no-op succeeded. 343 */ 344 fault_addr = insns; 345 fault_type = PKEY_DISABLE_EXECUTE; 346 fault_pkey = pkey; 347 remaining_faults = 1; 348 FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0); 349 pkey_set_rights(pkey, PKEY_DISABLE_ACCESS); 350 printf("execute at %p, pkey is execute-disabled, access-disabled\n", 351 (void *) fault_addr); 352 asm volatile("mtctr %0; bctrl" : : "r"(insns)); 353 FAIL_IF(remaining_faults != 0 || fault_code != SEGV_PKUERR); 354 355 /* 356 * Free the current pkey and allocate a new one that is 357 * fully permissive. 358 */ 359 sys_pkey_free(pkey); 360 pkey = sys_pkey_alloc(0, 0); 361 362 /* 363 * Jump to the executable region when AMR bits are not set 364 * i.e. the pkey permits read and write access. 365 * 366 * This should not generate any faults as the IAMR bits are 367 * also not set and hence will the pkey will not restrict 368 * execution. 369 */ 370 fault_pkey = pkey; 371 remaining_faults = 0; 372 FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0); 373 printf("execute at %p, pkey is execute-enabled, access-enabled\n", 374 (void *) fault_addr); 375 asm volatile("mtctr %0; bctrl" : : "r"(insns)); 376 FAIL_IF(remaining_faults != 0); 377 378 /* Cleanup */ 379 munmap((void *) insns, pgsize); 380 sys_pkey_free(pkey); 381 382 return 0; 383 } 384 385 int main(void) 386 { 387 test_harness(test, "pkey_exec_prot"); 388 } 389