1 /* Tests for memory protection keys.
2 Copyright (C) 2017-2018 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 <http://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");
200 FAIL_EXIT1 ("pkey_alloc: %m");
202 TEST_COMPARE (pkey_get (keys
[0]), 0);
203 for (int i
= 1; i
< key_count
; ++i
)
205 keys
[i
] = pkey_alloc (0, i
);
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
);
212 /* Check that all the keys have the expected access rights for the
214 for (int i
= 0; i
< key_count
; ++i
)
215 TEST_COMPARE (pkey_get (keys
[i
]), i
);
217 /* Allocate a test page for each key. */
218 for (int i
= 0; i
< key_count
; ++i
)
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);
226 /* Check that the initial thread does not have access to the new
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
);
242 /* Check that the current thread access rights are inherited by new
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
);
252 for (int i
= 0; i
< key_count
; ++i
)
253 TEST_COMPARE (pkey_get (keys
[i
]), i
);
255 /* Check that in a signal handler, there is no access. */
256 xsignal (SIGUSR1
, &sigusr1_handler
);
258 xsignal (SIGUSR1
, SIG_DFL
);
259 TEST_COMPARE (sigusr1_handler_ran
, 1);
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));
265 /* The other keys do not. */
266 for (int i
= 1; i
< key_count
; ++i
)
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
)
275 TEST_VERIFY (!check_page_access (i
, false));
276 TEST_VERIFY (!check_page_access (i
, true));
280 TEST_VERIFY (i
& PKEY_DISABLE_WRITE
);
281 TEST_VERIFY (check_page_access (i
, false));
282 TEST_VERIFY (!check_page_access (i
, true));
286 /* But if we set the current thread's access rights, we gain
288 for (int do_write
= 0; do_write
< 2; ++do_write
)
289 for (int allowed_key
= 0; allowed_key
< key_count
; ++allowed_key
)
291 for (int i
= 0; i
< key_count
; ++i
)
292 if (i
== allowed_key
)
295 TEST_COMPARE (pkey_set (keys
[i
], 0), 0);
297 TEST_COMPARE (pkey_set (keys
[i
], PKEY_DISABLE_WRITE
), 0);
300 TEST_COMPARE (pkey_set (keys
[i
], PKEY_DISABLE_ACCESS
), 0);
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
)
308 TEST_VERIFY (check_page_access (i
, false));
309 TEST_VERIFY (check_page_access (i
, true) == do_write
);
313 TEST_VERIFY (!check_page_access (i
, false));
314 TEST_VERIFY (!check_page_access (i
, true));
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
)
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));
326 delayed_thread_check_access
= false;
327 delayed_thread
= xpthread_create
328 (NULL
, delayed_thread_func
, &delayed_thread_check_access
);
330 TEST_COMPARE (pkey_free (keys
[0]), 0);
331 /* Second pkey_free will fail because the key has already been
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);
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. */
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)
351 int new_key
= pkey_alloc (0, PKEY_DISABLE_WRITE
);
354 /* No key reuse observed before running out of keys. */
355 TEST_COMPARE (errno
, ENOSPC
);
358 for (int i
= 0; i
< key_count
; ++i
)
359 if (new_key
== keys
[i
])
361 /* We allocated the key with disabled write access.
362 This should affect the protection state of the
364 TEST_VERIFY (check_page_access (i
, false));
365 TEST_VERIFY (!check_page_access (i
, true));
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
376 TEST_COMPARE (result2
->access_rights
[i
], 0);
379 keys_array
[keys_allocated
++] = new_key
;
380 goto after_key_search
;
382 /* Save key for later deallocation. */
383 keys_array
[keys_allocated
++] = new_key
;
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);
392 for (int i
= 0; i
< key_count
; ++i
)
393 xmunmap ((void *) pages
[i
], pagesize
);
395 xpthread_barrier_destroy (&barrier
);
399 #include <support/test-driver.c>