|
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>
|