978e96
commit 68448be208ee06e76665918b37b0a57e3e00c8b4
978e96
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
978e96
Date:   Fri Nov 17 16:04:29 2017 -0200
978e96
978e96
    i386: Fix i386 sigaction sa_restorer initialization (BZ#21269)
978e96
    
978e96
    This patch fixes the i386 sa_restorer field initialization for sigaction
978e96
    syscall for kernel with vDSO.  As described in bug report, i386 Linux
978e96
    (and compat on x86_64) interprets SA_RESTORER clear with nonzero
978e96
    sa_restorer as a request for stack switching if the SS segment is 'funny'.
978e96
    This means that anything that tries to mix glibc's signal handling with
978e96
    segmentation (for instance through modify_ldt syscall) is randomly broken
978e96
    depending on what values lands in sa_restorer.
978e96
    
978e96
    The testcase added  is based on Linux test tools/testing/selftests/x86/ldt_gdt.c,
978e96
    more specifically in do_multicpu_tests function.  The main changes are:
978e96
    
978e96
      - C11 atomics instead of plain access.
978e96
    
978e96
      - Remove x86_64 support which simplifies the syscall handling and fallbacks.
978e96
    
978e96
      - Replicate only the test required to trigger the issue.
978e96
    
978e96
    Checked on i686-linux-gnu.
978e96
    
978e96
            [BZ #21269]
978e96
            * sysdeps/unix/sysv/linux/i386/Makefile (tests): Add tst-bz21269.
978e96
            * sysdeps/unix/sysv/linux/i386/sigaction.c (SET_SA_RESTORER): Clear
978e96
            sa_restorer for vDSO case.
978e96
            * sysdeps/unix/sysv/linux/i386/tst-bz21269.c: New file.
978e96
978e96
(Adjusted for conflicted in sysdeps/unix/sysv/linux/i386/Makefile due
978e96
different context around the addition of the new test.)
978e96
978e96
diff --git a/sysdeps/unix/sysv/linux/i386/Makefile b/sysdeps/unix/sysv/linux/i386/Makefile
978e96
index acc30219e8dc965f..78e2101682d8d996 100644
978e96
--- a/sysdeps/unix/sysv/linux/i386/Makefile
978e96
+++ b/sysdeps/unix/sysv/linux/i386/Makefile
978e96
@@ -3,6 +3,9 @@ default-abi := 32
978e96
 
978e96
 ifeq ($(subdir),misc)
978e96
 sysdep_routines += ioperm iopl vm86 call_pselect6 call_fallocate
978e96
+
978e96
+tests += tst-bz21269
978e96
+$(objpfx)tst-bz21269: $(shared-thread-library)
978e96
 endif
978e96
 
978e96
 ifeq ($(subdir),elf)
978e96
diff --git a/sysdeps/unix/sysv/linux/i386/sigaction.c b/sysdeps/unix/sysv/linux/i386/sigaction.c
978e96
index 414ef759a97363c4..f10e1363865c3d18 100644
978e96
--- a/sysdeps/unix/sysv/linux/i386/sigaction.c
978e96
+++ b/sysdeps/unix/sysv/linux/i386/sigaction.c
978e96
@@ -44,7 +44,6 @@ extern void restore_rt (void) asm ("__restore_rt") attribute_hidden;
978e96
 #endif
978e96
 extern void restore (void) asm ("__restore") attribute_hidden;
978e96
 
978e96
-
978e96
 /* If ACT is not NULL, change the action for SIG to *ACT.
978e96
    If OACT is not NULL, put the old action for SIG in *OACT.  */
978e96
 int
978e96
@@ -67,6 +66,8 @@ __libc_sigaction (int sig, const struct sigaction *act, struct sigaction *oact)
978e96
 	  kact.sa_restorer = ((act->sa_flags & SA_SIGINFO)
978e96
 			      ? &restore_rt : &restore);
978e96
 	}
978e96
+      else
978e96
+	kact.sa_restorer = NULL;
978e96
     }
978e96
 
