Warning: That file was not part of the compilation database. It may have many parsing errors.

1/* Tests for memory protection keys.
2 Copyright (C) 2017-2019 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 <http://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 = 4 };
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 (keys[i]);
78
79 volatile int *addr = pages[page];
80 if (test_verbose > 0)
81 {
82 printf ("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, &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 (" --> access allowed");
96 return true;
97 }
98 else
99 {
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);
109 return false;
110 }
111}
112
113static volatile sig_atomic_t sigusr1_handler_ran;
114
115/* Used to check that access is revoked in signal handlers. */
116static void
117sigusr1_handler (int signum)
118{
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;
123}
124
125/* Used to report results from other threads. */
126struct thread_result
127{
128 int access_rights[key_count];
129 pthread_t next_thread;
130};
131
132/* Return the thread's access rights for the keys under test. */
133static void *
134get_thread_func (void *closure)
135{
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));
140 return result;
141}
142
143/* Wait for initialization and then check that the current thread does
144 not have access through the keys under test. */
145static void *
146delayed_thread_func (void *closure)
147{
148 bool check_access = *(bool *) closure;
149 pthread_barrier_wait (&barrier);
150 struct thread_result *result = get_thread_func (NULL);
151
152 if (check_access)
153 {
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)
158 {
159 TEST_VERIFY (!check_page_access (i, false));
160 TEST_VERIFY (!check_page_access (i, true));
161 }
162 }
163
164 result->next_thread = xpthread_create (NULL, get_thread_func, NULL);
165 return result;
166}
167
168static int
169do_test (void)
170{
171 long pagesize = xsysconf (_SC_PAGESIZE);
172
173 /* pkey_mprotect with key -1 should work even when there is no
174 protection key support. */
175 {
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),
179 0);
180 volatile int *vpage = page;
181 *vpage = 5;
182 TEST_COMPARE (*vpage, 5);
183 xmunmap (page, pagesize);
184 }
185
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);
190
191 keys[0] = pkey_alloc (0, 0);
192 if (keys[0] < 0)
193 {
194 if (errno == ENOSYS)
195 FAIL_UNSUPPORTED
196 ("kernel does not support memory protection keys");
197 if (errno == EINVAL)
198 FAIL_UNSUPPORTED
199 ("CPU does not support memory protection keys: %m");
200 FAIL_EXIT1 ("pkey_alloc: %m");
201 }
202 TEST_COMPARE (pkey_get (keys[0]), 0);
203 for (int i = 1; i < key_count; ++i)
204 {
205 keys[i] = pkey_alloc (0, i);
206 if (keys[i] < 0)
207 FAIL_EXIT1 ("pkey_alloc (0, %d): %m", i);
208 /* pkey_alloc is supposed to change the current thread's access
209 rights for the new key. */
210 TEST_COMPARE (pkey_get (keys[i]), i);
211 }
212 /* Check that all the keys have the expected access rights for the
213 current thread. */
214 for (int i = 0; i < key_count; ++i)
215 TEST_COMPARE (pkey_get (keys[i]), i);
216
217 /* Allocate a test page for each key. */
218 for (int i = 0; i < key_count; ++i)
219 {
220 pages[i] = xmmap (NULL, pagesize, PROT_READ | PROT_WRITE,
221 MAP_ANONYMOUS | MAP_PRIVATE, -1);
222 TEST_COMPARE (pkey_mprotect ((void *) pages[i], pagesize,
223 PROT_READ | PROT_WRITE, keys[i]), 0);
224 }
225
226 /* Check that the initial thread does not have access to the new
227 keys. */
228 {
229 pthread_barrier_wait (&barrier);
230 struct thread_result *result = xpthread_join (delayed_thread);
231 for (int i = 0; i < key_count; ++i)
232 TEST_COMPARE (result->access_rights[i],
233 PKEY_DISABLE_ACCESS);
234 struct thread_result *result2 = xpthread_join (result->next_thread);
235 for (int i = 0; i < key_count; ++i)
236 TEST_COMPARE (result->access_rights[i],
237 PKEY_DISABLE_ACCESS);
238 free (result);
239 free (result2);
240 }
241
242 /* Check that the current thread access rights are inherited by new
243 threads. */
244 {
245 pthread_t get_thread = xpthread_create (NULL, get_thread_func, NULL);
246 struct thread_result *result = xpthread_join (get_thread);
247 for (int i = 0; i < key_count; ++i)
248 TEST_COMPARE (result->access_rights[i], i);
249 free (result);
250 }
251
252 for (int i = 0; i < key_count; ++i)
253 TEST_COMPARE (pkey_get (keys[i]), i);
254
255 /* Check that in a signal handler, there is no access. */
256 xsignal (SIGUSR1, &sigusr1_handler);
257 xraise (SIGUSR1);
258 xsignal (SIGUSR1, SIG_DFL);
259 TEST_COMPARE (sigusr1_handler_ran, 1);
260
261 /* The first key results in a writable page. */
262 TEST_VERIFY (check_page_access (0, false));
263 TEST_VERIFY (check_page_access (0, true));
264
265 /* The other keys do not. */
266 for (int i = 1; i < key_count; ++i)
267 {
268 if (test_verbose)
269 printf ("info: checking access for key %d, bits 0x%x\n",
270 i, pkey_get (keys[i]));
271 for (int j = 0; j < key_count; ++j)
272 TEST_COMPARE (pkey_get (keys[j]), j);
273 if (i & PKEY_DISABLE_ACCESS)
274 {
275 TEST_VERIFY (!check_page_access (i, false));
276 TEST_VERIFY (!check_page_access (i, true));
277 }
278 else
279 {
280 TEST_VERIFY (i & PKEY_DISABLE_WRITE);
281 TEST_VERIFY (check_page_access (i, false));
282 TEST_VERIFY (!check_page_access (i, true));
283 }
284 }
285
286 /* But if we set the current thread's access rights, we gain
287 access. */
288 for (int do_write = 0; do_write < 2; ++do_write)
289 for (int allowed_key = 0; allowed_key < key_count; ++allowed_key)
290 {
291 for (int i = 0; i < key_count; ++i)
292 if (i == allowed_key)
293 {
294 if (do_write)
295 TEST_COMPARE (pkey_set (keys[i], 0), 0);
296 else
297 TEST_COMPARE (pkey_set (keys[i], PKEY_DISABLE_WRITE), 0);
298 }
299 else
300 TEST_COMPARE (pkey_set (keys[i], PKEY_DISABLE_ACCESS), 0);
301
302 if (test_verbose)
303 printf ("info: key %d is allowed access for %s\n",
304 allowed_key, do_write ? "writing" : "reading");
305 for (int i = 0; i < key_count; ++i)
306 if (i == allowed_key)
307 {
308 TEST_VERIFY (check_page_access (i, false));
309 TEST_VERIFY (check_page_access (i, true) == do_write);
310 }
311 else
312 {
313 TEST_VERIFY (!check_page_access (i, false));
314 TEST_VERIFY (!check_page_access (i, true));
315 }
316 }
317
318 /* Restore access to all keys, and launch a thread which should
319 inherit that access. */
320 for (int i = 0; i < key_count; ++i)
321 {
322 TEST_COMPARE (pkey_set (keys[i], 0), 0);
323 TEST_VERIFY (check_page_access (i, false));
324 TEST_VERIFY (check_page_access (i, true));
325 }
326 delayed_thread_check_access = false;
327 delayed_thread = xpthread_create
328 (NULL, delayed_thread_func, &delayed_thread_check_access);
329
330 TEST_COMPARE (pkey_free (keys[0]), 0);
331 /* Second pkey_free will fail because the key has already been
332 freed. */
333 TEST_COMPARE (pkey_free (keys[0]),-1);
334 TEST_COMPARE (errno, EINVAL);
335 for (int i = 1; i < key_count; ++i)
336 TEST_COMPARE (pkey_free (keys[i]), 0);
337
338 /* Check what happens to running threads which have access to
339 previously allocated protection keys. The implemented behavior
340 is somewhat dubious: Ideally, pkey_free should revoke access to
341 that key and pkey_alloc of the same (numeric) key should not
342 implicitly confer access to already-running threads, but this is
343 not what happens in practice. */
344 {
345 /* The limit is in place to avoid running indefinitely in case
346 there many keys available. */
347 int *keys_array = xcalloc (100000, sizeof (*keys_array));
348 int keys_allocated = 0;
349 while (keys_allocated < 100000)
350 {
351 int new_key = pkey_alloc (0, PKEY_DISABLE_WRITE);
352 if (new_key < 0)
353 {
354 /* No key reuse observed before running out of keys. */
355 TEST_COMPARE (errno, ENOSPC);
356 break;
357 }
358 for (int i = 0; i < key_count; ++i)
359 if (new_key == keys[i])
360 {
361 /* We allocated the key with disabled write access.
362 This should affect the protection state of the
363 existing page. */
364 TEST_VERIFY (check_page_access (i, false));
365 TEST_VERIFY (!check_page_access (i, true));
366
367 xpthread_barrier_wait (&barrier);
368 struct thread_result *result = xpthread_join (delayed_thread);
369 /* The thread which was launched before should still have
370 access to the key. */
371 TEST_COMPARE (result->access_rights[i], 0);
372 struct thread_result *result2
373 = xpthread_join (result->next_thread);
374 /* Same for a thread which is launched afterwards from
375 the old thread. */
376 TEST_COMPARE (result2->access_rights[i], 0);
377 free (result);
378 free (result2);
379 keys_array[keys_allocated++] = new_key;
380 goto after_key_search;
381 }
382 /* Save key for later deallocation. */
383 keys_array[keys_allocated++] = new_key;
384 }
385 after_key_search:
386 /* Deallocate the keys allocated for testing purposes. */
387 for (int j = 0; j < keys_allocated; ++j)
388 TEST_COMPARE (pkey_free (keys_array[j]), 0);
389 free (keys_array);
390 }
391
392 for (int i = 0; i < key_count; ++i)
393 xmunmap ((void *) pages[i], pagesize);
394
395 xpthread_barrier_destroy (&barrier);
396 return 0;
397}
398
399#include <support/test-driver.c>
400

Warning: That file was not part of the compilation database. It may have many parsing errors.