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 | |