diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/testing/selftests/x86/Makefile | 17 | ||||
-rw-r--r-- | tools/testing/selftests/x86/check_initial_reg_state.c | 109 | ||||
-rw-r--r-- | tools/testing/selftests/x86/ptrace_syscall.c | 132 | ||||
-rw-r--r-- | tools/testing/selftests/x86/sigreturn.c | 230 |
4 files changed, 452 insertions, 36 deletions
diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile index d0c473f65850..d5ce7d7aae3e 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile @@ -4,15 +4,16 @@ include ../lib.mk .PHONY: all all_32 all_64 warn_32bit_failure clean -TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_syscall -TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault sigreturn test_syscall_vdso unwind_vdso \ +TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_syscall \ + check_initial_reg_state sigreturn ldt_gdt +TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \ test_FCMOV test_FCOMI test_FISTTP \ - ldt_gdt \ vdso_restorer TARGETS_C_32BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_32BIT_ONLY) +TARGETS_C_64BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_64BIT_ONLY) BINARIES_32 := $(TARGETS_C_32BIT_ALL:%=%_32) -BINARIES_64 := $(TARGETS_C_BOTHBITS:%=%_64) +BINARIES_64 := $(TARGETS_C_64BIT_ALL:%=%_64) CFLAGS := -O2 -g -std=gnu99 -pthread -Wall @@ -40,7 +41,7 @@ clean: $(TARGETS_C_32BIT_ALL:%=%_32): %_32: %.c $(CC) -m32 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $^ -lrt -ldl -lm -$(TARGETS_C_BOTHBITS:%=%_64): %_64: %.c +$(TARGETS_C_64BIT_ALL:%=%_64): %_64: %.c $(CC) -m64 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $^ -lrt -ldl # x86_64 users should be encouraged to install 32-bit libraries @@ -65,3 +66,9 @@ endif sysret_ss_attrs_64: thunks.S ptrace_syscall_32: raw_syscall_helper_32.S test_syscall_vdso_32: thunks_32.S + +# check_initial_reg_state is special: it needs a custom entry, and it +# needs to be static so that its interpreter doesn't destroy its initial +# state. +check_initial_reg_state_32: CFLAGS += -Wl,-ereal_start -static +check_initial_reg_state_64: CFLAGS += -Wl,-ereal_start -static diff --git a/tools/testing/selftests/x86/check_initial_reg_state.c b/tools/testing/selftests/x86/check_initial_reg_state.c new file mode 100644 index 000000000000..6aaed9b85baf --- /dev/null +++ b/tools/testing/selftests/x86/check_initial_reg_state.c @@ -0,0 +1,109 @@ +/* + * check_initial_reg_state.c - check that execve sets the correct state + * Copyright (c) 2014-2016 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. + */ + +#define _GNU_SOURCE + +#include <stdio.h> + +unsigned long ax, bx, cx, dx, si, di, bp, sp, flags; +unsigned long r8, r9, r10, r11, r12, r13, r14, r15; + +asm ( + ".pushsection .text\n\t" + ".type real_start, @function\n\t" + ".global real_start\n\t" + "real_start:\n\t" +#ifdef __x86_64__ + "mov %rax, ax\n\t" + "mov %rbx, bx\n\t" + "mov %rcx, cx\n\t" + "mov %rdx, dx\n\t" + "mov %rsi, si\n\t" + "mov %rdi, di\n\t" + "mov %rbp, bp\n\t" + "mov %rsp, sp\n\t" + "mov %r8, r8\n\t" + "mov %r9, r9\n\t" + "mov %r10, r10\n\t" + "mov %r11, r11\n\t" + "mov %r12, r12\n\t" + "mov %r13, r13\n\t" + "mov %r14, r14\n\t" + "mov %r15, r15\n\t" + "pushfq\n\t" + "popq flags\n\t" +#else + "mov %eax, ax\n\t" + "mov %ebx, bx\n\t" + "mov %ecx, cx\n\t" + "mov %edx, dx\n\t" + "mov %esi, si\n\t" + "mov %edi, di\n\t" + "mov %ebp, bp\n\t" + "mov %esp, sp\n\t" + "pushfl\n\t" + "popl flags\n\t" +#endif + "jmp _start\n\t" + ".size real_start, . - real_start\n\t" + ".popsection"); + +int main() +{ + int nerrs = 0; + + if (sp == 0) { + printf("[FAIL]\tTest was built incorrectly\n"); + return 1; + } + + if (ax || bx || cx || dx || si || di || bp +#ifdef __x86_64__ + || r8 || r9 || r10 || r11 || r12 || r13 || r14 || r15 +#endif + ) { + printf("[FAIL]\tAll GPRs except SP should be 0\n"); +#define SHOW(x) printf("\t" #x " = 0x%lx\n", x); + SHOW(ax); + SHOW(bx); + SHOW(cx); + SHOW(dx); + SHOW(si); + SHOW(di); + SHOW(bp); + SHOW(sp); +#ifdef __x86_64__ + SHOW(r8); + SHOW(r9); + SHOW(r10); + SHOW(r11); + SHOW(r12); + SHOW(r13); + SHOW(r14); + SHOW(r15); +#endif + nerrs++; + } else { + printf("[OK]\tAll GPRs except SP are 0\n"); + } + + if (flags != 0x202) { + printf("[FAIL]\tFLAGS is 0x%lx, but it should be 0x202\n", flags); + nerrs++; + } else { + printf("[OK]\tFLAGS is 0x202\n"); + } + + return nerrs ? 1 : 0; +} diff --git a/tools/testing/selftests/x86/ptrace_syscall.c b/tools/testing/selftests/x86/ptrace_syscall.c index 5105b49cd8aa..421456784bc6 100644 --- a/tools/testing/selftests/x86/ptrace_syscall.c +++ b/tools/testing/selftests/x86/ptrace_syscall.c @@ -103,6 +103,17 @@ static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), err(1, "sigaction"); } +static void setsigign(int sig, int flags) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = (void *)SIG_IGN; + sa.sa_flags = flags; + sigemptyset(&sa.sa_mask); + if (sigaction(sig, &sa, 0)) + err(1, "sigaction"); +} + static void clearhandler(int sig) { struct sigaction sa; @@ -187,7 +198,7 @@ static void test_ptrace_syscall_restart(void) printf("[RUN]\tSYSEMU\n"); if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0) - err(1, "PTRACE_SYSCALL"); + err(1, "PTRACE_SYSEMU"); wait_trap(chld); if (ptrace(PTRACE_GETREGS, chld, 0, ®s) != 0) @@ -218,7 +229,7 @@ static void test_ptrace_syscall_restart(void) err(1, "PTRACE_SETREGS"); if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0) - err(1, "PTRACE_SYSCALL"); + err(1, "PTRACE_SYSEMU"); wait_trap(chld); if (ptrace(PTRACE_GETREGS, chld, 0, ®s) != 0) @@ -250,7 +261,7 @@ static void test_ptrace_syscall_restart(void) err(1, "PTRACE_SETREGS"); if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0) - err(1, "PTRACE_SYSCALL"); + err(1, "PTRACE_SYSEMU"); wait_trap(chld); if (ptrace(PTRACE_GETREGS, chld, 0, ®s) != 0) @@ -277,6 +288,119 @@ static void test_ptrace_syscall_restart(void) } } +static void test_restart_under_ptrace(void) +{ + printf("[RUN]\tkernel syscall restart under ptrace\n"); + pid_t chld = fork(); + if (chld < 0) + err(1, "fork"); + + if (chld == 0) { + if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0) + err(1, "PTRACE_TRACEME"); + + printf("\tChild will take a nap until signaled\n"); + setsigign(SIGUSR1, SA_RESTART); + raise(SIGSTOP); + + syscall(SYS_pause, 0, 0, 0, 0, 0, 0); + _exit(0); + } + + int status; + + /* Wait for SIGSTOP. */ + if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status)) + err(1, "waitpid"); + + struct user_regs_struct regs; + + printf("[RUN]\tSYSCALL\n"); + if (ptrace(PTRACE_SYSCALL, chld, 0, 0) != 0) + err(1, "PTRACE_SYSCALL"); + wait_trap(chld); + + /* We should be stopped at pause(2) entry. */ + + if (ptrace(PTRACE_GETREGS, chld, 0, ®s) != 0) + err(1, "PTRACE_GETREGS"); + + if (regs.user_syscall_nr != SYS_pause || + regs.user_arg0 != 0 || regs.user_arg1 != 0 || + regs.user_arg2 != 0 || regs.user_arg3 != 0 || + regs.user_arg4 != 0 || regs.user_arg5 != 0) { + printf("[FAIL]\tInitial args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5); + nerrs++; + } else { + printf("[OK]\tInitial nr and args are correct\n"); + } + + /* Interrupt it. */ + kill(chld, SIGUSR1); + + /* Advance. We should be stopped at exit. */ + printf("[RUN]\tSYSCALL\n"); + if (ptrace(PTRACE_SYSCALL, chld, 0, 0) != 0) + err(1, "PTRACE_SYSCALL"); + wait_trap(chld); + + if (ptrace(PTRACE_GETREGS, chld, 0, ®s) != 0) + err(1, "PTRACE_GETREGS"); + + if (regs.user_syscall_nr != SYS_pause || + regs.user_arg0 != 0 || regs.user_arg1 != 0 || + regs.user_arg2 != 0 || regs.user_arg3 != 0 || + regs.user_arg4 != 0 || regs.user_arg5 != 0) { + printf("[FAIL]\tArgs after SIGUSR1 are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5); + nerrs++; + } else { + printf("[OK]\tArgs after SIGUSR1 are correct (ax = %ld)\n", + (long)regs.user_ax); + } + + /* Poke the regs back in. This must not break anything. */ + if (ptrace(PTRACE_SETREGS, chld, 0, ®s) != 0) + err(1, "PTRACE_SETREGS"); + + /* Catch the (ignored) SIGUSR1. */ + if (ptrace(PTRACE_CONT, chld, 0, 0) != 0) + err(1, "PTRACE_CONT"); + if (waitpid(chld, &status, 0) != chld) + err(1, "waitpid"); + if (!WIFSTOPPED(status)) { + printf("[FAIL]\tChild was stopped for SIGUSR1 (status = 0x%x)\n", status); + nerrs++; + } else { + printf("[OK]\tChild got SIGUSR1\n"); + } + + /* The next event should be pause(2) again. */ + printf("[RUN]\tStep again\n"); + if (ptrace(PTRACE_SYSCALL, chld, 0, 0) != 0) + err(1, "PTRACE_SYSCALL"); + wait_trap(chld); + + /* We should be stopped at pause(2) entry. */ + + if (ptrace(PTRACE_GETREGS, chld, 0, ®s) != 0) + err(1, "PTRACE_GETREGS"); + + if (regs.user_syscall_nr != SYS_pause || + regs.user_arg0 != 0 || regs.user_arg1 != 0 || + regs.user_arg2 != 0 || regs.user_arg3 != 0 || + regs.user_arg4 != 0 || regs.user_arg5 != 0) { + printf("[FAIL]\tpause did not restart (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5); + nerrs++; + } else { + printf("[OK]\tpause(2) restarted correctly\n"); + } + + /* Kill it. */ + kill(chld, SIGKILL); + if (waitpid(chld, &status, 0) != chld) + err(1, "waitpid"); +} + int main() { printf("[RUN]\tCheck int80 return regs\n"); @@ -290,5 +414,7 @@ int main() test_ptrace_syscall_restart(); + test_restart_under_ptrace(); + return 0; } diff --git a/tools/testing/selftests/x86/sigreturn.c b/tools/testing/selftests/x86/sigreturn.c index b5aa1bab7416..8a577e7070c6 100644 --- a/tools/testing/selftests/x86/sigreturn.c +++ b/tools/testing/selftests/x86/sigreturn.c @@ -54,6 +54,37 @@ #include <sys/ptrace.h> #include <sys/user.h> +/* Pull in AR_xyz defines. */ +typedef unsigned int u32; +typedef unsigned short u16; +#include "../../../../arch/x86/include/asm/desc_defs.h" + +/* + * Copied from asm/ucontext.h, as asm/ucontext.h conflicts badly with the glibc + * headers. + */ +#ifdef __x86_64__ +/* + * UC_SIGCONTEXT_SS will be set when delivering 64-bit or x32 signals on + * kernels that save SS in the sigcontext. All kernels that set + * UC_SIGCONTEXT_SS will correctly restore at least the low 32 bits of esp + * regardless of SS (i.e. they implement espfix). + * + * Kernels that set UC_SIGCONTEXT_SS will also set UC_STRICT_RESTORE_SS + * when delivering a signal that came from 64-bit code. + * + * Sigreturn restores SS as follows: + * + * if (saved SS is valid || UC_STRICT_RESTORE_SS is set || + * saved CS is not 64-bit) + * new SS = saved SS (will fail IRET and signal if invalid) + * else + * new SS = a flat 32-bit data segment + */ +#define UC_SIGCONTEXT_SS 0x2 +#define UC_STRICT_RESTORE_SS 0x4 +#endif + /* * In principle, this test can run on Linux emulation layers (e.g. * Illumos "LX branded zones"). Solaris-based kernels reserve LDT @@ -267,6 +298,9 @@ static gregset_t initial_regs, requested_regs, resulting_regs; /* Instructions for the SIGUSR1 handler. */ static volatile unsigned short sig_cs, sig_ss; static volatile sig_atomic_t sig_trapped, sig_err, sig_trapno; +#ifdef __x86_64__ +static volatile sig_atomic_t sig_corrupt_final_ss; +#endif /* Abstractions for some 32-bit vs 64-bit differences. */ #ifdef __x86_64__ @@ -305,9 +339,105 @@ static greg_t *csptr(ucontext_t *ctx) } #endif +/* + * Checks a given selector for its code bitness or returns -1 if it's not + * a usable code segment selector. + */ +int cs_bitness(unsigned short cs) +{ + uint32_t valid = 0, ar; + asm ("lar %[cs], %[ar]\n\t" + "jnz 1f\n\t" + "mov $1, %[valid]\n\t" + "1:" + : [ar] "=r" (ar), [valid] "+rm" (valid) + : [cs] "r" (cs)); + + if (!valid) + return -1; + + bool db = (ar & (1 << 22)); + bool l = (ar & (1 << 21)); + + if (!(ar & (1<<11))) + return -1; /* Not code. */ + + if (l && !db) + return 64; + else if (!l && db) + return 32; + else if (!l && !db) + return 16; + else + return -1; /* Unknown bitness. */ +} + +/* + * Checks a given selector for its code bitness or returns -1 if it's not + * a usable code segment selector. + */ +bool is_valid_ss(unsigned short cs) +{ + uint32_t valid = 0, ar; + asm ("lar %[cs], %[ar]\n\t" + "jnz 1f\n\t" + "mov $1, %[valid]\n\t" + "1:" + : [ar] "=r" (ar), [valid] "+rm" (valid) + : [cs] "r" (cs)); + + if (!valid) + return false; + + if ((ar & AR_TYPE_MASK) != AR_TYPE_RWDATA && + (ar & AR_TYPE_MASK) != AR_TYPE_RWDATA_EXPDOWN) + return false; + + return (ar & AR_P); +} + /* Number of errors in the current test case. */ static volatile sig_atomic_t nerrs; +static void validate_signal_ss(int sig, ucontext_t *ctx) +{ +#ifdef __x86_64__ + bool was_64bit = (cs_bitness(*csptr(ctx)) == 64); + + if (!(ctx->uc_flags & UC_SIGCONTEXT_SS)) { + printf("[FAIL]\tUC_SIGCONTEXT_SS was not set\n"); + nerrs++; + + /* + * This happens on Linux 4.1. The rest will fail, too, so + * return now to reduce the noise. + */ + return; + } + + /* UC_STRICT_RESTORE_SS is set iff we came from 64-bit mode. */ + if (!!(ctx->uc_flags & UC_STRICT_RESTORE_SS) != was_64bit) { + printf("[FAIL]\tUC_STRICT_RESTORE_SS was wrong in signal %d\n", + sig); + nerrs++; + } + + if (is_valid_ss(*ssptr(ctx))) { + /* + * DOSEMU was written before 64-bit sigcontext had SS, and + * it tries to figure out the signal source SS by looking at + * the physical register. Make sure that keeps working. + */ + unsigned short hw_ss; + asm ("mov %%ss, %0" : "=rm" (hw_ss)); + if (hw_ss != *ssptr(ctx)) { + printf("[FAIL]\tHW SS didn't match saved SS\n"); + nerrs++; + } + } +#endif +} + /* * SIGUSR1 handler. Sets CS and SS as requested and points IP to the * int3 trampoline. Sets SP to a large known value so that we can see @@ -317,6 +447,8 @@ static void sigusr1(int sig, siginfo_t *info, void *ctx_void) { ucontext_t *ctx = (ucontext_t*)ctx_void; + validate_signal_ss(sig, ctx); + memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); *csptr(ctx) = sig_cs; @@ -334,13 +466,16 @@ static void sigusr1(int sig, siginfo_t *info, void *ctx_void) } /* - * Called after a successful sigreturn. Restores our state so that - * the original raise(SIGUSR1) returns. + * Called after a successful sigreturn (via int3) or from a failed + * sigreturn (directly by kernel). Restores our state so that the + * original raise(SIGUSR1) returns. */ static void sigtrap(int sig, siginfo_t *info, void *ctx_void) { ucontext_t *ctx = (ucontext_t*)ctx_void; + validate_signal_ss(sig, ctx); + sig_err = ctx->uc_mcontext.gregs[REG_ERR]; sig_trapno = ctx->uc_mcontext.gregs[REG_TRAPNO]; @@ -358,41 +493,62 @@ static void sigtrap(int sig, siginfo_t *info, void *ctx_void) memcpy(&resulting_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t)); +#ifdef __x86_64__ + if (sig_corrupt_final_ss) { + if (ctx->uc_flags & UC_STRICT_RESTORE_SS) { + printf("[FAIL]\tUC_STRICT_RESTORE_SS was set inappropriately\n"); + nerrs++; + } else { + /* + * DOSEMU transitions from 32-bit to 64-bit mode by + * adjusting sigcontext, and it requires that this work + * even if the saved SS is bogus. + */ + printf("\tCorrupting SS on return to 64-bit mode\n"); + *ssptr(ctx) = 0; + } + } +#endif + sig_trapped = sig; } -/* - * Checks a given selector for its code bitness or returns -1 if it's not - * a usable code segment selector. - */ -int cs_bitness(unsigned short cs) +#ifdef __x86_64__ +/* Tests recovery if !UC_STRICT_RESTORE_SS */ +static void sigusr2(int sig, siginfo_t *info, void *ctx_void) { - uint32_t valid = 0, ar; - asm ("lar %[cs], %[ar]\n\t" - "jnz 1f\n\t" - "mov $1, %[valid]\n\t" - "1:" - : [ar] "=r" (ar), [valid] "+rm" (valid) - : [cs] "r" (cs)); + ucontext_t *ctx = (ucontext_t*)ctx_void; - if (!valid) - return -1; + if (!(ctx->uc_flags & UC_STRICT_RESTORE_SS)) { + printf("[FAIL]\traise(2) didn't set UC_STRICT_RESTORE_SS\n"); + nerrs++; + return; /* We can't do the rest. */ + } - bool db = (ar & (1 << 22)); - bool l = (ar & (1 << 21)); + ctx->uc_flags &= ~UC_STRICT_RESTORE_SS; + *ssptr(ctx) = 0; - if (!(ar & (1<<11))) - return -1; /* Not code. */ + /* Return. The kernel should recover without sending another signal. */ +} - if (l && !db) - return 64; - else if (!l && db) - return 32; - else if (!l && !db) - return 16; - else - return -1; /* Unknown bitness. */ +static int test_nonstrict_ss(void) +{ + clearhandler(SIGUSR1); + clearhandler(SIGTRAP); + clearhandler(SIGSEGV); + clearhandler(SIGILL); + sethandler(SIGUSR2, sigusr2, 0); + + nerrs = 0; + + printf("[RUN]\tClear UC_STRICT_RESTORE_SS and corrupt SS\n"); + raise(SIGUSR2); + if (!nerrs) + printf("[OK]\tIt worked\n"); + + return nerrs; } +#endif /* Finds a usable code segment of the requested bitness. */ int find_cs(int bitness) @@ -576,6 +732,12 @@ static int test_bad_iret(int cs_bits, unsigned short ss, int force_cs) errdesc, strsignal(sig_trapped)); return 0; } else { + /* + * This also implicitly tests UC_STRICT_RESTORE_SS: + * We check that these signals set UC_STRICT_RESTORE_SS and, + * if UC_STRICT_RESTORE_SS doesn't cause strict behavior, + * then we won't get SIGSEGV. + */ printf("[FAIL]\tDid not get SIGSEGV\n"); return 1; } @@ -632,6 +794,14 @@ int main() GDT3(gdt_data16_idx)); } +#ifdef __x86_64__ + /* Nasty ABI case: check SS corruption handling. */ + sig_corrupt_final_ss = 1; + total_nerrs += test_valid_sigreturn(32, false, -1); + total_nerrs += test_valid_sigreturn(32, true, -1); + sig_corrupt_final_ss = 0; +#endif + /* * We're done testing valid sigreturn cases. Now we test states * for which sigreturn itself will succeed but the subsequent @@ -680,5 +850,9 @@ int main() if (gdt_npdata32_idx) test_bad_iret(32, GDT3(gdt_npdata32_idx), -1); +#ifdef __x86_64__ + total_nerrs += test_nonstrict_ss(); +#endif + return total_nerrs ? 1 : 0; } |