978e96
   /* XXX The size argument hopefully will have to be changed to the
978e96
diff --git a/sysdeps/unix/sysv/linux/i386/tst-bz21269.c b/sysdeps/unix/sysv/linux/i386/tst-bz21269.c
978e96
new file mode 100644
978e96
index 0000000000000000..353e36507dce92ea
978e96
--- /dev/null
978e96
+++ b/sysdeps/unix/sysv/linux/i386/tst-bz21269.c
978e96
@@ -0,0 +1,233 @@
978e96
+/* Test for i386 sigaction sa_restorer handling (BZ#21269)
978e96
+   Copyright (C) 2017 Free Software Foundation, Inc.
978e96
+   This file is part of the GNU C Library.
978e96
+
978e96
+   The GNU C Library is free software; you can redistribute it and/or
978e96
+   modify it under the terms of the GNU Lesser General Public
978e96
+   License as published by the Free Software Foundation; either
978e96
+   version 2.1 of the License, or (at your option) any later version.
978e96
+
978e96
+   The GNU C Library is distributed in the hope that it will be useful,
978e96
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
978e96
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
978e96
+   Lesser General Public License for more details.
978e96
+
978e96
+   You should have received a copy of the GNU Lesser General Public
978e96
+   License along with the GNU C Library; if not, see
978e96
+   <http://www.gnu.org/licenses/>.  */
978e96
+
978e96
+/* This is based on Linux test tools/testing/selftests/x86/ldt_gdt.c,
978e96
+   more specifically in do_multicpu_tests function.  The main changes
978e96
+   are:
978e96
+
978e96
+   - C11 atomics instead of plain access.
978e96
+   - Remove x86_64 support which simplifies the syscall handling
978e96
+     and fallbacks.
978e96
+   - Replicate only the test required to trigger the issue for the
978e96
+     BZ#21269.  */
978e96
+
978e96
+#include <stdatomic.h>
978e96
+
978e96
+#include <asm/ldt.h>
978e96
+#include <linux/futex.h>
978e96
+
978e96
+#include <setjmp.h>
978e96
+#include <signal.h>
978e96
+#include <errno.h>
978e96
+#include <sys/syscall.h>
978e96
+#include <sys/mman.h>
978e96
+
978e96
+#include <support/xunistd.h>
978e96
+#include <support/check.h>
978e96
+#include <support/xthread.h>
978e96
+
978e96
+static int
978e96
+xset_thread_area (struct user_desc *u_info)
978e96
+{
978e96
+  long ret = syscall (SYS_set_thread_area, u_info);
978e96
+  TEST_VERIFY_EXIT (ret == 0);
978e96
+  return ret;
978e96
+}
978e96
+
978e96
+static void
978e96
+xmodify_ldt (int func, const void *ptr, unsigned long bytecount)
978e96
+{
978e96
+  TEST_VERIFY_EXIT (syscall (SYS_modify_ldt, 1, ptr, bytecount) == 0);
978e96
+}
978e96
+
978e96
+static int
978e96
+futex (int *uaddr, int futex_op, int val, void *timeout, int *uaddr2,
978e96
+	int val3)
978e96
+{
978e96
+  return syscall (SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3);
978e96
+}
978e96
+
978e96
+static void
978e96
+xsethandler (int sig, void (*handler)(int, siginfo_t *, void *), int flags)
978e96
+{
978e96
+  struct sigaction sa = { 0 };
978e96
+  sa.sa_sigaction = handler;
978e96
+  sa.sa_flags = SA_SIGINFO | flags;
978e96
+  TEST_VERIFY_EXIT (sigemptyset (&sa.sa_mask) == 0);
978e96
+  TEST_VERIFY_EXIT (sigaction (sig, &sa, 0) == 0);
978e96
+}
978e96
+
978e96
+static jmp_buf jmpbuf;
978e96
+
978e96
+static void
978e96
+sigsegv_handler (int sig, siginfo_t *info, void *ctx_void)
978e96
+{
978e96
+  siglongjmp (jmpbuf, 1);
978e96
+}
978e96
+
978e96
+/* Points to an array of 1024 ints, each holding its own index.  */
978e96
+static const unsigned int *counter_page;
978e96
+static struct user_desc *low_user_desc;
978e96
+static struct user_desc *low_user_desc_clear; /* Used to delete GDT entry.  */
978e96
+static int gdt_entry_num;
978e96
+
978e96
+static void
978e96
+setup_counter_page (void)
978e96
+{
978e96
+  long page_size = sysconf (_SC_PAGE_SIZE);
978e96
+  TEST_VERIFY_EXIT (page_size > 0);
978e96
+  unsigned int *page = xmmap (NULL, page_size, PROT_READ | PROT_WRITE,
978e96
+			      MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1);
978e96
+  for (int i = 0; i < (page_size / sizeof (unsigned int)); i++)
978e96
+    page[i] = i;
978e96
+  counter_page = page;
978e96
+}
978e96
+
978e96
+static void
978e96
+setup_low_user_desc (void)
978e96
+{
978e96
+  low_user_desc = xmmap (NULL, 2 * sizeof (struct user_desc),
978e96
+			 PROT_READ | PROT_WRITE,
978e96
+			 MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1);
978e96
+
978e96
+  low_user_desc->entry_number    = -1;
978e96
+  low_user_desc->base_addr       = (unsigned long) &counter_page[1];
978e96
+  low_user_desc->limit           = 0xffff;
978e96
+  low_user_desc->seg_32bit       = 1;
978e96
+  low_user_desc->contents        = 0;
978e96
+  low_user_desc->read_exec_only  = 0;
978e96
+  low_user_desc->limit_in_pages  = 1;
978e96
+  low_user_desc->seg_not_present = 0;
978e96
+  low_user_desc->useable         = 0;
978e96
+
978e96
+  xset_thread_area (low_user_desc);
978e96
+
978e96
+  low_user_desc_clear = low_user_desc + 1;
978e96
+  low_user_desc_clear->entry_number = gdt_entry_num;
978e96
+  low_user_desc_clear->read_exec_only = 1;
978e96
+  low_user_desc_clear->seg_not_present = 1;
978e96
+}
978e96
+
978e96
+/* Possible values of futex:
978e96
+   0: thread is idle.
978e96
+   1: thread armed.
978e96
+   2: thread should clear LDT entry 0.
978e96
+   3: thread should exit.  */
978e96
+static atomic_uint ftx;
978e96
+
978e96
+static void *
978e96
+threadproc (void *ctx)
978e96
+{
978e96
+  while (1)
978e96
+    {
978e96
+      futex ((int *) &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
978e96
+      while (atomic_load (&ftx) != 2)
978e96
+	{
978e96
+	  if (atomic_load (&ftx) >= 3)
978e96
+	    return NULL;
978e96
+	}
978e96
+
978e96
+      /* clear LDT entry 0.  */
978e96
+      const struct user_desc desc = { 0 };
978e96
+      xmodify_ldt (1, &desc, sizeof (desc));
978e96
+
978e96
+      /* If ftx == 2, set it to zero,  If ftx == 100, quit.  */
978e96
+      if (atomic_fetch_add (&ftx, -2) != 2)
978e96
+	return NULL;
978e96
+    }
978e96
+}
978e96
+
978e96
+
978e96
+/* As described in testcase, for historical reasons x86_32 Linux (and compat
978e96
+   on x86_64) interprets SA_RESTORER clear with nonzero sa_restorer as a
978e96
+   request for stack switching if the SS segment is 'funny' (this is default
978e96
+   scenario for vDSO system).  This means that anything that tries to mix
978e96
+   signal handling with segmentation should explicit clear the sa_restorer.
978e96
+
978e96
+   This testcase check if sigaction in fact does it by changing the local
978e96
+   descriptor table (LDT) through the modify_ldt syscall and triggering
978e96
+   a synchronous segfault on iret fault by trying to install an invalid
978e96
+   segment.  With a correct zeroed sa_restorer it should not trigger an
978e96
+   'real' SEGSEGV and allows the siglongjmp in signal handler.  */
978e96
+
978e96
+static int
978e96
+do_test (void)
978e96
+{
978e96
+  setup_counter_page ();
978e96
+  setup_low_user_desc ();
978e96
+
978e96
+  pthread_t thread;
978e96
+  unsigned short orig_ss;
978e96
+
978e96
+  xsethandler (SIGSEGV, sigsegv_handler, 0);
978e96
+  /* 32-bit kernels send SIGILL instead of SIGSEGV on IRET faults.  */
978e96
+  xsethandler (SIGILL, sigsegv_handler, 0);
978e96
+
978e96
+  thread = xpthread_create (0, threadproc, 0);
978e96
+
978e96
+  asm volatile ("mov %%ss, %0" : "=rm" (orig_ss));
978e96
+
978e96
+  for (int i = 0; i < 5; i++)
978e96
+    {
978e96
+      if (sigsetjmp (jmpbuf, 1) != 0)
978e96
+	continue;
978e96
+
978e96
+      /* Make sure the thread is ready after the last test. */
978e96
+      while (atomic_load (&ftx) != 0)
978e96
+	;
978e96
+
978e96
+      struct user_desc desc = {
978e96
+	.entry_number       = 0,
978e96
+	.base_addr          = 0,
978e96
+	.limit              = 0xffff,
978e96
+	.seg_32bit          = 1,
978e96
+	.contents           = 0,
978e96
+	.read_exec_only     = 0,
978e96
+	.limit_in_pages     = 1,
978e96
+	.seg_not_present    = 0,
978e96
+	.useable            = 0
978e96
+      };
978e96
+
978e96
+      xmodify_ldt (0x11, &desc, sizeof (desc));
978e96
+
978e96
+      /* Arm the thread.  */
978e96
+      ftx = 1;
978e96
+      futex ((int*) &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
978e96
+
978e96
+      asm volatile ("mov %0, %%ss" : : "r" (0x7));
978e96
+
978e96
+      /* Fire up thread modify_ldt call.  */
978e96
+      atomic_store (&ftx, 2);
978e96
+
978e96
+      while (atomic_load (&ftx) != 0)
978e96
+	;
978e96
+
978e96
+      /* On success, modify_ldt will segfault us synchronously and we will
978e96
+	 escape via siglongjmp.  */
978e96
+      support_record_failure ();
978e96
+    }
978e96
+
978e96
+  atomic_store (&ftx, 100);
978e96
+  futex ((int*) &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
978e96
+
978e96
+  xpthread_join (thread);
978e96
+
978e96
+  return 0;
978e96
+}
978e96
+
978e96
+#include <support/test-driver.c>