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

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