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

selftests/powerpc: Add a test for execute-only memory

This selftest is designed to cover execute-only protections
on the Radix MMU but will also work with Hash.

The tests are based on those found in pkey_exec_test with modifications
to use the generic mprotect() instead of the pkey variants.

Signed-off-by: Nicholas Miehlbradt <nicholas@linux.ibm.com>
Signed-off-by: Russell Currey <ruscur@russell.cc>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/20220817050640.406017-2-ruscur@russell.cc

authored by

Nicholas Miehlbradt and committed by
Michael Ellerman
98acee3f 395cac77

+233 -1
+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 pkey_exec_prot \ 6 + large_vm_fork_separation bad_accesses exec_prot pkey_exec_prot \ 7 7 pkey_siginfo stack_expansion_signal stack_expansion_ldst \ 8 8 large_vm_gpr_corruption 9 9 TEST_PROGS := stress_code_patching.sh ··· 22 22 $(OUTPUT)/large_vm_fork_separation: CFLAGS += -m64 23 23 $(OUTPUT)/large_vm_gpr_corruption: CFLAGS += -m64 24 24 $(OUTPUT)/bad_accesses: CFLAGS += -m64 25 + $(OUTPUT)/exec_prot: CFLAGS += -m64 25 26 $(OUTPUT)/pkey_exec_prot: CFLAGS += -m64 26 27 $(OUTPUT)/pkey_siginfo: CFLAGS += -m64 27 28
+231
tools/testing/selftests/powerpc/mm/exec_prot.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + /* 4 + * Copyright 2022, Nicholas Miehlbradt, IBM Corporation 5 + * based on pkey_exec_prot.c 6 + * 7 + * Test if applying execute protection on pages 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 "pkeys.h" 20 + 21 + 22 + #define PPC_INST_NOP 0x60000000 23 + #define PPC_INST_TRAP 0x7fe00008 24 + #define PPC_INST_BLR 0x4e800020 25 + 26 + static volatile sig_atomic_t fault_code; 27 + static volatile sig_atomic_t remaining_faults; 28 + static volatile unsigned int *fault_addr; 29 + static unsigned long pgsize, numinsns; 30 + static unsigned int *insns; 31 + static bool pkeys_supported; 32 + 33 + static bool is_fault_expected(int fault_code) 34 + { 35 + if (fault_code == SEGV_ACCERR) 36 + return true; 37 + 38 + /* Assume any pkey error is fine since pkey_exec_prot test covers them */ 39 + if (fault_code == SEGV_PKUERR && pkeys_supported) 40 + return true; 41 + 42 + return false; 43 + } 44 + 45 + static void trap_handler(int signum, siginfo_t *sinfo, void *ctx) 46 + { 47 + /* Check if this fault originated from the expected address */ 48 + if (sinfo->si_addr != (void *)fault_addr) 49 + sigsafe_err("got a fault for an unexpected address\n"); 50 + 51 + _exit(1); 52 + } 53 + 54 + static void segv_handler(int signum, siginfo_t *sinfo, void *ctx) 55 + { 56 + fault_code = sinfo->si_code; 57 + 58 + /* Check if this fault originated from the expected address */ 59 + if (sinfo->si_addr != (void *)fault_addr) { 60 + sigsafe_err("got a fault for an unexpected address\n"); 61 + _exit(1); 62 + } 63 + 64 + /* Check if too many faults have occurred for a single test case */ 65 + if (!remaining_faults) { 66 + sigsafe_err("got too many faults for the same address\n"); 67 + _exit(1); 68 + } 69 + 70 + 71 + /* Restore permissions in order to continue */ 72 + if (is_fault_expected(fault_code)) { 73 + if (mprotect(insns, pgsize, PROT_READ | PROT_WRITE | PROT_EXEC)) { 74 + sigsafe_err("failed to set access permissions\n"); 75 + _exit(1); 76 + } 77 + } else { 78 + sigsafe_err("got a fault with an unexpected code\n"); 79 + _exit(1); 80 + } 81 + 82 + remaining_faults--; 83 + } 84 + 85 + static int check_exec_fault(int rights) 86 + { 87 + /* 88 + * Jump to the executable region. 89 + * 90 + * The first iteration also checks if the overwrite of the 91 + * first instruction word from a trap to a no-op succeeded. 92 + */ 93 + fault_code = -1; 94 + remaining_faults = 0; 95 + if (!(rights & PROT_EXEC)) 96 + remaining_faults = 1; 97 + 98 + FAIL_IF(mprotect(insns, pgsize, rights) != 0); 99 + asm volatile("mtctr %0; bctrl" : : "r"(insns)); 100 + 101 + FAIL_IF(remaining_faults != 0); 102 + if (!(rights & PROT_EXEC)) 103 + FAIL_IF(!is_fault_expected(fault_code)); 104 + 105 + return 0; 106 + } 107 + 108 + static int test(void) 109 + { 110 + struct sigaction segv_act, trap_act; 111 + int i; 112 + 113 + /* Skip the test if the CPU doesn't support Radix */ 114 + SKIP_IF(!have_hwcap2(PPC_FEATURE2_ARCH_3_00)); 115 + 116 + /* Check if pkeys are supported */ 117 + pkeys_supported = pkeys_unsupported() == 0; 118 + 119 + /* Setup SIGSEGV handler */ 120 + segv_act.sa_handler = 0; 121 + segv_act.sa_sigaction = segv_handler; 122 + FAIL_IF(sigprocmask(SIG_SETMASK, 0, &segv_act.sa_mask) != 0); 123 + segv_act.sa_flags = SA_SIGINFO; 124 + segv_act.sa_restorer = 0; 125 + FAIL_IF(sigaction(SIGSEGV, &segv_act, NULL) != 0); 126 + 127 + /* Setup SIGTRAP handler */ 128 + trap_act.sa_handler = 0; 129 + trap_act.sa_sigaction = trap_handler; 130 + FAIL_IF(sigprocmask(SIG_SETMASK, 0, &trap_act.sa_mask) != 0); 131 + trap_act.sa_flags = SA_SIGINFO; 132 + trap_act.sa_restorer = 0; 133 + FAIL_IF(sigaction(SIGTRAP, &trap_act, NULL) != 0); 134 + 135 + /* Setup executable region */ 136 + pgsize = getpagesize(); 137 + numinsns = pgsize / sizeof(unsigned int); 138 + insns = (unsigned int *)mmap(NULL, pgsize, PROT_READ | PROT_WRITE, 139 + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 140 + FAIL_IF(insns == MAP_FAILED); 141 + 142 + /* Write the instruction words */ 143 + for (i = 1; i < numinsns - 1; i++) 144 + insns[i] = PPC_INST_NOP; 145 + 146 + /* 147 + * Set the first instruction as an unconditional trap. If 148 + * the last write to this address succeeds, this should 149 + * get overwritten by a no-op. 150 + */ 151 + insns[0] = PPC_INST_TRAP; 152 + 153 + /* 154 + * Later, to jump to the executable region, we use a branch 155 + * and link instruction (bctrl) which sets the return address 156 + * automatically in LR. Use that to return back. 157 + */ 158 + insns[numinsns - 1] = PPC_INST_BLR; 159 + 160 + /* 161 + * Pick the first instruction's address from the executable 162 + * region. 163 + */ 164 + fault_addr = insns; 165 + 166 + /* 167 + * Read an instruction word from the address when the page 168 + * is execute only. This should generate an access fault. 169 + */ 170 + fault_code = -1; 171 + remaining_faults = 1; 172 + printf("Testing read on --x, should fault..."); 173 + FAIL_IF(mprotect(insns, pgsize, PROT_EXEC) != 0); 174 + i = *fault_addr; 175 + FAIL_IF(remaining_faults != 0 || !is_fault_expected(fault_code)); 176 + printf("ok!\n"); 177 + 178 + /* 179 + * Write an instruction word to the address when the page 180 + * execute only. This should also generate an access fault. 181 + */ 182 + fault_code = -1; 183 + remaining_faults = 1; 184 + printf("Testing write on --x, should fault..."); 185 + FAIL_IF(mprotect(insns, pgsize, PROT_EXEC) != 0); 186 + *fault_addr = PPC_INST_NOP; 187 + FAIL_IF(remaining_faults != 0 || !is_fault_expected(fault_code)); 188 + printf("ok!\n"); 189 + 190 + printf("Testing exec on ---, should fault..."); 191 + FAIL_IF(check_exec_fault(PROT_NONE)); 192 + printf("ok!\n"); 193 + 194 + printf("Testing exec on r--, should fault..."); 195 + FAIL_IF(check_exec_fault(PROT_READ)); 196 + printf("ok!\n"); 197 + 198 + printf("Testing exec on -w-, should fault..."); 199 + FAIL_IF(check_exec_fault(PROT_WRITE)); 200 + printf("ok!\n"); 201 + 202 + printf("Testing exec on rw-, should fault..."); 203 + FAIL_IF(check_exec_fault(PROT_READ | PROT_WRITE)); 204 + printf("ok!\n"); 205 + 206 + printf("Testing exec on --x, should succeed..."); 207 + FAIL_IF(check_exec_fault(PROT_EXEC)); 208 + printf("ok!\n"); 209 + 210 + printf("Testing exec on r-x, should succeed..."); 211 + FAIL_IF(check_exec_fault(PROT_READ | PROT_EXEC)); 212 + printf("ok!\n"); 213 + 214 + printf("Testing exec on -wx, should succeed..."); 215 + FAIL_IF(check_exec_fault(PROT_WRITE | PROT_EXEC)); 216 + printf("ok!\n"); 217 + 218 + printf("Testing exec on rwx, should succeed..."); 219 + FAIL_IF(check_exec_fault(PROT_READ | PROT_WRITE | PROT_EXEC)); 220 + printf("ok!\n"); 221 + 222 + /* Cleanup */ 223 + FAIL_IF(munmap((void *)insns, pgsize)); 224 + 225 + return 0; 226 + } 227 + 228 + int main(void) 229 + { 230 + return test_harness(test, "exec_prot"); 231 + }