295 lines · 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 memory7 * protection keys works as expected.8 */9 10#define _GNU_SOURCE11#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 0x6000000021#define PPC_INST_TRAP 0x7fe0000822#define PPC_INST_BLR 0x4e80002023 24static volatile sig_atomic_t fault_pkey, fault_code, fault_type;25static volatile sig_atomic_t remaining_faults;26static volatile unsigned int *fault_addr;27static unsigned long pgsize, numinsns;28static unsigned int *insns;29 30static 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 39static 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 region80 * to be able to continue. Unlike AMR, we cannot81 * set IAMR directly from userspace to restore the82 * 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 102static 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. If141 * the last write to this address succeeds, this should142 * 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 branch148 * and link instruction (bctrl) which sets the return address149 * 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 executable160 * 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 bits170 * are not set i.e. the pkey permits both read and write171 * access.172 *173 * This should not generate a fault as having PROT_EXEC174 * implies PROT_READ on GNU systems. The pkey currently175 * restricts execution only based on the IAMR bits. The176 * 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 bits187 * are not set i.e. the pkey permits both read and write188 * access.189 *190 * This should generate an access fault as having just191 * PROT_EXEC also restricts writes. The pkey currently192 * restricts execution only based on the IAMR bits. The193 * 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 bits209 * are set i.e. the pkey permits neither read nor write210 * access.211 *212 * This should generate a pkey fault based on AMR bits only213 * 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 bits225 * are set i.e. the pkey permits neither read nor write226 * access.227 *228 * This should generate two faults. First, a pkey fault229 * based on AMR bits and then an access fault since230 * 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 not254 * be set but they should not affect execution.255 *256 * This should generate pkey faults based on IAMR bits which257 * may be set to restrict execution.258 *259 * The first iteration also checks if the overwrite of the260 * 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 291int main(void)292{293 return test_harness(test, "pkey_exec_prot");294}295