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 TEST_COMPARE (pkey_get (keys[0]), 0);
209 for (int i = 1; i < key_count; ++i)
210 {
211 keys[i] = pkey_alloc (flags: 0, access_rights: i);
212 if (keys[i] < 0)
213 FAIL_EXIT1 ("pkey_alloc (0, %d): %m", i);
214 /* pkey_alloc is supposed to change the current thread's access
215 rights for the new key. */
216 TEST_COMPARE (pkey_get (keys[i]), i);
217 }
218 /* Check that all the keys have the expected access rights for the
219 current thread. */
220 for (int i = 0; i < key_count; ++i)
221 TEST_COMPARE (pkey_get (keys[i]), i);
222
223 /* Allocate a test page for each key. */
224 for (int i = 0; i < key_count; ++i)
225 {
226 pages[i] = xmmap (NULL, length: pagesize, PROT_READ | PROT_WRITE,
227 MAP_ANONYMOUS | MAP_PRIVATE, fd: -1);
228 TEST_COMPARE (pkey_mprotect ((void *) pages[i], pagesize,
229 PROT_READ | PROT_WRITE, keys[i]), 0);
230 }
231
232 /* Check that the initial thread does not have access to the new
233 keys. */
234 {
235 pthread_barrier_wait (barrier: &barrier);
236 struct thread_result *result = xpthread_join (thr: delayed_thread);
237 for (int i = 0; i < key_count; ++i)
238 TEST_COMPARE (result->access_rights[i],
239 PKEY_DISABLE_ACCESS);
240 struct thread_result *result2 = xpthread_join (thr: result->next_thread);
241 for (int i = 0; i < key_count; ++i)
242 TEST_COMPARE (result->access_rights[i],
243 PKEY_DISABLE_ACCESS);
244 free (ptr: result);
245 free (ptr: result2);
246 }
247
248 /* Check that the current thread access rights are inherited by new
249 threads. */
250 {
251 pthread_t get_thread = xpthread_create (NULL, thread_func: get_thread_func, NULL);
252 struct thread_result *result = xpthread_join (thr: get_thread);
253 for (int i = 0; i < key_count; ++i)
254 TEST_COMPARE (result->access_rights[i], i);
255 free (ptr: result);
256 }
257
258 for (int i = 0; i < key_count; ++i)
259 TEST_COMPARE (pkey_get (keys[i]), i);
260
261 /* Check that in a signal handler, there is no access. */
262 xsignal (SIGUSR1, handler: &sigusr1_handler);
263 xraise (SIGUSR1);
264 xsignal (SIGUSR1, SIG_DFL);
265 TEST_COMPARE (sigusr1_handler_ran, 1);
266
267 /* The first key results in a writable page. */
268 TEST_VERIFY (check_page_access (0, false));
269 TEST_VERIFY (check_page_access (0, true));
270
271 /* The other keys do not. */
272 for (int i = 1; i < key_count; ++i)
273 {
274 if (test_verbose)
275 printf (format: "info: checking access for key %d, bits 0x%x\n",
276 i, pkey_get (key: keys[i]));
277 for (int j = 0; j < key_count; ++j)
278 TEST_COMPARE (pkey_get (keys[j]), j);
279 if (i & PKEY_DISABLE_ACCESS)
280 {
281 TEST_VERIFY (!check_page_access (i, false));
282 TEST_VERIFY (!check_page_access (i, true));
283 }
284 else
285 {
286 TEST_VERIFY (i & PKEY_DISABLE_WRITE);
287 TEST_VERIFY (check_page_access (i, false));
288 TEST_VERIFY (!check_page_access (i, true));
289 }
290 }
291
292 /* But if we set the current thread's access rights, we gain
293 access. */
294 for (int do_write = 0; do_write < 2; ++do_write)
295 for (int allowed_key = 0; allowed_key < key_count; ++allowed_key)
296 {
297 for (int i = 0; i < key_count; ++i)
298 if (i == allowed_key)
299 {
300 if (do_write)
301 TEST_COMPARE (pkey_set (keys[i], 0), 0);
302 else
303 TEST_COMPARE (pkey_set (keys[i], PKEY_DISABLE_WRITE), 0);
304 }
305 else
306 TEST_COMPARE (pkey_set (keys[i], PKEY_DISABLE_ACCESS), 0);
307
308 if (test_verbose)
309 printf (format: "info: key %d is allowed access for %s\n",
310 allowed_key, do_write ? "writing" : "reading");
311 for (int i = 0; i < key_count; ++i)
312 if (i == allowed_key)
313 {
314 TEST_VERIFY (check_page_access (i, false));
315 TEST_VERIFY (check_page_access (i, true) == do_write);
316 }
317 else
318 {
319 TEST_VERIFY (!check_page_access (i, false));
320 TEST_VERIFY (!check_page_access (i, true));
321 }
322 }
323
324 /* Restore access to all keys, and launch a thread which should
325 inherit that access. */
326 for (int i = 0; i < key_count; ++i)
327 {
328 TEST_COMPARE (pkey_set (keys[i], 0), 0);
329 TEST_VERIFY (check_page_access (i, false));
330 TEST_VERIFY (check_page_access (i, true));
331 }
332 delayed_thread_check_access = false;
333 delayed_thread = xpthread_create
334 (NULL, thread_func: delayed_thread_func, closure: &delayed_thread_check_access);
335
336 TEST_COMPARE (pkey_free (keys[0]), 0);
337 /* Second pkey_free will fail because the key has already been
338 freed. */
339 TEST_COMPARE (pkey_free (keys[0]),-1);
340 TEST_COMPARE (errno, EINVAL);
341 for (int i = 1; i < key_count; ++i)
342 TEST_COMPARE (pkey_free (keys[i]), 0);
343
344 /* Check what happens to running threads which have access to
345 previously allocated protection keys. The implemented behavior
346 is somewhat dubious: Ideally, pkey_free should revoke access to
347 that key and pkey_alloc of the same (numeric) key should not
348 implicitly confer access to already-running threads, but this is
349 not what happens in practice. */
350 {
351 /* The limit is in place to avoid running indefinitely in case
352 there many keys available. */
353 int *keys_array = xcalloc (n: 100000, s: sizeof (*keys_array));
354 int keys_allocated = 0;
355 while (keys_allocated < 100000)
356 {
357 int new_key = pkey_alloc (flags: 0, PKEY_DISABLE_WRITE);
358 if (new_key < 0)
359 {
360 /* No key reuse observed before running out of keys. */
361 TEST_COMPARE (errno, ENOSPC);
362 break;
363 }
364 for (int i = 0; i < key_count; ++i)
365 if (new_key == keys[i])
366 {
367 /* We allocated the key with disabled write access.
368 This should affect the protection state of the
369 existing page. */
370 TEST_VERIFY (check_page_access (i, false));
371 TEST_VERIFY (!check_page_access (i, true));
372
373 xpthread_barrier_wait (barrier: &barrier);
374 struct thread_result *result = xpthread_join (thr: delayed_thread);
375 /* The thread which was launched before should still have
376 access to the key. */
377 TEST_COMPARE (result->access_rights[i], 0);
378 struct thread_result *result2
379 = xpthread_join (thr: result->next_thread);
380 /* Same for a thread which is launched afterwards from
381 the old thread. */
382 TEST_COMPARE (result2->access_rights[i], 0);
383 free (ptr: result);
384 free (ptr: result2);
385 keys_array[keys_allocated++] = new_key;
386 goto after_key_search;
387 }
388 /* Save key for later deallocation. */
389 keys_array[keys_allocated++] = new_key;
390 }
391 after_key_search:
392 /* Deallocate the keys allocated for testing purposes. */
393 for (int j = 0; j < keys_allocated; ++j)
394 TEST_COMPARE (pkey_free (keys_array[j]), 0);
395 free (ptr: keys_array);
396 }
397
398 for (int i = 0; i < key_count; ++i)
399 xmunmap (addr: (void *) pages[i], length: pagesize);
400
401 xpthread_barrier_destroy (barrier: &barrier);
402 return 0;
403}
404
405#include <support/test-driver.c>
406

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