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