1/* Tests for memory protection keys.
2 Copyright (C) 2017-2024 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
18
19#include <errno.h>
20#include <inttypes.h>
21#include <setjmp.h>
22#include <stdbool.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <support/check.h>
27#include <support/support.h>
28#include <support/test-driver.h>
29#include <support/xsignal.h>
30#include <support/xthread.h>
31#include <support/xunistd.h>
32#include <sys/mman.h>
33
34/* Used to force threads to wait until the main thread has set up the
35 keys as intended. */
36static pthread_barrier_t barrier;
37
38/* The keys used for testing. These have been allocated with access
39 rights set based on their array index. */
40enum { key_count = 3 };
41static int keys[key_count];
42static volatile int *pages[key_count];
43
44/* Used to report results from the signal handler. */
45static volatile void *sigsegv_addr;
46static volatile int sigsegv_code;
47static volatile int sigsegv_pkey;
48static sigjmp_buf sigsegv_jmp;
49
50/* Used to handle expected read or write faults. */
51static void
52sigsegv_handler (int signum, siginfo_t *info, void *context)
53{
54 sigsegv_addr = info->si_addr;
55 sigsegv_code = info->si_code;
56 sigsegv_pkey = info->si_pkey;
57 siglongjmp (sigsegv_jmp, 2);
58}
59
60static const struct sigaction sigsegv_sigaction =
61 {
62 .sa_flags = SA_RESETHAND | SA_SIGINFO,
63 .sa_sigaction = &sigsegv_handler,
64 };
65
66/* Check if PAGE is readable (if !WRITE) or writable (if WRITE). */
67static bool
68check_page_access (int page, bool write)
69{
70 /* This is needed to work around bug 22396: On x86-64, siglongjmp
71 does not restore the protection key access rights for the current
72 thread. We restore only the access rights for the keys under
73 test. (This is not a general solution to this problem, but it
74 allows testing to proceed after a fault.) */
75 unsigned saved_rights[key_count];
76 for (int i = 0; i < key_count; ++i)
77 saved_rights[i] = pkey_get (key: keys[i]);
78
79 volatile int *addr = pages[page];
80 if (test_verbose > 0)
81 {
82 printf (format: "info: checking access at %p (page %d) for %s\n",
83 addr, page, write ? "writing" : "reading");
84 }
85 int result = sigsetjmp (sigsegv_jmp, 1);
86 if (result == 0)
87 {
88 xsigaction (SIGSEGV, newact: &sigsegv_sigaction, NULL);
89 if (write)
90 *addr = 3;
91 else
92 (void) *addr;
93 xsignal (SIGSEGV, SIG_DFL);
94 if (test_verbose > 0)
95 puts (s: " --> access allowed");
96 return true;
97 }
98 else
99 {
100 xsignal (SIGSEGV, SIG_DFL);
101 if (test_verbose > 0)
102 puts (s: " --> access denied");
103 TEST_COMPARE (result, 2);
104 TEST_COMPARE ((uintptr_t) sigsegv_addr, (uintptr_t) addr);
105 TEST_COMPARE (sigsegv_code, SEGV_PKUERR);
106 TEST_COMPARE (sigsegv_pkey, keys[page]);
107 for (int i = 0; i < key_count; ++i)
108 TEST_COMPARE (pkey_set (keys[i], saved_rights[i]), 0);
109 return false;
110 }
111}
112
113static volatile sig_atomic_t sigusr1_handler_ran;
114/* Used to check the behavior in signal handlers. In x86 all access are
115 revoked during signal handling. In PowerPC the key permissions are
116 inherited by the interrupted thread. This test accept both approaches. */
117static void
118sigusr1_handler (int signum)
119{
120 TEST_COMPARE (signum, SIGUSR1);
121 for (int i = 0; i < key_count; ++i)
122 TEST_VERIFY (pkey_get (keys[i]) == PKEY_DISABLE_ACCESS
123 || pkey_get (keys[i]) == i);
124 sigusr1_handler_ran = 1;
125}
126
127/* Used to report results from other threads. */
128struct thread_result
129{
130 int access_rights[key_count];
131 pthread_t next_thread;
132};
133
134/* Return the thread's access rights for the keys under test. */
135static void *
136get_thread_func (void *closure)
137{
138 struct thread_result *result = xmalloc (n: sizeof (*result));
139 for (int i = 0; i < key_count; ++i)
140 result->access_rights[i] = pkey_get (key: keys[i]);
141 memset (&result->next_thread, 0, sizeof (result->next_thread));
142 return result;
143}
144
145/* Wait for initialization and then check that the current thread does
146 not have access through the keys under test. */
147static void *
148delayed_thread_func (void *closure)
149{
150 bool check_access = *(bool *) closure;
151 pthread_barrier_wait (barrier: &barrier);
152 struct thread_result *result = get_thread_func (NULL);
153
154 if (check_access)
155 {
156 /* Also check directly. This code should not run with other
157 threads in parallel because of the SIGSEGV handler which is
158 installed by check_page_access. */
159 for (int i = 0; i < key_count; ++i)
160 {
161 TEST_VERIFY (!check_page_access (i, false));
162 TEST_VERIFY (!check_page_access (i, true));
163 }
164 }
165
166 result->next_thread = xpthread_create (NULL, thread_func: get_thread_func, NULL);
167 return result;
168}
169
170static int
171do_test (void)
172{
173 long pagesize = xsysconf (_SC_PAGESIZE);
174
175 /* pkey_mprotect with key -1 should work even when there is no
176 protection key support. */
177 {
178 int *page = xmmap (NULL, length: pagesize, PROT_NONE,
179 MAP_ANONYMOUS | MAP_PRIVATE, fd: -1);
180 TEST_COMPARE (pkey_mprotect (page, pagesize, PROT_READ | PROT_WRITE, -1),
181 0);
182 volatile int *vpage = page;
183 *vpage = 5;
184 TEST_COMPARE (*vpage, 5);
185 xmunmap (addr: page, length: pagesize);
186 }
187
188 xpthread_barrier_init (barrier: &barrier, NULL, count: 2);
189 bool delayed_thread_check_access = true;
190 pthread_t delayed_thread = xpthread_create
191 (NULL, thread_func: &delayed_thread_func, closure: &delayed_thread_check_access);
192
193 keys[0] = pkey_alloc (flags: 0, access_rights: 0);
194 if (keys[0] < 0)
195 {
196 if (errno == ENOSYS)
197 FAIL_UNSUPPORTED
198 ("kernel does not support memory protection keys");
199 if (errno == EINVAL)
200 FAIL_UNSUPPORTED
201 ("CPU does not support memory protection keys: %m");
202 if (errno == ENOSPC)
203 FAIL_UNSUPPORTED
204 ("no keys available or kernel does not support memory"
205 " protection keys");
206 FAIL_EXIT1 ("pkey_alloc: %m");
207 }
208 if (pkey_get (key: keys[0]) < 0)
209 {
210 if (errno == ENOSYS)
211 FAIL_UNSUPPORTED
212 ("glibc does not support memory protection keys");
213 FAIL_EXIT1 ("pkey_alloc: %m");
214 }
215 for (int i = 1; i < key_count; ++i)
216 {
217 keys[i] = pkey_alloc (flags: 0, access_rights: i);
218 if (keys[i] < 0)
219 FAIL_EXIT1 ("pkey_alloc (0, %d): %m", i);
220 /* pkey_alloc is supposed to change the current thread's access
221 rights for the new key. */
222 TEST_COMPARE (pkey_get (keys[i]), i);
223 }
224 /* Check that all the keys have the expected access rights for the
225 current thread. */
226 for (int i = 0; i < key_count; ++i)
227 TEST_COMPARE (pkey_get (keys[i]), i);
228
229 /* Allocate a test page for each key. */
230 for (int i = 0; i < key_count; ++i)
231 {
232 pages[i] = xmmap (NULL, length: pagesize, PROT_READ | PROT_WRITE,
233 MAP_ANONYMOUS | MAP_PRIVATE, fd: -1);
234 TEST_COMPARE (pkey_mprotect ((void *) pages[i], pagesize,
235 PROT_READ | PROT_WRITE, keys[i]), 0);
236 }
237
238 /* Check that the initial thread does not have access to the new
239 keys. */
240 {
241 pthread_barrier_wait (barrier: &barrier);
242 struct thread_result *result = xpthread_join (thr: delayed_thread);
243 for (int i = 0; i < key_count; ++i)
244 TEST_COMPARE (result->access_rights[i],
245 PKEY_DISABLE_ACCESS);
246 struct thread_result *result2 = xpthread_join (thr: result->next_thread);
247 for (int i = 0; i < key_count; ++i)
248 TEST_COMPARE (result->access_rights[i],
249 PKEY_DISABLE_ACCESS);
250 free (ptr: result);
251 free (ptr: result2);
252 }
253
254 /* Check that the current thread access rights are inherited by new
255 threads. */
256 {
257 pthread_t get_thread = xpthread_create (NULL, thread_func: get_thread_func, NULL);
258 struct thread_result *result = xpthread_join (thr: get_thread);
259 for (int i = 0; i < key_count; ++i)
260 TEST_COMPARE (result->access_rights[i], i);
261 free (ptr: result);
262 }
263
264 for (int i = 0; i < key_count; ++i)
265 TEST_COMPARE (pkey_get (keys[i]), i);
266
267 /* Check that in a signal handler, there is no access. */
268 xsignal (SIGUSR1, handler: &sigusr1_handler);
269 xraise (SIGUSR1);
270 xsignal (SIGUSR1, SIG_DFL);
271 TEST_COMPARE (sigusr1_handler_ran, 1);
272
273 /* The first key results in a writable page. */
274 TEST_VERIFY (check_page_access (0, false));
275 TEST_VERIFY (check_page_access (0, true));
276
277 /* The other keys do not. */
278 for (int i = 1; i < key_count; ++i)
279 {
280 if (test_verbose)
281 printf (format: "info: checking access for key %d, bits 0x%x\n",
282 i, pkey_get (key: keys[i]));
283 for (int j = 0; j < key_count; ++j)
284 TEST_COMPARE (pkey_get (keys[j]), j);
285 if (i & PKEY_DISABLE_ACCESS)
286 {
287 TEST_VERIFY (!check_page_access (i, false));
288 TEST_VERIFY (!check_page_access (i, true));
289 }
290 else
291 {
292 TEST_VERIFY (i & PKEY_DISABLE_WRITE);
293 TEST_VERIFY (check_page_access (i, false));
294 TEST_VERIFY (!check_page_access (i, true));
295 }
296 }
297
298 /* But if we set the current thread's access rights, we gain
299 access. */
300 for (int do_write = 0; do_write < 2; ++do_write)
301 for (int allowed_key = 0; allowed_key < key_count; ++allowed_key)
302 {
303 for (int i = 0; i < key_count; ++i)
304 if (i == allowed_key)
305 {
306 if (do_write)
307 TEST_COMPARE (pkey_set (keys[i], 0), 0);
308 else
309 TEST_COMPARE (pkey_set (keys[i], PKEY_DISABLE_WRITE), 0);
310 }
311 else
312 TEST_COMPARE (pkey_set (keys[i], PKEY_DISABLE_ACCESS), 0);
313
314 if (test_verbose)
315 printf (format: "info: key %d is allowed access for %s\n",
316 allowed_key, do_write ? "writing" : "reading");
317 for (int i = 0; i < key_count; ++i)
318 if (i == allowed_key)
319 {
320 TEST_VERIFY (check_page_access (i, false));
321 TEST_VERIFY (check_page_access (i, true) == do_write);
322 }
323 else
324 {
325 TEST_VERIFY (!check_page_access (i, false));
326 TEST_VERIFY (!check_page_access (i, true));
327 }
328 }
329
330 /* Restore access to all keys, and launch a thread which should
331 inherit that access. */
332 for (int i = 0; i < key_count; ++i)
333 {
334 TEST_COMPARE (pkey_set (keys[i], 0), 0);
335 TEST_VERIFY (check_page_access (i, false));
336 TEST_VERIFY (check_page_access (i, true));
337 }
338 delayed_thread_check_access = false;
339 delayed_thread = xpthread_create
340 (NULL, thread_func: delayed_thread_func, closure: &delayed_thread_check_access);
341
342 TEST_COMPARE (pkey_free (keys[0]), 0);
343 /* Second pkey_free will fail because the key has already been
344 freed. */
345 TEST_COMPARE (pkey_free (keys[0]),-1);
346 TEST_COMPARE (errno, EINVAL);
347 for (int i = 1; i < key_count; ++i)
348 TEST_COMPARE (pkey_free (keys[i]), 0);
349
350 /* Check what happens to running threads which have access to
351 previously allocated protection keys. The implemented behavior
352 is somewhat dubious: Ideally, pkey_free should revoke access to
353 that key and pkey_alloc of the same (numeric) key should not
354 implicitly confer access to already-running threads, but this is
355 not what happens in practice. */
356 {
357 /* The limit is in place to avoid running indefinitely in case
358 there many keys available. */
359 int *keys_array = xcalloc (n: 100000, s: sizeof (*keys_array));
360 int keys_allocated = 0;
361 while (keys_allocated < 100000)
362 {
363 int new_key = pkey_alloc (flags: 0, PKEY_DISABLE_WRITE);
364 if (new_key < 0)
365 {
366 /* No key reuse observed before running out of keys. */
367 TEST_COMPARE (errno, ENOSPC);
368 break;
369 }
370 for (int i = 0; i < key_count; ++i)
371 if (new_key == keys[i])
372 {
373 /* We allocated the key with disabled write access.
374 This should affect the protection state of the
375 existing page. */
376 TEST_VERIFY (check_page_access (i, false));
377 TEST_VERIFY (!check_page_access (i, true));
378
379 xpthread_barrier_wait (barrier: &barrier);
380 struct thread_result *result = xpthread_join (thr: delayed_thread);
381 /* The thread which was launched before should still have
382 access to the key. */
383 TEST_COMPARE (result->access_rights[i], 0);
384 struct thread_result *result2
385 = xpthread_join (thr: result->next_thread);
386 /* Same for a thread which is launched afterwards from
387 the old thread. */
388 TEST_COMPARE (result2->access_rights[i], 0);
389 free (ptr: result);
390 free (ptr: result2);
391 keys_array[keys_allocated++] = new_key;
392 goto after_key_search;
393 }
394 /* Save key for later deallocation. */
395 keys_array[keys_allocated++] = new_key;
396 }
397 after_key_search:
398 /* Deallocate the keys allocated for testing purposes. */
399 for (int j = 0; j < keys_allocated; ++j)
400 TEST_COMPARE (pkey_free (keys_array[j]), 0);
401 free (ptr: keys_array);
402 }
403
404 for (int i = 0; i < key_count; ++i)
405 xmunmap (addr: (void *) pages[i], length: pagesize);
406
407 xpthread_barrier_destroy (barrier: &barrier);
408 return 0;
409}
410
411#include <support/test-driver.c>
412

source code of glibc/sysdeps/unix/sysv/linux/tst-pkey.c