1 /* Tests for memory protection keys.
2    Copyright (C) 2017-2021 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.  */
36 static 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.  */
40 enum { key_count = 3 };
41 static int keys[key_count];
42 static volatile int *pages[key_count];
43 
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;
49 
50 /* Used to handle expected read or write faults.  */
51 static void
sigsegv_handler(int signum,siginfo_t * info,void * context)52 sigsegv_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 
60 static 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).  */
67 static bool
check_page_access(int page,bool write)68 check_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 
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.  */
117 static void
sigusr1_handler(int signum)118 sigusr1_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.  */
128 struct 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.  */
135 static void *
get_thread_func(void * closure)136 get_thread_func (void *closure)
137 {
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));
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.  */
147 static void *
delayed_thread_func(void * closure)148 delayed_thread_func (void *closure)
149 {
150   bool check_access = *(bool *) closure;
151   pthread_barrier_wait (&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, get_thread_func, NULL);
167   return result;
168 }
169 
170 static int
do_test(void)171 do_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, pagesize, PROT_NONE,
179                        MAP_ANONYMOUS | MAP_PRIVATE, -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 (page, pagesize);
186   }
187 
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);
192 
193   keys[0] = pkey_alloc (0, 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 (0, 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, 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);
230     }
231 
232   /* Check that the initial thread does not have access to the new
233      keys.  */
234   {
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);
244     free (result);
245     free (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, 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);
255     free (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, &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 ("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)
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 ("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, delayed_thread_func, &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 (100000, sizeof (*keys_array));
354     int keys_allocated = 0;
355     while (keys_allocated < 100000)
356       {
357         int new_key = pkey_alloc (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);
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
381                  the old thread.  */
382               TEST_COMPARE (result2->access_rights[i], 0);
383               free (result);
384               free (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 (keys_array);
396   }
397 
398   for (int i = 0; i < key_count; ++i)
399     xmunmap ((void *) pages[i], pagesize);
400 
401   xpthread_barrier_destroy (&barrier);
402   return 0;
403 }
404 
405 #include <support/test-driver.c>
406