1 /* Tests for memory protection keys.
2 Copyright (C) 2017-2020 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
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.
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.
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/>. */
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>
34 /* Used to force threads to wait until the main thread has set up the
36 static pthread_barrier_t barrier;
38 /* The keys used for testing. These have been allocated with access
39 rights set based on their array index. */
40 enum { key_count = 4 };
41 static int keys[key_count];
42 static volatile int *pages[key_count];
44 /* Used to report results from the signal handler. */
45 static volatile void *sigsegv_addr;
46 static volatile int sigsegv_code;
47 static volatile int sigsegv_pkey;
48 static sigjmp_buf sigsegv_jmp;
50 /* Used to handle expected read or write faults. */
52 sigsegv_handler (int signum, siginfo_t *info, void *context)
54 sigsegv_addr = info->si_addr;
55 sigsegv_code = info->si_code;
56 sigsegv_pkey = info->si_pkey;
57 siglongjmp (sigsegv_jmp, 2);
60 static const struct sigaction sigsegv_sigaction =
62 .sa_flags = SA_RESETHAND | SA_SIGINFO,
63 .sa_sigaction = &sigsegv_handler,
66 /* Check if PAGE is readable (if !WRITE) or writable (if WRITE). */
68 check_page_access (int page, bool write)
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 (keys[i]);
79 volatile int *addr = pages[page];
82 printf ("info: checking access at %p (page %d) for %s\n",
83 addr, page, write ? "writing" : "reading");
85 int result = sigsetjmp (sigsegv_jmp, 1);
88 xsigaction (SIGSEGV, &sigsegv_sigaction, NULL);
93 xsignal (SIGSEGV, SIG_DFL);
95 puts (" --> access allowed");
100 xsignal (SIGSEGV, SIG_DFL);
101 if (test_verbose > 0)
102 puts (" --> 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);
113 static volatile sig_atomic_t sigusr1_handler_ran;
115 /* Used to check that access is revoked in signal handlers. */
117 sigusr1_handler (int signum)
119 TEST_COMPARE (signum, SIGUSR1);
120 for (int i = 0; i < key_count; ++i)
121 TEST_COMPARE (pkey_get (keys[i]), PKEY_DISABLE_ACCESS);
122 sigusr1_handler_ran = 1;
125 /* Used to report results from other threads. */
128 int access_rights[key_count];
129 pthread_t next_thread;
132 /* Return the thread's access rights for the keys under test. */
134 get_thread_func (void *closure)
136 struct thread_result *result = xmalloc (sizeof (*result));
137 for (int i = 0; i < key_count; ++i)
138 result->access_rights[i] = pkey_get (keys[i]);
139 memset (&result->next_thread, 0, sizeof (result->next_thread));
143 /* Wait for initialization and then check that the current thread does
144 not have access through the keys under test. */
146 delayed_thread_func (void *closure)
148 bool check_access = *(bool *) closure;
149 pthread_barrier_wait (&barrier);
150 struct thread_result *result = get_thread_func (NULL);
154 /* Also check directly. This code should not run with other
155 threads in parallel because of the SIGSEGV handler which is
156 installed by check_page_access. */
157 for (int i = 0; i < key_count; ++i)
159 TEST_VERIFY (!check_page_access (i, false));
160 TEST_VERIFY (!check_page_access (i, true));
164 result->next_thread = xpthread_create (NULL, get_thread_func, NULL);
171 long pagesize = xsysconf (_SC_PAGESIZE);
173 /* pkey_mprotect with key -1 should work even when there is no
174 protection key support. */
176 int *page = xmmap (NULL, pagesize, PROT_NONE,
177 MAP_ANONYMOUS | MAP_PRIVATE, -1);
178 TEST_COMPARE (pkey_mprotect (page, pagesize, PROT_READ | PROT_WRITE, -1),
180 volatile int *vpage = page;
182 TEST_COMPARE (*vpage, 5);
183 xmunmap (page, pagesize);
186 xpthread_barrier_init (&barrier, NULL, 2);
187 bool delayed_thread_check_access = true;
188 pthread_t delayed_thread = xpthread_create
189 (NULL, &delayed_thread_func, &delayed_thread_check_access);
191 keys[0] = pkey_alloc (0, 0);
196 ("kernel does not support memory protection keys");
199 ("CPU does not support memory protection keys: %m");
202 ("no keys available or kernel does not support memory"
204 FAIL_EXIT1 ("pkey_alloc: %m");
206 TEST_COMPARE (pkey_get (keys[0]), 0);
207 for (int i = 1; i < key_count; ++i)
209 keys[i] = pkey_alloc (0, i);
211 FAIL_EXIT1 ("pkey_alloc (0, %d): %m", i);
212 /* pkey_alloc is supposed to change the current thread's access
213 rights for the new key. */
214 TEST_COMPARE (pkey_get (keys[i]), i);
216 /* Check that all the keys have the expected access rights for the
218 for (int i = 0; i < key_count; ++i)
219 TEST_COMPARE (pkey_get (keys[i]), i);
221 /* Allocate a test page for each key. */
222 for (int i = 0; i < key_count; ++i)
224 pages[i] = xmmap (NULL, pagesize, PROT_READ | PROT_WRITE,
225 MAP_ANONYMOUS | MAP_PRIVATE, -1);
226 TEST_COMPARE (pkey_mprotect ((void *) pages[i], pagesize,
227 PROT_READ | PROT_WRITE, keys[i]), 0);
230 /* Check that the initial thread does not have access to the new
233 pthread_barrier_wait (&barrier);
234 struct thread_result *result = xpthread_join (delayed_thread);
235 for (int i = 0; i < key_count; ++i)
236 TEST_COMPARE (result->access_rights[i],
237 PKEY_DISABLE_ACCESS);
238 struct thread_result *result2 = xpthread_join (result->next_thread);
239 for (int i = 0; i < key_count; ++i)
240 TEST_COMPARE (result->access_rights[i],
241 PKEY_DISABLE_ACCESS);
246 /* Check that the current thread access rights are inherited by new
249 pthread_t get_thread = xpthread_create (NULL, get_thread_func, NULL);
250 struct thread_result *result = xpthread_join (get_thread);
251 for (int i = 0; i < key_count; ++i)
252 TEST_COMPARE (result->access_rights[i], i);
256 for (int i = 0; i < key_count; ++i)
257 TEST_COMPARE (pkey_get (keys[i]), i);
259 /* Check that in a signal handler, there is no access. */
260 xsignal (SIGUSR1, &sigusr1_handler);
262 xsignal (SIGUSR1, SIG_DFL);
263 TEST_COMPARE (sigusr1_handler_ran, 1);
265 /* The first key results in a writable page. */
266 TEST_VERIFY (check_page_access (0, false));
267 TEST_VERIFY (check_page_access (0, true));
269 /* The other keys do not. */
270 for (int i = 1; i < key_count; ++i)
273 printf ("info: checking access for key %d, bits 0x%x\n",
274 i, pkey_get (keys[i]));
275 for (int j = 0; j < key_count; ++j)
276 TEST_COMPARE (pkey_get (keys[j]), j);
277 if (i & PKEY_DISABLE_ACCESS)
279 TEST_VERIFY (!check_page_access (i, false));
280 TEST_VERIFY (!check_page_access (i, true));
284 TEST_VERIFY (i & PKEY_DISABLE_WRITE);
285 TEST_VERIFY (check_page_access (i, false));
286 TEST_VERIFY (!check_page_access (i, true));
290 /* But if we set the current thread's access rights, we gain
292 for (int do_write = 0; do_write < 2; ++do_write)
293 for (int allowed_key = 0; allowed_key < key_count; ++allowed_key)
295 for (int i = 0; i < key_count; ++i)
296 if (i == allowed_key)
299 TEST_COMPARE (pkey_set (keys[i], 0), 0);
301 TEST_COMPARE (pkey_set (keys[i], PKEY_DISABLE_WRITE), 0);
304 TEST_COMPARE (pkey_set (keys[i], PKEY_DISABLE_ACCESS), 0);
307 printf ("info: key %d is allowed access for %s\n",
308 allowed_key, do_write ? "writing" : "reading");
309 for (int i = 0; i < key_count; ++i)
310 if (i == allowed_key)
312 TEST_VERIFY (check_page_access (i, false));
313 TEST_VERIFY (check_page_access (i, true) == do_write);
317 TEST_VERIFY (!check_page_access (i, false));
318 TEST_VERIFY (!check_page_access (i, true));
322 /* Restore access to all keys, and launch a thread which should
323 inherit that access. */
324 for (int i = 0; i < key_count; ++i)
326 TEST_COMPARE (pkey_set (keys[i], 0), 0);
327 TEST_VERIFY (check_page_access (i, false));
328 TEST_VERIFY (check_page_access (i, true));
330 delayed_thread_check_access = false;
331 delayed_thread = xpthread_create
332 (NULL, delayed_thread_func, &delayed_thread_check_access);
334 TEST_COMPARE (pkey_free (keys[0]), 0);
335 /* Second pkey_free will fail because the key has already been
337 TEST_COMPARE (pkey_free (keys[0]),-1);
338 TEST_COMPARE (errno, EINVAL);
339 for (int i = 1; i < key_count; ++i)
340 TEST_COMPARE (pkey_free (keys[i]), 0);
342 /* Check what happens to running threads which have access to
343 previously allocated protection keys. The implemented behavior
344 is somewhat dubious: Ideally, pkey_free should revoke access to
345 that key and pkey_alloc of the same (numeric) key should not
346 implicitly confer access to already-running threads, but this is
347 not what happens in practice. */
349 /* The limit is in place to avoid running indefinitely in case
350 there many keys available. */
351 int *keys_array = xcalloc (100000, sizeof (*keys_array));
352 int keys_allocated = 0;
353 while (keys_allocated < 100000)
355 int new_key = pkey_alloc (0, PKEY_DISABLE_WRITE);
358 /* No key reuse observed before running out of keys. */
359 TEST_COMPARE (errno, ENOSPC);
362 for (int i = 0; i < key_count; ++i)
363 if (new_key == keys[i])
365 /* We allocated the key with disabled write access.
366 This should affect the protection state of the
368 TEST_VERIFY (check_page_access (i, false));
369 TEST_VERIFY (!check_page_access (i, true));
371 xpthread_barrier_wait (&barrier);
372 struct thread_result *result = xpthread_join (delayed_thread);
373 /* The thread which was launched before should still have
374 access to the key. */
375 TEST_COMPARE (result->access_rights[i], 0);
376 struct thread_result *result2
377 = xpthread_join (result->next_thread);
378 /* Same for a thread which is launched afterwards from
380 TEST_COMPARE (result2->access_rights[i], 0);
383 keys_array[keys_allocated++] = new_key;
384 goto after_key_search;
386 /* Save key for later deallocation. */
387 keys_array[keys_allocated++] = new_key;
390 /* Deallocate the keys allocated for testing purposes. */
391 for (int j = 0; j < keys_allocated; ++j)
392 TEST_COMPARE (pkey_free (keys_array[j]), 0);
396 for (int i = 0; i < key_count; ++i)
397 xmunmap ((void *) pages[i], pagesize);
399 xpthread_barrier_destroy (&barrier);
403 #include <support/test-driver.c>