1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | /* |
4 | * Copyright 2020, Sandipan Das, IBM Corp. |
5 | * |
6 | * Test if the signal information reports the correct memory protection |
7 | * key upon getting a key access violation fault for a page that was |
8 | * attempted to be protected by two different keys from two competing |
9 | * threads at the same time. |
10 | */ |
11 | |
12 | #define _GNU_SOURCE |
13 | #include <stdio.h> |
14 | #include <stdlib.h> |
15 | #include <string.h> |
16 | #include <signal.h> |
17 | |
18 | #include <unistd.h> |
19 | #include <pthread.h> |
20 | #include <sys/mman.h> |
21 | |
22 | #include "pkeys.h" |
23 | |
24 | #define PPC_INST_NOP 0x60000000 |
25 | #define PPC_INST_BLR 0x4e800020 |
26 | #define PROT_RWX (PROT_READ | PROT_WRITE | PROT_EXEC) |
27 | |
28 | #define NUM_ITERATIONS 1000000 |
29 | |
30 | static volatile sig_atomic_t perm_pkey, rest_pkey; |
31 | static volatile sig_atomic_t rights, fault_count; |
32 | static volatile unsigned int *volatile fault_addr; |
33 | static pthread_barrier_t iteration_barrier; |
34 | |
35 | static void segv_handler(int signum, siginfo_t *sinfo, void *ctx) |
36 | { |
37 | void *pgstart; |
38 | size_t pgsize; |
39 | int pkey; |
40 | |
41 | pkey = siginfo_pkey(sinfo); |
42 | |
43 | /* Check if this fault originated from a pkey access violation */ |
44 | if (sinfo->si_code != SEGV_PKUERR) { |
45 | sigsafe_err("got a fault for an unexpected reason\n" ); |
46 | _exit(1); |
47 | } |
48 | |
49 | /* Check if this fault originated from the expected address */ |
50 | if (sinfo->si_addr != (void *) fault_addr) { |
51 | sigsafe_err("got a fault for an unexpected address\n" ); |
52 | _exit(1); |
53 | } |
54 | |
55 | /* Check if this fault originated from the restrictive pkey */ |
56 | if (pkey != rest_pkey) { |
57 | sigsafe_err("got a fault for an unexpected pkey\n" ); |
58 | _exit(1); |
59 | } |
60 | |
61 | /* Check if too many faults have occurred for the same iteration */ |
62 | if (fault_count > 0) { |
63 | sigsafe_err("got too many faults for the same address\n" ); |
64 | _exit(1); |
65 | } |
66 | |
67 | pgsize = getpagesize(); |
68 | pgstart = (void *) ((unsigned long) fault_addr & ~(pgsize - 1)); |
69 | |
70 | /* |
71 | * If the current fault occurred due to lack of execute rights, |
72 | * reassociate the page with the exec-only pkey since execute |
73 | * rights cannot be changed directly for the faulting pkey as |
74 | * IAMR is inaccessible from userspace. |
75 | * |
76 | * Otherwise, if the current fault occurred due to lack of |
77 | * read-write rights, change the AMR permission bits for the |
78 | * pkey. |
79 | * |
80 | * This will let the test continue. |
81 | */ |
82 | if (rights == PKEY_DISABLE_EXECUTE && |
83 | mprotect(pgstart, pgsize, PROT_EXEC)) |
84 | _exit(1); |
85 | else |
86 | pkey_set_rights(pkey, 0); |
87 | |
88 | fault_count++; |
89 | } |
90 | |
91 | struct region { |
92 | unsigned long rights; |
93 | unsigned int *base; |
94 | size_t size; |
95 | }; |
96 | |
97 | static void *protect(void *p) |
98 | { |
99 | unsigned long rights; |
100 | unsigned int *base; |
101 | size_t size; |
102 | int tid, i; |
103 | |
104 | tid = gettid(); |
105 | base = ((struct region *) p)->base; |
106 | size = ((struct region *) p)->size; |
107 | FAIL_IF_EXIT(!base); |
108 | |
109 | /* No read, write and execute restrictions */ |
110 | rights = 0; |
111 | |
112 | printf("tid %d, pkey permissions are %s\n" , tid, pkey_rights(rights)); |
113 | |
114 | /* Allocate the permissive pkey */ |
115 | perm_pkey = sys_pkey_alloc(0, rights); |
116 | FAIL_IF_EXIT(perm_pkey < 0); |
117 | |
118 | /* |
119 | * Repeatedly try to protect the common region with a permissive |
120 | * pkey |
121 | */ |
122 | for (i = 0; i < NUM_ITERATIONS; i++) { |
123 | /* |
124 | * Wait until the other thread has finished allocating the |
125 | * restrictive pkey or until the next iteration has begun |
126 | */ |
127 | pthread_barrier_wait(&iteration_barrier); |
128 | |
129 | /* Try to associate the permissive pkey with the region */ |
130 | FAIL_IF_EXIT(sys_pkey_mprotect(base, size, PROT_RWX, |
131 | perm_pkey)); |
132 | } |
133 | |
134 | /* Free the permissive pkey */ |
135 | sys_pkey_free(perm_pkey); |
136 | |
137 | return NULL; |
138 | } |
139 | |
140 | static void *protect_access(void *p) |
141 | { |
142 | size_t size, numinsns; |
143 | unsigned int *base; |
144 | int tid, i; |
145 | |
146 | tid = gettid(); |
147 | base = ((struct region *) p)->base; |
148 | size = ((struct region *) p)->size; |
149 | rights = ((struct region *) p)->rights; |
150 | numinsns = size / sizeof(base[0]); |
151 | FAIL_IF_EXIT(!base); |
152 | |
153 | /* Allocate the restrictive pkey */ |
154 | rest_pkey = sys_pkey_alloc(0, rights); |
155 | FAIL_IF_EXIT(rest_pkey < 0); |
156 | |
157 | printf("tid %d, pkey permissions are %s\n" , tid, pkey_rights(rights)); |
158 | printf("tid %d, %s randomly in range [%p, %p]\n" , tid, |
159 | (rights == PKEY_DISABLE_EXECUTE) ? "execute" : |
160 | (rights == PKEY_DISABLE_WRITE) ? "write" : "read" , |
161 | base, base + numinsns); |
162 | |
163 | /* |
164 | * Repeatedly try to protect the common region with a restrictive |
165 | * pkey and read, write or execute from it |
166 | */ |
167 | for (i = 0; i < NUM_ITERATIONS; i++) { |
168 | /* |
169 | * Wait until the other thread has finished allocating the |
170 | * permissive pkey or until the next iteration has begun |
171 | */ |
172 | pthread_barrier_wait(&iteration_barrier); |
173 | |
174 | /* Try to associate the restrictive pkey with the region */ |
175 | FAIL_IF_EXIT(sys_pkey_mprotect(base, size, PROT_RWX, |
176 | rest_pkey)); |
177 | |
178 | /* Choose a random instruction word address from the region */ |
179 | fault_addr = base + (rand() % numinsns); |
180 | fault_count = 0; |
181 | |
182 | switch (rights) { |
183 | /* Read protection test */ |
184 | case PKEY_DISABLE_ACCESS: |
185 | /* |
186 | * Read an instruction word from the region and |
187 | * verify if it has not been overwritten to |
188 | * something unexpected |
189 | */ |
190 | FAIL_IF_EXIT(*fault_addr != PPC_INST_NOP && |
191 | *fault_addr != PPC_INST_BLR); |
192 | break; |
193 | |
194 | /* Write protection test */ |
195 | case PKEY_DISABLE_WRITE: |
196 | /* |
197 | * Write an instruction word to the region and |
198 | * verify if the overwrite has succeeded |
199 | */ |
200 | *fault_addr = PPC_INST_BLR; |
201 | FAIL_IF_EXIT(*fault_addr != PPC_INST_BLR); |
202 | break; |
203 | |
204 | /* Execute protection test */ |
205 | case PKEY_DISABLE_EXECUTE: |
206 | /* Jump to the region and execute instructions */ |
207 | asm volatile( |
208 | "mtctr %0; bctrl" |
209 | : : "r" (fault_addr) : "ctr" , "lr" ); |
210 | break; |
211 | } |
212 | |
213 | /* |
214 | * Restore the restrictions originally imposed by the |
215 | * restrictive pkey as the signal handler would have |
216 | * cleared out the corresponding AMR bits |
217 | */ |
218 | pkey_set_rights(rest_pkey, rights); |
219 | } |
220 | |
221 | /* Free restrictive pkey */ |
222 | sys_pkey_free(rest_pkey); |
223 | |
224 | return NULL; |
225 | } |
226 | |
227 | static void reset_pkeys(unsigned long rights) |
228 | { |
229 | int pkeys[NR_PKEYS], i; |
230 | |
231 | /* Exhaustively allocate all available pkeys */ |
232 | for (i = 0; i < NR_PKEYS; i++) |
233 | pkeys[i] = sys_pkey_alloc(0, rights); |
234 | |
235 | /* Free all allocated pkeys */ |
236 | for (i = 0; i < NR_PKEYS; i++) |
237 | sys_pkey_free(pkeys[i]); |
238 | } |
239 | |
240 | static int test(void) |
241 | { |
242 | pthread_t prot_thread, pacc_thread; |
243 | struct sigaction act; |
244 | pthread_attr_t attr; |
245 | size_t numinsns; |
246 | struct region r; |
247 | int ret, i; |
248 | |
249 | srand(time(NULL)); |
250 | ret = pkeys_unsupported(); |
251 | if (ret) |
252 | return ret; |
253 | |
254 | /* Allocate the region */ |
255 | r.size = getpagesize(); |
256 | r.base = mmap(NULL, r.size, PROT_RWX, |
257 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
258 | FAIL_IF(r.base == MAP_FAILED); |
259 | |
260 | /* |
261 | * Fill the region with no-ops with a branch at the end |
262 | * for returning to the caller |
263 | */ |
264 | numinsns = r.size / sizeof(r.base[0]); |
265 | for (i = 0; i < numinsns - 1; i++) |
266 | r.base[i] = PPC_INST_NOP; |
267 | r.base[i] = PPC_INST_BLR; |
268 | |
269 | /* Setup SIGSEGV handler */ |
270 | act.sa_handler = 0; |
271 | act.sa_sigaction = segv_handler; |
272 | FAIL_IF(sigprocmask(SIG_SETMASK, 0, &act.sa_mask) != 0); |
273 | act.sa_flags = SA_SIGINFO; |
274 | act.sa_restorer = 0; |
275 | FAIL_IF(sigaction(SIGSEGV, &act, NULL) != 0); |
276 | |
277 | /* |
278 | * For these tests, the parent process should clear all bits of |
279 | * AMR and IAMR, i.e. impose no restrictions, for all available |
280 | * pkeys. This will be the base for the initial AMR and IAMR |
281 | * values for all the test thread pairs. |
282 | * |
283 | * If the AMR and IAMR bits of all available pkeys are cleared |
284 | * before running the tests and a fault is generated when |
285 | * attempting to read, write or execute instructions from a |
286 | * pkey protected region, the pkey responsible for this must be |
287 | * the one from the protect-and-access thread since the other |
288 | * one is fully permissive. Despite that, if the pkey reported |
289 | * by siginfo is not the restrictive pkey, then there must be a |
290 | * kernel bug. |
291 | */ |
292 | reset_pkeys(rights: 0); |
293 | |
294 | /* Setup barrier for protect and protect-and-access threads */ |
295 | FAIL_IF(pthread_attr_init(&attr) != 0); |
296 | FAIL_IF(pthread_barrier_init(&iteration_barrier, NULL, 2) != 0); |
297 | |
298 | /* Setup and start protect and protect-and-read threads */ |
299 | puts("starting thread pair (protect, protect-and-read)" ); |
300 | r.rights = PKEY_DISABLE_ACCESS; |
301 | FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0); |
302 | FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0); |
303 | FAIL_IF(pthread_join(prot_thread, NULL) != 0); |
304 | FAIL_IF(pthread_join(pacc_thread, NULL) != 0); |
305 | |
306 | /* Setup and start protect and protect-and-write threads */ |
307 | puts("starting thread pair (protect, protect-and-write)" ); |
308 | r.rights = PKEY_DISABLE_WRITE; |
309 | FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0); |
310 | FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0); |
311 | FAIL_IF(pthread_join(prot_thread, NULL) != 0); |
312 | FAIL_IF(pthread_join(pacc_thread, NULL) != 0); |
313 | |
314 | /* Setup and start protect and protect-and-execute threads */ |
315 | puts("starting thread pair (protect, protect-and-execute)" ); |
316 | r.rights = PKEY_DISABLE_EXECUTE; |
317 | FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0); |
318 | FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0); |
319 | FAIL_IF(pthread_join(prot_thread, NULL) != 0); |
320 | FAIL_IF(pthread_join(pacc_thread, NULL) != 0); |
321 | |
322 | /* Cleanup */ |
323 | FAIL_IF(pthread_attr_destroy(&attr) != 0); |
324 | FAIL_IF(pthread_barrier_destroy(&iteration_barrier) != 0); |
325 | munmap(r.base, r.size); |
326 | |
327 | return 0; |
328 | } |
329 | |
330 | int main(void) |
331 | { |
332 | return test_harness(test, "pkey_siginfo" ); |
333 | } |
334 | |