diff options
Diffstat (limited to 'tools/testing/selftests/x86/single_step_syscall.c')
| -rw-r--r-- | tools/testing/selftests/x86/single_step_syscall.c | 121 |
1 files changed, 77 insertions, 44 deletions
diff --git a/tools/testing/selftests/x86/single_step_syscall.c b/tools/testing/selftests/x86/single_step_syscall.c index ddfdd635de16..280d7a22b9c9 100644 --- a/tools/testing/selftests/x86/single_step_syscall.c +++ b/tools/testing/selftests/x86/single_step_syscall.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * single_step_syscall.c - single-steps various x86 syscalls * Copyright (c) 2014-2015 Andrew Lutomirski * - * This program is free software; you can redistribute it and/or modify - * it under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * * This is a very simple series of tests that makes system calls with * the TF flag set. This exercises some nasty kernel code in the * SYSENTER case: SYSENTER does not clear TF, so SYSENTER with TF set @@ -39,19 +31,10 @@ #include <sys/ptrace.h> #include <sys/user.h> -static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), - int flags) -{ - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_sigaction = handler; - sa.sa_flags = SA_SIGINFO | flags; - sigemptyset(&sa.sa_mask); - if (sigaction(sig, &sa, 0)) - err(1, "sigaction"); -} +#include "helpers.h" -static volatile sig_atomic_t sig_traps; +static volatile sig_atomic_t sig_traps, sig_eflags; +sigjmp_buf jmpbuf; #ifdef __x86_64__ # define REG_IP REG_RIP @@ -63,21 +46,6 @@ static volatile sig_atomic_t sig_traps; # define INT80_CLOBBERS #endif -static unsigned long get_eflags(void) -{ - unsigned long eflags; - asm volatile ("pushf" WIDTH "\n\tpop" WIDTH " %0" : "=rm" (eflags)); - return eflags; -} - -static void set_eflags(unsigned long eflags) -{ - asm volatile ("push" WIDTH " %0\n\tpopf" WIDTH - : : "rm" (eflags) : "flags"); -} - -#define X86_EFLAGS_TF (1UL << 8) - static void sigtrap(int sig, siginfo_t *info, void *ctx_void) { ucontext_t *ctx = (ucontext_t*)ctx_void; @@ -98,6 +66,25 @@ static void sigtrap(int sig, siginfo_t *info, void *ctx_void) } } +static char const * const signames[] = { + [SIGSEGV] = "SIGSEGV", + [SIGBUS] = "SIBGUS", + [SIGTRAP] = "SIGTRAP", + [SIGILL] = "SIGILL", +}; + +static void print_and_longjmp(int sig, siginfo_t *si, void *ctx_void) +{ + ucontext_t *ctx = ctx_void; + + printf("\tGot %s with RIP=%lx, TF=%ld\n", signames[sig], + (unsigned long)ctx->uc_mcontext.gregs[REG_IP], + (unsigned long)ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_TF); + + sig_eflags = (unsigned long)ctx->uc_mcontext.gregs[REG_EFL]; + siglongjmp(jmpbuf, 1); +} + static void check_result(void) { unsigned long new_eflags = get_eflags(); @@ -117,6 +104,22 @@ static void check_result(void) sig_traps = 0; } +static void fast_syscall_no_tf(void) +{ + sig_traps = 0; + printf("[RUN]\tFast syscall with TF cleared\n"); + fflush(stdout); /* Force a syscall */ + if (get_eflags() & X86_EFLAGS_TF) { + printf("[FAIL]\tTF is now set\n"); + exit(1); + } + if (sig_traps) { + printf("[FAIL]\tGot SIGTRAP\n"); + exit(1); + } + printf("[OK]\tNothing unexpected happened\n"); +} + int main() { #ifdef CAN_BUILD_32 @@ -171,17 +174,47 @@ int main() check_result(); /* Now make sure that another fast syscall doesn't set TF again. */ - printf("[RUN]\tFast syscall with TF cleared\n"); - fflush(stdout); /* Force a syscall */ - if (get_eflags() & X86_EFLAGS_TF) { - printf("[FAIL]\tTF is now set\n"); - exit(1); + fast_syscall_no_tf(); + + /* + * And do a forced SYSENTER to make sure that this works even if + * fast syscalls don't use SYSENTER. + * + * Invoking SYSENTER directly breaks all the rules. Just handle + * the SIGSEGV. + */ + if (sigsetjmp(jmpbuf, 1) == 0) { + unsigned long nr = SYS_getpid; + printf("[RUN]\tSet TF and check SYSENTER\n"); + stack_t stack = { + .ss_sp = malloc(sizeof(char) * SIGSTKSZ), + .ss_size = SIGSTKSZ, + }; + if (sigaltstack(&stack, NULL) != 0) + err(1, "sigaltstack"); + sethandler(SIGSEGV, print_and_longjmp, + SA_RESETHAND | SA_ONSTACK); + sethandler(SIGILL, print_and_longjmp, SA_RESETHAND); + set_eflags(get_eflags() | X86_EFLAGS_TF); + free(stack.ss_sp); + /* Clear EBP first to make sure we segfault cleanly. */ + asm volatile ("xorl %%ebp, %%ebp; SYSENTER" : "+a" (nr) :: "flags", "rcx" +#ifdef __x86_64__ + , "r11" +#endif + ); + + /* We're unreachable here. SYSENTER forgets RIP. */ } - if (sig_traps) { - printf("[FAIL]\tGot SIGTRAP\n"); + clearhandler(SIGSEGV); + clearhandler(SIGILL); + if (!(sig_eflags & X86_EFLAGS_TF)) { + printf("[FAIL]\tTF was cleared\n"); exit(1); } - printf("[OK]\tNothing unexpected happened\n"); + + /* Now make sure that another fast syscall doesn't set TF again. */ + fast_syscall_no_tf(); return 0; } |
