Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/powerpc: Add test for execute-disabled pkeys

Apart from read and write access, memory protection keys can
also be used for restricting execute permission of pages on
powerpc. This adds a test to verify if the feature works as
expected.

Signed-off-by: Sandipan Das <sandipan@linux.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/20200604125610.649668-4-sandipan@linux.ibm.com

authored by

Sandipan Das and committed by
Michael Ellerman
1addb644 c405b738

+391 -1
+1
tools/testing/selftests/powerpc/mm/.gitignore
··· 8 8 large_vm_fork_separation 9 9 bad_accesses 10 10 tlbie_test 11 + pkey_exec_prot
+2 -1
tools/testing/selftests/powerpc/mm/Makefile
··· 3 3 $(MAKE) -C ../ 4 4 5 5 TEST_GEN_PROGS := hugetlb_vs_thp_test subpage_prot prot_sao segv_errors wild_bctr \ 6 - large_vm_fork_separation bad_accesses 6 + large_vm_fork_separation bad_accesses pkey_exec_prot 7 7 TEST_GEN_PROGS_EXTENDED := tlbie_test 8 8 TEST_GEN_FILES := tempfile 9 9 ··· 17 17 $(OUTPUT)/wild_bctr: CFLAGS += -m64 18 18 $(OUTPUT)/large_vm_fork_separation: CFLAGS += -m64 19 19 $(OUTPUT)/bad_accesses: CFLAGS += -m64 20 + $(OUTPUT)/pkey_exec_prot: CFLAGS += -m64 20 21 21 22 $(OUTPUT)/tempfile: 22 23 dd if=/dev/zero of=$@ bs=64k count=1
+388
tools/testing/selftests/powerpc/mm/pkey_exec_prot.c
··· 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 + }