Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 
0003 /*
0004  * Copyright 2020, Sandipan Das, IBM Corp.
0005  *
0006  * Test if applying execute protection on pages using memory
0007  * protection keys works as expected.
0008  */
0009 
0010 #define _GNU_SOURCE
0011 #include <stdio.h>
0012 #include <stdlib.h>
0013 #include <string.h>
0014 #include <signal.h>
0015 
0016 #include <unistd.h>
0017 
0018 #include "pkeys.h"
0019 
0020 #define PPC_INST_NOP    0x60000000
0021 #define PPC_INST_TRAP   0x7fe00008
0022 #define PPC_INST_BLR    0x4e800020
0023 
0024 static volatile sig_atomic_t fault_pkey, fault_code, fault_type;
0025 static volatile sig_atomic_t remaining_faults;
0026 static volatile unsigned int *fault_addr;
0027 static unsigned long pgsize, numinsns;
0028 static unsigned int *insns;
0029 
0030 static void trap_handler(int signum, siginfo_t *sinfo, void *ctx)
0031 {
0032     /* Check if this fault originated from the expected address */
0033     if (sinfo->si_addr != (void *) fault_addr)
0034         sigsafe_err("got a fault for an unexpected address\n");
0035 
0036     _exit(1);
0037 }
0038 
0039 static void segv_handler(int signum, siginfo_t *sinfo, void *ctx)
0040 {
0041     int signal_pkey;
0042 
0043     signal_pkey = siginfo_pkey(sinfo);
0044     fault_code = sinfo->si_code;
0045 
0046     /* Check if this fault originated from the expected address */
0047     if (sinfo->si_addr != (void *) fault_addr) {
0048         sigsafe_err("got a fault for an unexpected address\n");
0049         _exit(1);
0050     }
0051 
0052     /* Check if too many faults have occurred for a single test case */
0053     if (!remaining_faults) {
0054         sigsafe_err("got too many faults for the same address\n");
0055         _exit(1);
0056     }
0057 
0058 
0059     /* Restore permissions in order to continue */
0060     switch (fault_code) {
0061     case SEGV_ACCERR:
0062         if (mprotect(insns, pgsize, PROT_READ | PROT_WRITE)) {
0063             sigsafe_err("failed to set access permissions\n");
0064             _exit(1);
0065         }
0066         break;
0067     case SEGV_PKUERR:
0068         if (signal_pkey != fault_pkey) {
0069             sigsafe_err("got a fault for an unexpected pkey\n");
0070             _exit(1);
0071         }
0072 
0073         switch (fault_type) {
0074         case PKEY_DISABLE_ACCESS:
0075             pkey_set_rights(fault_pkey, 0);
0076             break;
0077         case PKEY_DISABLE_EXECUTE:
0078             /*
0079              * Reassociate the exec-only pkey with the region
0080              * to be able to continue. Unlike AMR, we cannot
0081              * set IAMR directly from userspace to restore the
0082              * permissions.
0083              */
0084             if (mprotect(insns, pgsize, PROT_EXEC)) {
0085                 sigsafe_err("failed to set execute permissions\n");
0086                 _exit(1);
0087             }
0088             break;
0089         default:
0090             sigsafe_err("got a fault with an unexpected type\n");
0091             _exit(1);
0092         }
0093         break;
0094     default:
0095         sigsafe_err("got a fault with an unexpected code\n");
0096         _exit(1);
0097     }
0098 
0099     remaining_faults--;
0100 }
0101 
0102 static int test(void)
0103 {
0104     struct sigaction segv_act, trap_act;
0105     unsigned long rights;
0106     int pkey, ret, i;
0107 
0108     ret = pkeys_unsupported();
0109     if (ret)
0110         return ret;
0111 
0112     /* Setup SIGSEGV handler */
0113     segv_act.sa_handler = 0;
0114     segv_act.sa_sigaction = segv_handler;
0115     FAIL_IF(sigprocmask(SIG_SETMASK, 0, &segv_act.sa_mask) != 0);
0116     segv_act.sa_flags = SA_SIGINFO;
0117     segv_act.sa_restorer = 0;
0118     FAIL_IF(sigaction(SIGSEGV, &segv_act, NULL) != 0);
0119 
0120     /* Setup SIGTRAP handler */
0121     trap_act.sa_handler = 0;
0122     trap_act.sa_sigaction = trap_handler;
0123     FAIL_IF(sigprocmask(SIG_SETMASK, 0, &trap_act.sa_mask) != 0);
0124     trap_act.sa_flags = SA_SIGINFO;
0125     trap_act.sa_restorer = 0;
0126     FAIL_IF(sigaction(SIGTRAP, &trap_act, NULL) != 0);
0127 
0128     /* Setup executable region */
0129     pgsize = getpagesize();
0130     numinsns = pgsize / sizeof(unsigned int);
0131     insns = (unsigned int *) mmap(NULL, pgsize, PROT_READ | PROT_WRITE,
0132                       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
0133     FAIL_IF(insns == MAP_FAILED);
0134 
0135     /* Write the instruction words */
0136     for (i = 1; i < numinsns - 1; i++)
0137         insns[i] = PPC_INST_NOP;
0138 
0139     /*
0140      * Set the first instruction as an unconditional trap. If
0141      * the last write to this address succeeds, this should
0142      * get overwritten by a no-op.
0143      */
0144     insns[0] = PPC_INST_TRAP;
0145 
0146     /*
0147      * Later, to jump to the executable region, we use a branch
0148      * and link instruction (bctrl) which sets the return address
0149      * automatically in LR. Use that to return back.
0150      */
0151     insns[numinsns - 1] = PPC_INST_BLR;
0152 
0153     /* Allocate a pkey that restricts execution */
0154     rights = PKEY_DISABLE_EXECUTE;
0155     pkey = sys_pkey_alloc(0, rights);
0156     FAIL_IF(pkey < 0);
0157 
0158     /*
0159      * Pick the first instruction's address from the executable
0160      * region.
0161      */
0162     fault_addr = insns;
0163 
0164     /* The following two cases will avoid SEGV_PKUERR */
0165     fault_type = -1;
0166     fault_pkey = -1;
0167 
0168     /*
0169      * Read an instruction word from the address when AMR bits
0170      * are not set i.e. the pkey permits both read and write
0171      * access.
0172      *
0173      * This should not generate a fault as having PROT_EXEC
0174      * implies PROT_READ on GNU systems. The pkey currently
0175      * restricts execution only based on the IAMR bits. The
0176      * AMR bits are cleared.
0177      */
0178     remaining_faults = 0;
0179     FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
0180     printf("read from %p, pkey permissions are %s\n", fault_addr,
0181            pkey_rights(rights));
0182     i = *fault_addr;
0183     FAIL_IF(remaining_faults != 0);
0184 
0185     /*
0186      * Write an instruction word to the address when AMR bits
0187      * are not set i.e. the pkey permits both read and write
0188      * access.
0189      *
0190      * This should generate an access fault as having just
0191      * PROT_EXEC also restricts writes. The pkey currently
0192      * restricts execution only based on the IAMR bits. The
0193      * AMR bits are cleared.
0194      */
0195     remaining_faults = 1;
0196     FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
0197     printf("write to %p, pkey permissions are %s\n", fault_addr,
0198            pkey_rights(rights));
0199     *fault_addr = PPC_INST_TRAP;
0200     FAIL_IF(remaining_faults != 0 || fault_code != SEGV_ACCERR);
0201 
0202     /* The following three cases will generate SEGV_PKUERR */
0203     rights |= PKEY_DISABLE_ACCESS;
0204     fault_type = PKEY_DISABLE_ACCESS;
0205     fault_pkey = pkey;
0206 
0207     /*
0208      * Read an instruction word from the address when AMR bits
0209      * are set i.e. the pkey permits neither read nor write
0210      * access.
0211      *
0212      * This should generate a pkey fault based on AMR bits only
0213      * as having PROT_EXEC implicitly allows reads.
0214      */
0215     remaining_faults = 1;
0216     FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
0217     pkey_set_rights(pkey, rights);
0218     printf("read from %p, pkey permissions are %s\n", fault_addr,
0219            pkey_rights(rights));
0220     i = *fault_addr;
0221     FAIL_IF(remaining_faults != 0 || fault_code != SEGV_PKUERR);
0222 
0223     /*
0224      * Write an instruction word to the address when AMR bits
0225      * are set i.e. the pkey permits neither read nor write
0226      * access.
0227      *
0228      * This should generate two faults. First, a pkey fault
0229      * based on AMR bits and then an access fault since
0230      * PROT_EXEC does not allow writes.
0231      */
0232     remaining_faults = 2;
0233     FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
0234     pkey_set_rights(pkey, rights);
0235     printf("write to %p, pkey permissions are %s\n", fault_addr,
0236            pkey_rights(rights));
0237     *fault_addr = PPC_INST_NOP;
0238     FAIL_IF(remaining_faults != 0 || fault_code != SEGV_ACCERR);
0239 
0240     /* Free the current pkey */
0241     sys_pkey_free(pkey);
0242 
0243     rights = 0;
0244     do {
0245         /*
0246          * Allocate pkeys with all valid combinations of read,
0247          * write and execute restrictions.
0248          */
0249         pkey = sys_pkey_alloc(0, rights);
0250         FAIL_IF(pkey < 0);
0251 
0252         /*
0253          * Jump to the executable region. AMR bits may or may not
0254          * be set but they should not affect execution.
0255          *
0256          * This should generate pkey faults based on IAMR bits which
0257          * may be set to restrict execution.
0258          *
0259          * The first iteration also checks if the overwrite of the
0260          * first instruction word from a trap to a no-op succeeded.
0261          */
0262         fault_pkey = pkey;
0263         fault_type = -1;
0264         remaining_faults = 0;
0265         if (rights & PKEY_DISABLE_EXECUTE) {
0266             fault_type = PKEY_DISABLE_EXECUTE;
0267             remaining_faults = 1;
0268         }
0269 
0270         FAIL_IF(sys_pkey_mprotect(insns, pgsize, PROT_EXEC, pkey) != 0);
0271         printf("execute at %p, pkey permissions are %s\n", fault_addr,
0272                pkey_rights(rights));
0273         asm volatile("mtctr %0; bctrl" : : "r"(insns));
0274         FAIL_IF(remaining_faults != 0);
0275         if (rights & PKEY_DISABLE_EXECUTE)
0276             FAIL_IF(fault_code != SEGV_PKUERR);
0277 
0278         /* Free the current pkey */
0279         sys_pkey_free(pkey);
0280 
0281         /* Find next valid combination of pkey rights */
0282         rights = next_pkey_rights(rights);
0283     } while (rights);
0284 
0285     /* Cleanup */
0286     munmap((void *) insns, pgsize);
0287 
0288     return 0;
0289 }
0290 
0291 int main(void)
0292 {
0293     return test_harness(test, "pkey_exec_prot");
0294 }