~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/tools/testing/selftests/powerpc/mm/pkey_exec_prot.c

Version: ~ [ linux-6.11.5 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.58 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.114 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.169 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.228 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.284 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.322 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.9 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  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 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php