Blame sysdeps/unix/sysv/linux/tst-pkey.c

Packit 6c4009
/* Tests for memory protection keys.
Packit 6c4009
   Copyright (C) 2017-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include <inttypes.h>
Packit 6c4009
#include <setjmp.h>
Packit 6c4009
#include <stdbool.h>
Packit 6c4009
#include <stdio.h>
Packit 6c4009
#include <stdlib.h>
Packit 6c4009
#include <string.h>
Packit 6c4009
#include <support/check.h>
Packit 6c4009
#include <support/support.h>
Packit 6c4009
#include <support/test-driver.h>
Packit 6c4009
#include <support/xsignal.h>
Packit 6c4009
#include <support/xthread.h>
Packit 6c4009
#include <support/xunistd.h>
Packit 6c4009
#include <sys/mman.h>
Packit 6c4009
Packit 6c4009
/* Used to force threads to wait until the main thread has set up the
Packit 6c4009
   keys as intended.  */
Packit 6c4009
static pthread_barrier_t barrier;
Packit 6c4009
Packit 6c4009
/* The keys used for testing.  These have been allocated with access
Packit 6c4009
   rights set based on their array index.  */
Packit Service 1cfa45
enum { key_count = 3 };
Packit 6c4009
static int keys[key_count];
Packit 6c4009
static volatile int *pages[key_count];
Packit 6c4009
Packit 6c4009
/* Used to report results from the signal handler.  */
Packit 6c4009
static volatile void *sigsegv_addr;
Packit 6c4009
static volatile int sigsegv_code;
Packit 6c4009
static volatile int sigsegv_pkey;
Packit 6c4009
static sigjmp_buf sigsegv_jmp;
Packit 6c4009
Packit 6c4009
/* Used to handle expected read or write faults.  */
Packit 6c4009
static void
Packit 6c4009
sigsegv_handler (int signum, siginfo_t *info, void *context)
Packit 6c4009
{
Packit 6c4009
  sigsegv_addr = info->si_addr;
Packit 6c4009
  sigsegv_code = info->si_code;
Packit 6c4009
  sigsegv_pkey = info->si_pkey;
Packit 6c4009
  siglongjmp (sigsegv_jmp, 2);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static const struct sigaction sigsegv_sigaction =
Packit 6c4009
  {
Packit 6c4009
    .sa_flags = SA_RESETHAND | SA_SIGINFO,
Packit 6c4009
    .sa_sigaction = &sigsegv_handler,
Packit 6c4009
  };
Packit 6c4009
Packit 6c4009
/* Check if PAGE is readable (if !WRITE) or writable (if WRITE).  */
Packit 6c4009
static bool
Packit 6c4009
check_page_access (int page, bool write)
Packit 6c4009
{
Packit 6c4009
  /* This is needed to work around bug 22396: On x86-64, siglongjmp
Packit 6c4009
     does not restore the protection key access rights for the current
Packit 6c4009
     thread.  We restore only the access rights for the keys under
Packit 6c4009
     test.  (This is not a general solution to this problem, but it
Packit 6c4009
     allows testing to proceed after a fault.)  */
Packit 6c4009
  unsigned saved_rights[key_count];
Packit 6c4009
  for (int i = 0; i < key_count; ++i)
Packit 6c4009
    saved_rights[i] = pkey_get (keys[i]);
Packit 6c4009
Packit 6c4009
  volatile int *addr = pages[page];
Packit 6c4009
  if (test_verbose > 0)
Packit 6c4009
    {
Packit 6c4009
      printf ("info: checking access at %p (page %d) for %s\n",
Packit 6c4009
              addr, page, write ? "writing" : "reading");
Packit 6c4009
    }
Packit 6c4009
  int result = sigsetjmp (sigsegv_jmp, 1);
Packit 6c4009
  if (result == 0)
Packit 6c4009
    {
Packit 6c4009
      xsigaction (SIGSEGV, &sigsegv_sigaction, NULL);
Packit 6c4009
      if (write)
Packit 6c4009
        *addr = 3;
Packit 6c4009
      else
Packit 6c4009
        (void) *addr;
Packit 6c4009
      xsignal (SIGSEGV, SIG_DFL);
Packit 6c4009
      if (test_verbose > 0)
Packit 6c4009
        puts ("  --> access allowed");
Packit 6c4009
      return true;
Packit 6c4009
    }
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
      xsignal (SIGSEGV, SIG_DFL);
Packit 6c4009
      if (test_verbose > 0)
Packit 6c4009
        puts ("  --> access denied");
Packit 6c4009
      TEST_COMPARE (result, 2);
Packit 6c4009
      TEST_COMPARE ((uintptr_t) sigsegv_addr, (uintptr_t) addr);
Packit 6c4009
      TEST_COMPARE (sigsegv_code, SEGV_PKUERR);
Packit 6c4009
      TEST_COMPARE (sigsegv_pkey, keys[page]);
Packit 6c4009
      for (int i = 0; i < key_count; ++i)
Packit 6c4009
        TEST_COMPARE (pkey_set (keys[i], saved_rights[i]), 0);
Packit 6c4009
      return false;
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static volatile sig_atomic_t sigusr1_handler_ran;
Packit Service 1cfa45
/* Used to check the behavior in signal handlers.  In x86 all access are
Packit Service 1cfa45
   revoked during signal handling.  In PowerPC the key permissions are
Packit Service 1cfa45
   inherited by the interrupted thread. This test accept both approaches.  */
Packit 6c4009
static void
Packit 6c4009
sigusr1_handler (int signum)
Packit 6c4009
{
Packit 6c4009
  TEST_COMPARE (signum, SIGUSR1);
Packit 6c4009
  for (int i = 0; i < key_count; ++i)
Packit Service 1cfa45
    TEST_VERIFY (pkey_get (keys[i]) == PKEY_DISABLE_ACCESS
Packit Service 1cfa45
                 || pkey_get (keys[i]) == i);
Packit 6c4009
  sigusr1_handler_ran = 1;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Used to report results from other threads.  */
Packit 6c4009
struct thread_result
Packit 6c4009
{
Packit 6c4009
  int access_rights[key_count];
Packit 6c4009
  pthread_t next_thread;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* Return the thread's access rights for the keys under test.  */
Packit 6c4009
static void *
Packit 6c4009
get_thread_func (void *closure)
Packit 6c4009
{
Packit 6c4009
  struct thread_result *result = xmalloc (sizeof (*result));
Packit 6c4009
  for (int i = 0; i < key_count; ++i)
Packit 6c4009
    result->access_rights[i] = pkey_get (keys[i]);
Packit 6c4009
  memset (&result->next_thread, 0, sizeof (result->next_thread));
Packit 6c4009
  return result;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Wait for initialization and then check that the current thread does
Packit 6c4009
   not have access through the keys under test.  */
Packit 6c4009
static void *
Packit 6c4009
delayed_thread_func (void *closure)
Packit 6c4009
{
Packit 6c4009
  bool check_access = *(bool *) closure;
Packit 6c4009
  pthread_barrier_wait (&barrier);
Packit 6c4009
  struct thread_result *result = get_thread_func (NULL);
Packit 6c4009
Packit 6c4009
  if (check_access)
Packit 6c4009
    {
Packit 6c4009
      /* Also check directly.  This code should not run with other
Packit 6c4009
         threads in parallel because of the SIGSEGV handler which is
Packit 6c4009
         installed by check_page_access.  */
Packit 6c4009
      for (int i = 0; i < key_count; ++i)
Packit 6c4009
        {
Packit 6c4009
          TEST_VERIFY (!check_page_access (i, false));
Packit 6c4009
          TEST_VERIFY (!check_page_access (i, true));
Packit 6c4009
        }
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  result->next_thread = xpthread_create (NULL, get_thread_func, NULL);
Packit 6c4009
  return result;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
do_test (void)
Packit 6c4009
{
Packit 6c4009
  long pagesize = xsysconf (_SC_PAGESIZE);
Packit 6c4009
Packit 6c4009
  /* pkey_mprotect with key -1 should work even when there is no
Packit 6c4009
     protection key support.  */
Packit 6c4009
  {
Packit 6c4009
    int *page = xmmap (NULL, pagesize, PROT_NONE,
Packit 6c4009
                       MAP_ANONYMOUS | MAP_PRIVATE, -1);
Packit 6c4009
    TEST_COMPARE (pkey_mprotect (page, pagesize, PROT_READ | PROT_WRITE, -1),
Packit 6c4009
                  0);
Packit 6c4009
    volatile int *vpage = page;
Packit 6c4009
    *vpage = 5;
Packit 6c4009
    TEST_COMPARE (*vpage, 5);
Packit 6c4009
    xmunmap (page, pagesize);
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  xpthread_barrier_init (&barrier, NULL, 2);
Packit 6c4009
  bool delayed_thread_check_access = true;
Packit 6c4009
  pthread_t delayed_thread = xpthread_create
Packit 6c4009
    (NULL, &delayed_thread_func, &delayed_thread_check_access);
Packit 6c4009
Packit 6c4009
  keys[0] = pkey_alloc (0, 0);
Packit 6c4009
  if (keys[0] < 0)
Packit 6c4009
    {
Packit 6c4009
      if (errno == ENOSYS)
Packit 6c4009
        FAIL_UNSUPPORTED
Packit 6c4009
          ("kernel does not support memory protection keys");
Packit 6c4009
      if (errno == EINVAL)
Packit 6c4009
        FAIL_UNSUPPORTED
Packit 6c4009
          ("CPU does not support memory protection keys: %m");
Packit 6c4009
      FAIL_EXIT1 ("pkey_alloc: %m");
Packit 6c4009
    }
Packit 6c4009
  TEST_COMPARE (pkey_get (keys[0]), 0);
Packit 6c4009
  for (int i = 1; i < key_count; ++i)
Packit 6c4009
    {
Packit 6c4009
      keys[i] = pkey_alloc (0, i);
Packit 6c4009
      if (keys[i] < 0)
Packit 6c4009
        FAIL_EXIT1 ("pkey_alloc (0, %d): %m", i);
Packit 6c4009
      /* pkey_alloc is supposed to change the current thread's access
Packit 6c4009
         rights for the new key.  */
Packit 6c4009
      TEST_COMPARE (pkey_get (keys[i]), i);
Packit 6c4009
    }
Packit 6c4009
  /* Check that all the keys have the expected access rights for the
Packit 6c4009
     current thread.  */
Packit 6c4009
  for (int i = 0; i < key_count; ++i)
Packit 6c4009
    TEST_COMPARE (pkey_get (keys[i]), i);
Packit 6c4009
Packit 6c4009
  /* Allocate a test page for each key.  */
Packit 6c4009
  for (int i = 0; i < key_count; ++i)
Packit 6c4009
    {
Packit 6c4009
      pages[i] = xmmap (NULL, pagesize, PROT_READ | PROT_WRITE,
Packit 6c4009
                        MAP_ANONYMOUS | MAP_PRIVATE, -1);
Packit 6c4009
      TEST_COMPARE (pkey_mprotect ((void *) pages[i], pagesize,
Packit 6c4009
                                   PROT_READ | PROT_WRITE, keys[i]), 0);
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Check that the initial thread does not have access to the new
Packit 6c4009
     keys.  */
Packit 6c4009
  {
Packit 6c4009
    pthread_barrier_wait (&barrier);
Packit 6c4009
    struct thread_result *result = xpthread_join (delayed_thread);
Packit 6c4009
    for (int i = 0; i < key_count; ++i)
Packit 6c4009
      TEST_COMPARE (result->access_rights[i],
Packit 6c4009
                    PKEY_DISABLE_ACCESS);
Packit 6c4009
    struct thread_result *result2 = xpthread_join (result->next_thread);
Packit 6c4009
    for (int i = 0; i < key_count; ++i)
Packit 6c4009
      TEST_COMPARE (result->access_rights[i],
Packit 6c4009
                    PKEY_DISABLE_ACCESS);
Packit 6c4009
    free (result);
Packit 6c4009
    free (result2);
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  /* Check that the current thread access rights are inherited by new
Packit 6c4009
     threads.  */
Packit 6c4009
  {
Packit 6c4009
    pthread_t get_thread = xpthread_create (NULL, get_thread_func, NULL);
Packit 6c4009
    struct thread_result *result = xpthread_join (get_thread);
Packit 6c4009
    for (int i = 0; i < key_count; ++i)
Packit 6c4009
      TEST_COMPARE (result->access_rights[i], i);
Packit 6c4009
    free (result);
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  for (int i = 0; i < key_count; ++i)
Packit 6c4009
    TEST_COMPARE (pkey_get (keys[i]), i);
Packit 6c4009
Packit 6c4009
  /* Check that in a signal handler, there is no access.  */
Packit 6c4009
  xsignal (SIGUSR1, &sigusr1_handler);
Packit 6c4009
  xraise (SIGUSR1);
Packit 6c4009
  xsignal (SIGUSR1, SIG_DFL);
Packit 6c4009
  TEST_COMPARE (sigusr1_handler_ran, 1);
Packit 6c4009
Packit 6c4009
  /* The first key results in a writable page.  */
Packit 6c4009
  TEST_VERIFY (check_page_access (0, false));
Packit 6c4009
  TEST_VERIFY (check_page_access (0, true));
Packit 6c4009
Packit 6c4009
  /* The other keys do not.   */
Packit 6c4009
  for (int i = 1; i < key_count; ++i)
Packit 6c4009
    {
Packit 6c4009
      if (test_verbose)
Packit 6c4009
        printf ("info: checking access for key %d, bits 0x%x\n",
Packit 6c4009
                i, pkey_get (keys[i]));
Packit 6c4009
      for (int j = 0; j < key_count; ++j)
Packit 6c4009
        TEST_COMPARE (pkey_get (keys[j]), j);
Packit 6c4009
      if (i & PKEY_DISABLE_ACCESS)
Packit 6c4009
        {
Packit 6c4009
          TEST_VERIFY (!check_page_access (i, false));
Packit 6c4009
          TEST_VERIFY (!check_page_access (i, true));
Packit 6c4009
        }
Packit 6c4009
      else
Packit 6c4009
        {
Packit 6c4009
          TEST_VERIFY (i & PKEY_DISABLE_WRITE);
Packit 6c4009
          TEST_VERIFY (check_page_access (i, false));
Packit 6c4009
          TEST_VERIFY (!check_page_access (i, true));
Packit 6c4009
        }
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* But if we set the current thread's access rights, we gain
Packit 6c4009
     access.  */
Packit 6c4009
  for (int do_write = 0; do_write < 2; ++do_write)
Packit 6c4009
    for (int allowed_key = 0; allowed_key < key_count; ++allowed_key)
Packit 6c4009
      {
Packit 6c4009
        for (int i = 0; i < key_count; ++i)
Packit 6c4009
          if (i == allowed_key)
Packit 6c4009
            {
Packit 6c4009
              if (do_write)
Packit 6c4009
                TEST_COMPARE (pkey_set (keys[i], 0), 0);
Packit 6c4009
              else
Packit 6c4009
                TEST_COMPARE (pkey_set (keys[i], PKEY_DISABLE_WRITE), 0);
Packit 6c4009
            }
Packit 6c4009
          else
Packit 6c4009
            TEST_COMPARE (pkey_set (keys[i], PKEY_DISABLE_ACCESS), 0);
Packit 6c4009
Packit 6c4009
        if (test_verbose)
Packit 6c4009
          printf ("info: key %d is allowed access for %s\n",
Packit 6c4009
                  allowed_key, do_write ? "writing" : "reading");
Packit 6c4009
        for (int i = 0; i < key_count; ++i)
Packit 6c4009
          if (i == allowed_key)
Packit 6c4009
            {
Packit 6c4009
              TEST_VERIFY (check_page_access (i, false));
Packit 6c4009
              TEST_VERIFY (check_page_access (i, true) == do_write);
Packit 6c4009
            }
Packit 6c4009
          else
Packit 6c4009
            {
Packit 6c4009
              TEST_VERIFY (!check_page_access (i, false));
Packit 6c4009
              TEST_VERIFY (!check_page_access (i, true));
Packit 6c4009
            }
Packit 6c4009
      }
Packit 6c4009
Packit 6c4009
  /* Restore access to all keys, and launch a thread which should
Packit 6c4009
     inherit that access.  */
Packit 6c4009
  for (int i = 0; i < key_count; ++i)
Packit 6c4009
    {
Packit 6c4009
      TEST_COMPARE (pkey_set (keys[i], 0), 0);
Packit 6c4009
      TEST_VERIFY (check_page_access (i, false));
Packit 6c4009
      TEST_VERIFY (check_page_access (i, true));
Packit 6c4009
    }
Packit 6c4009
  delayed_thread_check_access = false;
Packit 6c4009
  delayed_thread = xpthread_create
Packit 6c4009
    (NULL, delayed_thread_func, &delayed_thread_check_access);
Packit 6c4009
Packit 6c4009
  TEST_COMPARE (pkey_free (keys[0]), 0);
Packit 6c4009
  /* Second pkey_free will fail because the key has already been
Packit 6c4009
     freed.  */
Packit 6c4009
  TEST_COMPARE (pkey_free (keys[0]),-1);
Packit 6c4009
  TEST_COMPARE (errno, EINVAL);
Packit 6c4009
  for (int i = 1; i < key_count; ++i)
Packit 6c4009
    TEST_COMPARE (pkey_free (keys[i]), 0);
Packit 6c4009
Packit 6c4009
  /* Check what happens to running threads which have access to
Packit 6c4009
     previously allocated protection keys.  The implemented behavior
Packit 6c4009
     is somewhat dubious: Ideally, pkey_free should revoke access to
Packit 6c4009
     that key and pkey_alloc of the same (numeric) key should not
Packit 6c4009
     implicitly confer access to already-running threads, but this is
Packit 6c4009
     not what happens in practice.  */
Packit 6c4009
  {
Packit 6c4009
    /* The limit is in place to avoid running indefinitely in case
Packit 6c4009
       there many keys available.  */
Packit 6c4009
    int *keys_array = xcalloc (100000, sizeof (*keys_array));
Packit 6c4009
    int keys_allocated = 0;
Packit 6c4009
    while (keys_allocated < 100000)
Packit 6c4009
      {
Packit 6c4009
        int new_key = pkey_alloc (0, PKEY_DISABLE_WRITE);
Packit 6c4009
        if (new_key < 0)
Packit 6c4009
          {
Packit 6c4009
            /* No key reuse observed before running out of keys.  */
Packit 6c4009
            TEST_COMPARE (errno, ENOSPC);
Packit 6c4009
            break;
Packit 6c4009
          }
Packit 6c4009
        for (int i = 0; i < key_count; ++i)
Packit 6c4009
          if (new_key == keys[i])
Packit 6c4009
            {
Packit 6c4009
              /* We allocated the key with disabled write access.
Packit 6c4009
                 This should affect the protection state of the
Packit 6c4009
                 existing page.  */
Packit 6c4009
              TEST_VERIFY (check_page_access (i, false));
Packit 6c4009
              TEST_VERIFY (!check_page_access (i, true));
Packit 6c4009
Packit 6c4009
              xpthread_barrier_wait (&barrier);
Packit 6c4009
              struct thread_result *result = xpthread_join (delayed_thread);
Packit 6c4009
              /* The thread which was launched before should still have
Packit 6c4009
                 access to the key.  */
Packit 6c4009
              TEST_COMPARE (result->access_rights[i], 0);
Packit 6c4009
              struct thread_result *result2
Packit 6c4009
                = xpthread_join (result->next_thread);
Packit 6c4009
              /* Same for a thread which is launched afterwards from
Packit 6c4009
                 the old thread.  */
Packit 6c4009
              TEST_COMPARE (result2->access_rights[i], 0);
Packit 6c4009
              free (result);
Packit 6c4009
              free (result2);
Packit 6c4009
              keys_array[keys_allocated++] = new_key;
Packit 6c4009
              goto after_key_search;
Packit 6c4009
            }
Packit 6c4009
        /* Save key for later deallocation.  */
Packit 6c4009
        keys_array[keys_allocated++] = new_key;
Packit 6c4009
      }
Packit 6c4009
  after_key_search:
Packit 6c4009
    /* Deallocate the keys allocated for testing purposes.  */
Packit 6c4009
    for (int j = 0; j < keys_allocated; ++j)
Packit 6c4009
      TEST_COMPARE (pkey_free (keys_array[j]), 0);
Packit 6c4009
    free (keys_array);
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  for (int i = 0; i < key_count; ++i)
Packit 6c4009
    xmunmap ((void *) pages[i], pagesize);
Packit 6c4009
Packit 6c4009
  xpthread_barrier_destroy (&barrier);
Packit 6c4009
  return 0;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
#include <support/test-driver.c>