1 /* Tests for memory protection keys.
2 Copyright (C) 2017-2022 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
= 3 };
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
;
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. */
118 sigusr1_handler (int signum
)
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;
127 /* Used to report results from other threads. */
130 int access_rights
[key_count
];
131 pthread_t next_thread
;
134 /* Return the thread's access rights for the keys under test. */
136 get_thread_func (void *closure
)
138 struct thread_result
*result
= xmalloc (sizeof (*result
));
139 for (int i
= 0; i
< key_count
; ++i
)
140 result
->access_rights
[i
] = pkey_get (keys
[i
]);
141 memset (&result
->next_thread
, 0, sizeof (result
->next_thread
));
145 /* Wait for initialization and then check that the current thread does
146 not have access through the keys under test. */
148 delayed_thread_func (void *closure
)
150 bool check_access
= *(bool *) closure
;
151 pthread_barrier_wait (&barrier
);
152 struct thread_result
*result
= get_thread_func (NULL
);
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
)
161 TEST_VERIFY (!check_page_access (i
, false));
162 TEST_VERIFY (!check_page_access (i
, true));
166 result
->next_thread
= xpthread_create (NULL
, get_thread_func
, NULL
);
173 long pagesize
= xsysconf (_SC_PAGESIZE
);
175 /* pkey_mprotect with key -1 should work even when there is no
176 protection key support. */
178 int *page
= xmmap (NULL
, pagesize
, PROT_NONE
,
179 MAP_ANONYMOUS
| MAP_PRIVATE
, -1);
180 TEST_COMPARE (pkey_mprotect (page
, pagesize
, PROT_READ
| PROT_WRITE
, -1),
182 volatile int *vpage
= page
;
184 TEST_COMPARE (*vpage
, 5);
185 xmunmap (page
, pagesize
);
188 xpthread_barrier_init (&barrier
, NULL
, 2);
189 bool delayed_thread_check_access
= true;
190 pthread_t delayed_thread
= xpthread_create
191 (NULL
, &delayed_thread_func
, &delayed_thread_check_access
);
193 keys
[0] = pkey_alloc (0, 0);
198 ("kernel does not support memory protection keys");
201 ("CPU does not support memory protection keys: %m");
204 ("no keys available or kernel does not support memory"
206 FAIL_EXIT1 ("pkey_alloc: %m");
208 TEST_COMPARE (pkey_get (keys
[0]), 0);
209 for (int i
= 1; i
< key_count
; ++i
)
211 keys
[i
] = pkey_alloc (0, i
);
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
);
218 /* Check that all the keys have the expected access rights for the
220 for (int i
= 0; i
< key_count
; ++i
)
221 TEST_COMPARE (pkey_get (keys
[i
]), i
);
223 /* Allocate a test page for each key. */
224 for (int i
= 0; i
< key_count
; ++i
)
226 pages
[i
] = xmmap (NULL
, pagesize
, PROT_READ
| PROT_WRITE
,
227 MAP_ANONYMOUS
| MAP_PRIVATE
, -1);
228 TEST_COMPARE (pkey_mprotect ((void *) pages
[i
], pagesize
,
229 PROT_READ
| PROT_WRITE
, keys
[i
]), 0);
232 /* Check that the initial thread does not have access to the new
235 pthread_barrier_wait (&barrier
);
236 struct thread_result
*result
= xpthread_join (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 (result
->next_thread
);
241 for (int i
= 0; i
< key_count
; ++i
)
242 TEST_COMPARE (result
->access_rights
[i
],
243 PKEY_DISABLE_ACCESS
);
248 /* Check that the current thread access rights are inherited by new
251 pthread_t get_thread
= xpthread_create (NULL
, get_thread_func
, NULL
);
252 struct thread_result
*result
= xpthread_join (get_thread
);
253 for (int i
= 0; i
< key_count
; ++i
)
254 TEST_COMPARE (result
->access_rights
[i
], i
);
258 for (int i
= 0; i
< key_count
; ++i
)
259 TEST_COMPARE (pkey_get (keys
[i
]), i
);
261 /* Check that in a signal handler, there is no access. */
262 xsignal (SIGUSR1
, &sigusr1_handler
);
264 xsignal (SIGUSR1
, SIG_DFL
);
265 TEST_COMPARE (sigusr1_handler_ran
, 1);
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));
271 /* The other keys do not. */
272 for (int i
= 1; i
< key_count
; ++i
)
275 printf ("info: checking access for key %d, bits 0x%x\n",
276 i
, pkey_get (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
)
281 TEST_VERIFY (!check_page_access (i
, false));
282 TEST_VERIFY (!check_page_access (i
, true));
286 TEST_VERIFY (i
& PKEY_DISABLE_WRITE
);
287 TEST_VERIFY (check_page_access (i
, false));
288 TEST_VERIFY (!check_page_access (i
, true));
292 /* But if we set the current thread's access rights, we gain
294 for (int do_write
= 0; do_write
< 2; ++do_write
)
295 for (int allowed_key
= 0; allowed_key
< key_count
; ++allowed_key
)
297 for (int i
= 0; i
< key_count
; ++i
)
298 if (i
== allowed_key
)
301 TEST_COMPARE (pkey_set (keys
[i
], 0), 0);
303 TEST_COMPARE (pkey_set (keys
[i
], PKEY_DISABLE_WRITE
), 0);
306 TEST_COMPARE (pkey_set (keys
[i
], PKEY_DISABLE_ACCESS
), 0);
309 printf ("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
)
314 TEST_VERIFY (check_page_access (i
, false));
315 TEST_VERIFY (check_page_access (i
, true) == do_write
);
319 TEST_VERIFY (!check_page_access (i
, false));
320 TEST_VERIFY (!check_page_access (i
, true));
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
)
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));
332 delayed_thread_check_access
= false;
333 delayed_thread
= xpthread_create
334 (NULL
, delayed_thread_func
, &delayed_thread_check_access
);
336 TEST_COMPARE (pkey_free (keys
[0]), 0);
337 /* Second pkey_free will fail because the key has already been
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);
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. */
351 /* The limit is in place to avoid running indefinitely in case
352 there many keys available. */
353 int *keys_array
= xcalloc (100000, sizeof (*keys_array
));
354 int keys_allocated
= 0;
355 while (keys_allocated
< 100000)
357 int new_key
= pkey_alloc (0, PKEY_DISABLE_WRITE
);
360 /* No key reuse observed before running out of keys. */
361 TEST_COMPARE (errno
, ENOSPC
);
364 for (int i
= 0; i
< key_count
; ++i
)
365 if (new_key
== keys
[i
])
367 /* We allocated the key with disabled write access.
368 This should affect the protection state of the
370 TEST_VERIFY (check_page_access (i
, false));
371 TEST_VERIFY (!check_page_access (i
, true));
373 xpthread_barrier_wait (&barrier
);
374 struct thread_result
*result
= xpthread_join (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 (result
->next_thread
);
380 /* Same for a thread which is launched afterwards from
382 TEST_COMPARE (result2
->access_rights
[i
], 0);
385 keys_array
[keys_allocated
++] = new_key
;
386 goto after_key_search
;
388 /* Save key for later deallocation. */
389 keys_array
[keys_allocated
++] = new_key
;
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);
398 for (int i
= 0; i
< key_count
; ++i
)
399 xmunmap ((void *) pages
[i
], pagesize
);
401 xpthread_barrier_destroy (&barrier
);
405 #include <support/test-driver.c>