Blame sysdeps/unix/sysv/linux/i386/tst-bz21269.c

Packit 6c4009
/* Test for i386 sigaction sa_restorer handling (BZ#21269)
Packit 6c4009
   Copyright (C) 2017 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
/* This is based on Linux test tools/testing/selftests/x86/ldt_gdt.c,
Packit 6c4009
   more specifically in do_multicpu_tests function.  The main changes
Packit 6c4009
   are:
Packit 6c4009
Packit 6c4009
   - C11 atomics instead of plain access.
Packit 6c4009
   - Remove x86_64 support which simplifies the syscall handling
Packit 6c4009
     and fallbacks.
Packit 6c4009
   - Replicate only the test required to trigger the issue for the
Packit 6c4009
     BZ#21269.  */
Packit 6c4009
Packit 6c4009
#include <stdatomic.h>
Packit 6c4009
Packit 6c4009
#include <asm/ldt.h>
Packit 6c4009
#include <linux/futex.h>
Packit 6c4009
Packit 6c4009
#include <setjmp.h>
Packit 6c4009
#include <signal.h>
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include <sys/syscall.h>
Packit 6c4009
#include <sys/mman.h>
Packit 6c4009
Packit 6c4009
#include <support/xunistd.h>
Packit 6c4009
#include <support/check.h>
Packit 6c4009
#include <support/xthread.h>
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
xset_thread_area (struct user_desc *u_info)
Packit 6c4009
{
Packit 6c4009
  long ret = syscall (SYS_set_thread_area, u_info);
Packit 6c4009
  TEST_VERIFY_EXIT (ret == 0);
Packit 6c4009
  return ret;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
xmodify_ldt (int func, const void *ptr, unsigned long bytecount)
Packit 6c4009
{
Packit 6c4009
  TEST_VERIFY_EXIT (syscall (SYS_modify_ldt, 1, ptr, bytecount) == 0);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
futex (int *uaddr, int futex_op, int val, void *timeout, int *uaddr2,
Packit 6c4009
	int val3)
Packit 6c4009
{
Packit 6c4009
  return syscall (SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
xsethandler (int sig, void (*handler)(int, siginfo_t *, void *), int flags)
Packit 6c4009
{
Packit 6c4009
  struct sigaction sa = { 0 };
Packit 6c4009
  sa.sa_sigaction = handler;
Packit 6c4009
  sa.sa_flags = SA_SIGINFO | flags;
Packit 6c4009
  TEST_VERIFY_EXIT (sigemptyset (&sa.sa_mask) == 0);
Packit 6c4009
  TEST_VERIFY_EXIT (sigaction (sig, &sa, 0) == 0);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static jmp_buf jmpbuf;
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
sigsegv_handler (int sig, siginfo_t *info, void *ctx_void)
Packit 6c4009
{
Packit 6c4009
  siglongjmp (jmpbuf, 1);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Points to an array of 1024 ints, each holding its own index.  */
Packit 6c4009
static const unsigned int *counter_page;
Packit 6c4009
static struct user_desc *low_user_desc;
Packit 6c4009
static struct user_desc *low_user_desc_clear; /* Used to delete GDT entry.  */
Packit 6c4009
static int gdt_entry_num;
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
setup_counter_page (void)
Packit 6c4009
{
Packit 6c4009
  long page_size = sysconf (_SC_PAGE_SIZE);
Packit 6c4009
  TEST_VERIFY_EXIT (page_size > 0);
Packit 6c4009
  unsigned int *page = xmmap (NULL, page_size, PROT_READ | PROT_WRITE,
Packit 6c4009
			      MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1);
Packit 6c4009
  for (int i = 0; i < (page_size / sizeof (unsigned int)); i++)
Packit 6c4009
    page[i] = i;
Packit 6c4009
  counter_page = page;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
setup_low_user_desc (void)
Packit 6c4009
{
Packit 6c4009
  low_user_desc = xmmap (NULL, 2 * sizeof (struct user_desc),
Packit 6c4009
			 PROT_READ | PROT_WRITE,
Packit 6c4009
			 MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1);
Packit 6c4009
Packit 6c4009
  low_user_desc->entry_number    = -1;
Packit 6c4009
  low_user_desc->base_addr       = (unsigned long) &counter_page[1];
Packit 6c4009
  low_user_desc->limit           = 0xffff;
Packit 6c4009
  low_user_desc->seg_32bit       = 1;
Packit 6c4009
  low_user_desc->contents        = 0;
Packit 6c4009
  low_user_desc->read_exec_only  = 0;
Packit 6c4009
  low_user_desc->limit_in_pages  = 1;
Packit 6c4009
  low_user_desc->seg_not_present = 0;
Packit 6c4009
  low_user_desc->useable         = 0;
Packit 6c4009
Packit 6c4009
  xset_thread_area (low_user_desc);
Packit 6c4009
Packit 6c4009
  low_user_desc_clear = low_user_desc + 1;
Packit 6c4009
  low_user_desc_clear->entry_number = gdt_entry_num;
Packit 6c4009
  low_user_desc_clear->read_exec_only = 1;
Packit 6c4009
  low_user_desc_clear->seg_not_present = 1;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Possible values of futex:
Packit 6c4009
   0: thread is idle.
Packit 6c4009
   1: thread armed.
Packit 6c4009
   2: thread should clear LDT entry 0.
Packit 6c4009
   3: thread should exit.  */
Packit 6c4009
static atomic_uint ftx;
Packit 6c4009
Packit 6c4009
static void *
Packit 6c4009
threadproc (void *ctx)
Packit 6c4009
{
Packit 6c4009
  while (1)
Packit 6c4009
    {
Packit 6c4009
      futex ((int *) &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
Packit 6c4009
      while (atomic_load (&ftx) != 2)
Packit 6c4009
	{
Packit 6c4009
	  if (atomic_load (&ftx) >= 3)
Packit 6c4009
	    return NULL;
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
      /* clear LDT entry 0.  */
Packit 6c4009
      const struct user_desc desc = { 0 };
Packit 6c4009
      xmodify_ldt (1, &desc, sizeof (desc));
Packit 6c4009
Packit 6c4009
      /* If ftx == 2, set it to zero,  If ftx == 100, quit.  */
Packit 6c4009
      if (atomic_fetch_add (&ftx, -2) != 2)
Packit 6c4009
	return NULL;
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* As described in testcase, for historical reasons x86_32 Linux (and compat
Packit 6c4009
   on x86_64) interprets SA_RESTORER clear with nonzero sa_restorer as a
Packit 6c4009
   request for stack switching if the SS segment is 'funny' (this is default
Packit 6c4009
   scenario for vDSO system).  This means that anything that tries to mix
Packit 6c4009
   signal handling with segmentation should explicit clear the sa_restorer.
Packit 6c4009
Packit 6c4009
   This testcase check if sigaction in fact does it by changing the local
Packit 6c4009
   descriptor table (LDT) through the modify_ldt syscall and triggering
Packit 6c4009
   a synchronous segfault on iret fault by trying to install an invalid
Packit 6c4009
   segment.  With a correct zeroed sa_restorer it should not trigger an
Packit 6c4009
   'real' SEGSEGV and allows the siglongjmp in signal handler.  */
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
do_test (void)
Packit 6c4009
{
Packit 6c4009
  setup_counter_page ();
Packit 6c4009
  setup_low_user_desc ();
Packit 6c4009
Packit 6c4009
  pthread_t thread;
Packit 6c4009
  unsigned short orig_ss;
Packit 6c4009
Packit 6c4009
  xsethandler (SIGSEGV, sigsegv_handler, 0);
Packit 6c4009
  /* 32-bit kernels send SIGILL instead of SIGSEGV on IRET faults.  */
Packit 6c4009
  xsethandler (SIGILL, sigsegv_handler, 0);
Packit 6c4009
  /* Some kernels send SIGBUS instead.  */
Packit 6c4009
  xsethandler (SIGBUS, sigsegv_handler, 0);
Packit 6c4009
Packit 6c4009
  thread = xpthread_create (0, threadproc, 0);
Packit 6c4009
Packit 6c4009
  asm volatile ("mov %%ss, %0" : "=rm" (orig_ss));
Packit 6c4009
Packit 6c4009
  for (int i = 0; i < 5; i++)
Packit 6c4009
    {
Packit 6c4009
      if (sigsetjmp (jmpbuf, 1) != 0)
Packit 6c4009
	continue;
Packit 6c4009
Packit 6c4009
      /* Make sure the thread is ready after the last test. */
Packit 6c4009
      while (atomic_load (&ftx) != 0)
Packit 6c4009
	;
Packit 6c4009
Packit 6c4009
      struct user_desc desc = {
Packit 6c4009
	.entry_number       = 0,
Packit 6c4009
	.base_addr          = 0,
Packit 6c4009
	.limit              = 0xffff,
Packit 6c4009
	.seg_32bit          = 1,
Packit 6c4009
	.contents           = 0,
Packit 6c4009
	.read_exec_only     = 0,
Packit 6c4009
	.limit_in_pages     = 1,
Packit 6c4009
	.seg_not_present    = 0,
Packit 6c4009
	.useable            = 0
Packit 6c4009
      };
Packit 6c4009
Packit 6c4009
      xmodify_ldt (0x11, &desc, sizeof (desc));
Packit 6c4009
Packit 6c4009
      /* Arm the thread.  */
Packit 6c4009
      ftx = 1;
Packit 6c4009
      futex ((int*) &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
Packit 6c4009
Packit 6c4009
      asm volatile ("mov %0, %%ss" : : "r" (0x7));
Packit 6c4009
Packit 6c4009
      /* Fire up thread modify_ldt call.  */
Packit 6c4009
      atomic_store (&ftx, 2);
Packit 6c4009
Packit 6c4009
      while (atomic_load (&ftx) != 0)
Packit 6c4009
	;
Packit 6c4009
Packit 6c4009
      /* On success, modify_ldt will segfault us synchronously and we will
Packit 6c4009
	 escape via siglongjmp.  */
Packit 6c4009
      support_record_failure ();
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  atomic_store (&ftx, 100);
Packit 6c4009
  futex ((int*) &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
Packit 6c4009
Packit 6c4009
  xpthread_join (thread);
Packit 6c4009
Packit 6c4009
  return 0;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
#include <support/test-driver.c>