(root)/
glibc-2.38/
sysdeps/
unix/
sysv/
linux/
tst-pkey.c
       1  /* Tests for memory protection keys.
       2     Copyright (C) 2017-2023 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
      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
      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
     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 *
     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 *
     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
     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>