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 18 #include "pkeys.h" 19 20 #define PPC_INST_NOP 0x60000000 21 #define PPC_INST_TRAP 0x7fe00008 22 #define PPC_INST_BLR 0x4e800020 23 24 static volatile sig_atomic_t fault_pkey, fault_code, fault_type; 25 static volatile sig_atomic_t remaining_faults; 26 static volatile unsigned int *fault_addr; 27 static unsigned long pgsize, numinsns; 28 static unsigned int *insns; 29 30 static void trap_handler(int signum, siginfo_t *sinfo, void *ctx) 31 { 32 /* Check if this fault originated from the expected address */ 33 if (sinfo->si_addr != (void *) fault_addr) 34 sigsafe_err("got a fault for an unexpected address\n"); 35 36 _exit(1); 37 } 38 39 static void segv_handler(int signum, siginfo_t *sinfo, void *ctx) 40 { 41 int signal_pkey; 42 43 signal_pkey = siginfo_pkey(sinfo); 44 fault_code = sinfo->si_code; 45 46 /* Check if this fault originated from the expected address */ 47 if (sinfo->si_addr != (void *) fault_addr) { 48 sigsafe_err("got a fault for an unexpected address\n"); 49 _exit(1); 50 } 51 52 /* Check if too many faults have occurred for a single test case */ 53 if (!remaining_faults) { 54 sigsafe_err("got too many faults for the same address\n"); 55 _exit(1); 56 } 57 58 59 /* Restore permissions in order to continue */ 60 switch (fault_code) { 61 case SEGV_ACCERR: 62 if (mprotect(insns, pgsize, PROT_READ | PROT_WRITE)) { 63 sigsafe_err("failed to set access permissions\n"); 64 _exit(1); 65 } 66 break; 67 case SEGV_PKUERR: 68 if (signal_pkey != fault_pkey) { 69 sigsafe_err("got a fault for an unexpected pkey\n"); 70 _exit(1); 71 } 72 73 switch (fault_type) { 74 case PKEY_DISABLE_ACCESS: 75 pkey_set_rights(fault_pkey, 0); 76 break; 77 case PKEY_DISABLE_EXECUTE: 78 /* 79 * Reassociate the exec-only pkey with the region 80 * to be able to continue. Unlike AMR, we cannot 81 * set IAMR directly from userspace to restore the 82 * permissions. 83 */ 84 if (mprotect(insns, pgsize, PROT_EXEC)) { 85 sigsafe_err("failed to set execute permissions\n"); 86 _exit(1); 87 } 88 break; 89 default: 90 sigsafe_err("got a fault with an unexpected type\n"); 91 _exit(1); 92 } 93 break; 94 default: 95 sigsafe_err("got a fault with an unexpected code\n"); 96 _exit(1); 97 } 98 99 remaining_faults--; 100 } 101 102 static int test(void) 103 { 104 struct sigaction segv_act, trap_act; 105 unsigned long rights; 106 int pkey, ret, i; 107 108 ret = pkeys_unsupported(); 109 if (ret) 110 return ret; 111 112 /* Setup SIGSEGV handler */ 113 segv_act.sa_handler = 0; 114 segv_act.sa_sigaction = segv_handler; 115 FAIL_IF(sigprocmask(SIG_SETMASK, 0, &segv_act.sa_mask) != 0); 116 segv_act.sa_flags = SA_SIGINFO; 117 segv_act.sa_restorer = 0; 118 FAIL_IF(sigaction(SIGSEGV, &segv_act, NULL) != 0); 119 120 /* Setup SIGTRAP handler */ 121 trap_act.sa_handler = 0; 122 trap_act.sa_sigaction = trap_handler; 123 FAIL_IF(sigprocmask(SIG_SETMASK, 0, &trap_act.sa_mask) != 0); 124 trap_act.sa_flags = SA_SIGINFO; 125 trap_act.sa_restorer = 0; 126 FAIL_IF(sigaction(SIGTRAP, &trap_act, NULL) != 0); 127 128 /* Setup executable region */ 129 pgsize = getpagesize(); 130 numinsns = pgsize / sizeof(unsigned int); 131 insns = (unsigned int *) mmap(NULL, pgsize, PROT_READ | PROT_WRITE, 132 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 133 FAIL_IF(insns == MAP_FAILED); 134 135 /* Write the instruction words */ 136 for (i = 1; i < numinsns - 1; i++) 137 insns[i] = PPC_INST_NOP; 138 139 /* 140 * Set the first instruction as an unconditional trap. If 141 * the last write to this address succeeds, this should 142 * get overwritten by a no-op. 143 */ 144 insns[0] = PPC_INST_TRAP; 145 146 /* 147 * Later, to jump to the executable region, we use a branch 148 * and link instruction (bctrl) which sets the return address 149 * automatically in LR. Use that to return back. 150 */ 151 insns[numinsns - 1] = PPC_INST_BLR; 152 153 /* Allocate a pkey that restricts execution */ 154 rights = PKEY_DISABLE_EXECUTE; 155 pkey = sys_pkey_alloc(0, rights); 156 FAIL_IF(pkey < 0); 157 158 /* 159 * Pick the first instruction's address from the executable 160 * region. 161 */ 162 fault_addr = insns; 163 164 /* The following two cases will avoid SEGV_PKUERR */ 165 fault_type = -1; 166 fault_pkey = -1; 167 168 /* 169 * Read an instruction word from the address when AMR bits 170 * are not set i.e. the pkey permits both read and write 171 * access. 172 * 173 * This should not generate a fault as having PROT_EXEC 174 * implies PROT_READ on GNU systems. The pkey currently 175 * restricts execution only based on the IAMR bits. The 176 * AMR bits are cleared. 177 */ 178 remaining_faults = 0; 179 FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0); 180 printf("read from %p, pkey permissions are %s\n", fault_addr, 181 pkey_rights(rights)); 182 i = *fault_addr; 183 FAIL_IF(remaining_faults != 0); 184 185 /* 186 * Write an instruction word to the address when AMR bits 187 * are not set i.e. the pkey permits both read and write 188 * access. 189 * 190 * This should generate an access fault as having just 191 * PROT_EXEC also restricts writes. The pkey currently 192 * restricts execution only based on the IAMR bits. The 193 * AMR bits are cleared. 194 */ 195 remaining_faults = 1; 196 FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0); 197 printf("write to %p, pkey permissions are %s\n", fault_addr, 198 pkey_rights(rights)); 199 *fault_addr = PPC_INST_TRAP; 200 FAIL_IF(remaining_faults != 0 || fault_code != SEGV_ACCERR); 201 202 /* The following three cases will generate SEGV_PKUERR */ 203 rights |= PKEY_DISABLE_ACCESS; 204 fault_type = PKEY_DISABLE_ACCESS; 205 fault_pkey = pkey; 206 207 /* 208 * Read an instruction word from the address when AMR bits 209 * are set i.e. the pkey permits neither read nor write 210 * access. 211 * 212 * This should generate a pkey fault based on AMR bits only 213 * as having PROT_EXEC implicitly allows reads. 214 */ 215 remaining_faults = 1; 216 FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0); 217 pkey_set_rights(pkey, rights); 218 printf("read from %p, pkey permissions are %s\n", fault_addr, 219 pkey_rights(rights)); 220 i = *fault_addr; 221 FAIL_IF(remaining_faults != 0 || fault_code != SEGV_PKUERR); 222 223 /* 224 * Write an instruction word to the address when AMR bits 225 * are set i.e. the pkey permits neither read nor write 226 * access. 227 * 228 * This should generate two faults. First, a pkey fault 229 * based on AMR bits and then an access fault since 230 * PROT_EXEC does not allow writes. 231 */ 232 remaining_faults = 2; 233 FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0); 234 pkey_set_rights(pkey, rights); 235 printf("write to %p, pkey permissions are %s\n", fault_addr, 236 pkey_rights(rights)); 237 *fault_addr = PPC_INST_NOP; 238 FAIL_IF(remaining_faults != 0 || fault_code != SEGV_ACCERR); 239 240 /* Free the current pkey */ 241 sys_pkey_free(pkey); 242 243 rights = 0; 244 do { 245 /* 246 * Allocate pkeys with all valid combinations of read, 247 * write and execute restrictions. 248 */ 249 pkey = sys_pkey_alloc(0, rights); 250 FAIL_IF(pkey < 0); 251 252 /* 253 * Jump to the executable region. AMR bits may or may not 254 * be set but they should not affect execution. 255 * 256 * This should generate pkey faults based on IAMR bits which 257 * may be set to restrict execution. 258 * 259 * The first iteration also checks if the overwrite of the 260 * first instruction word from a trap to a no-op succeeded. 261 */ 262 fault_pkey = pkey; 263 fault_type = -1; 264 remaining_faults = 0; 265 if (rights & PKEY_DISABLE_EXECUTE) { 266 fault_type = PKEY_DISABLE_EXECUTE; 267 remaining_faults = 1; 268 } 269 270 FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0); 271 printf("execute at %p, pkey permissions are %s\n", fault_addr, 272 pkey_rights(rights)); 273 asm volatile("mtctr %0; bctrl" : : "r"(insns)); 274 FAIL_IF(remaining_faults != 0); 275 if (rights & PKEY_DISABLE_EXECUTE) 276 FAIL_IF(fault_code != SEGV_PKUERR); 277 278 /* Free the current pkey */ 279 sys_pkey_free(pkey); 280 281 /* Find next valid combination of pkey rights */ 282 rights = next_pkey_rights(rights); 283 } while (rights); 284 285 /* Cleanup */ 286 munmap((void *) insns, pgsize); 287 288 return 0; 289 } 290 291 int main(void) 292 { 293 return test_harness(test, "pkey_exec_prot"); 294 } 295
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.