Merge branch 'x86-asm-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull x86 asm updates from Ingo Molnar:
"This is another big update. Main changes are:
- lots of x86 system call (and other traps/exceptions) entry code
enhancements. In particular the complex parts of the 64-bit entry
code have been migrated to C code as well, and a number of dusty
corners have been refreshed. (Andy Lutomirski)
- vDSO special mapping robustification and general cleanups (Andy
Lutomirski)
- cpufeature refactoring, cleanups and speedups (Borislav Petkov)
- lots of other changes ..."
* 'x86-asm-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (64 commits)
x86/cpufeature: Enable new AVX-512 features
x86/entry/traps: Show unhandled signal for i386 in do_trap()
x86/entry: Call enter_from_user_mode() with IRQs off
x86/entry/32: Change INT80 to be an interrupt gate
x86/entry: Improve system call entry comments
x86/entry: Remove TIF_SINGLESTEP entry work
x86/entry/32: Add and check a stack canary for the SYSENTER stack
x86/entry/32: Simplify and fix up the SYSENTER stack #DB/NMI fixup
x86/entry: Only allocate space for tss_struct::SYSENTER_stack if needed
x86/entry: Vastly simplify SYSENTER TF (single-step) handling
x86/entry/traps: Clear DR6 early in do_debug() and improve the comment
x86/entry/traps: Clear TIF_BLOCKSTEP on all debug exceptions
x86/entry/32: Restore FLAGS on SYSEXIT
x86/entry/32: Filter NT and speed up AC filtering in SYSENTER
x86/entry/compat: In SYSENTER, sink AC clearing below the existing FLAGS test
selftests/x86: In syscall_nt, test NT|TF as well
x86/asm-offsets: Remove PARAVIRT_enabled
x86/entry/32: Introduce and use X86_BUG_ESPFIX instead of paravirt_enabled
uprobes: __create_xol_area() must nullify xol_mapping.fault
x86/cpufeature: Create a new synthetic cpu capability for machine check recovery
...
This commit is contained in:
@@ -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
|
||||
|
||||
109
tools/testing/selftests/x86/check_initial_reg_state.c
Normal file
109
tools/testing/selftests/x86/check_initial_reg_state.c
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,62 +339,6 @@ static greg_t *csptr(ucontext_t *ctx)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Number of errors in the current test case. */
|
||||
static volatile sig_atomic_t nerrs;
|
||||
|
||||
/*
|
||||
* 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
|
||||
* whether the value round-trips back to user mode correctly.
|
||||
*/
|
||||
static void sigusr1(int sig, siginfo_t *info, void *ctx_void)
|
||||
{
|
||||
ucontext_t *ctx = (ucontext_t*)ctx_void;
|
||||
|
||||
memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
|
||||
|
||||
*csptr(ctx) = sig_cs;
|
||||
*ssptr(ctx) = sig_ss;
|
||||
|
||||
ctx->uc_mcontext.gregs[REG_IP] =
|
||||
sig_cs == code16_sel ? 0 : (unsigned long)&int3;
|
||||
ctx->uc_mcontext.gregs[REG_SP] = (unsigned long)0x8badf00d5aadc0deULL;
|
||||
ctx->uc_mcontext.gregs[REG_AX] = 0;
|
||||
|
||||
memcpy(&requested_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
|
||||
requested_regs[REG_AX] = *ssptr(ctx); /* The asm code does this. */
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called after a successful sigreturn. 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;
|
||||
|
||||
sig_err = ctx->uc_mcontext.gregs[REG_ERR];
|
||||
sig_trapno = ctx->uc_mcontext.gregs[REG_TRAPNO];
|
||||
|
||||
unsigned short ss;
|
||||
asm ("mov %%ss,%0" : "=r" (ss));
|
||||
|
||||
greg_t asm_ss = ctx->uc_mcontext.gregs[REG_AX];
|
||||
if (asm_ss != sig_ss && sig == SIGTRAP) {
|
||||
/* Sanity check failure. */
|
||||
printf("[FAIL]\tSIGTRAP: ss = %hx, frame ss = %hx, ax = %llx\n",
|
||||
ss, *ssptr(ctx), (unsigned long long)asm_ss);
|
||||
nerrs++;
|
||||
}
|
||||
|
||||
memcpy(&resulting_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
|
||||
memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t));
|
||||
|
||||
sig_trapped = sig;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks a given selector for its code bitness or returns -1 if it's not
|
||||
* a usable code segment selector.
|
||||
@@ -394,6 +372,184 @@ int cs_bitness(unsigned short cs)
|
||||
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
|
||||
* whether the value round-trips back to user mode correctly.
|
||||
*/
|
||||
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;
|
||||
*ssptr(ctx) = sig_ss;
|
||||
|
||||
ctx->uc_mcontext.gregs[REG_IP] =
|
||||
sig_cs == code16_sel ? 0 : (unsigned long)&int3;
|
||||
ctx->uc_mcontext.gregs[REG_SP] = (unsigned long)0x8badf00d5aadc0deULL;
|
||||
ctx->uc_mcontext.gregs[REG_AX] = 0;
|
||||
|
||||
memcpy(&requested_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
|
||||
requested_regs[REG_AX] = *ssptr(ctx); /* The asm code does this. */
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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];
|
||||
|
||||
unsigned short ss;
|
||||
asm ("mov %%ss,%0" : "=r" (ss));
|
||||
|
||||
greg_t asm_ss = ctx->uc_mcontext.gregs[REG_AX];
|
||||
if (asm_ss != sig_ss && sig == SIGTRAP) {
|
||||
/* Sanity check failure. */
|
||||
printf("[FAIL]\tSIGTRAP: ss = %hx, frame ss = %hx, ax = %llx\n",
|
||||
ss, *ssptr(ctx), (unsigned long long)asm_ss);
|
||||
nerrs++;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef __x86_64__
|
||||
/* Tests recovery if !UC_STRICT_RESTORE_SS */
|
||||
static void sigusr2(int sig, siginfo_t *info, void *ctx_void)
|
||||
{
|
||||
ucontext_t *ctx = (ucontext_t*)ctx_void;
|
||||
|
||||
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. */
|
||||
}
|
||||
|
||||
ctx->uc_flags &= ~UC_STRICT_RESTORE_SS;
|
||||
*ssptr(ctx) = 0;
|
||||
|
||||
/* Return. The kernel should recover without sending another signal. */
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <err.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <asm/processor-flags.h>
|
||||
|
||||
@@ -26,6 +29,8 @@
|
||||
# define WIDTH "l"
|
||||
#endif
|
||||
|
||||
static unsigned int nerrs;
|
||||
|
||||
static unsigned long get_eflags(void)
|
||||
{
|
||||
unsigned long eflags;
|
||||
@@ -39,16 +44,52 @@ static void set_eflags(unsigned long eflags)
|
||||
: : "rm" (eflags) : "flags");
|
||||
}
|
||||
|
||||
int main()
|
||||
static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
|
||||
int flags)
|
||||
{
|
||||
printf("[RUN]\tSet NT and issue a syscall\n");
|
||||
set_eflags(get_eflags() | X86_EFLAGS_NT);
|
||||
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");
|
||||
}
|
||||
|
||||
static void sigtrap(int sig, siginfo_t *si, void *ctx_void)
|
||||
{
|
||||
}
|
||||
|
||||
static void do_it(unsigned long extraflags)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
set_eflags(get_eflags() | extraflags);
|
||||
syscall(SYS_getpid);
|
||||
if (get_eflags() & X86_EFLAGS_NT) {
|
||||
printf("[OK]\tThe syscall worked and NT is still set\n");
|
||||
return 0;
|
||||
flags = get_eflags();
|
||||
if ((flags & extraflags) == extraflags) {
|
||||
printf("[OK]\tThe syscall worked and flags are still set\n");
|
||||
} else {
|
||||
printf("[FAIL]\tThe syscall worked but NT was cleared\n");
|
||||
return 1;
|
||||
printf("[FAIL]\tThe syscall worked but flags were cleared (flags = 0x%lx but expected 0x%lx set)\n",
|
||||
flags, extraflags);
|
||||
nerrs++;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("[RUN]\tSet NT and issue a syscall\n");
|
||||
do_it(X86_EFLAGS_NT);
|
||||
|
||||
/*
|
||||
* Now try it again with TF set -- TF forces returns via IRET in all
|
||||
* cases except non-ptregs-using 64-bit full fast path syscalls.
|
||||
*/
|
||||
|
||||
sethandler(SIGTRAP, sigtrap, 0);
|
||||
|
||||
printf("[RUN]\tSet NT|TF and issue a syscall\n");
|
||||
do_it(X86_EFLAGS_NT | X86_EFLAGS_TF);
|
||||
|
||||
return nerrs == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user