diff options
Diffstat (limited to 'x86')
-rw-r--r-- | x86/.gitignore | 2 | ||||
-rw-r--r-- | x86/Makefile | 65 | ||||
-rwxr-xr-x | x86/check_cc.sh | 16 | ||||
-rw-r--r-- | x86/entry_from_vm86.c | 238 | ||||
-rw-r--r-- | x86/ldt_gdt.c | 576 | ||||
-rw-r--r-- | x86/ptrace_syscall.c | 294 | ||||
-rw-r--r-- | x86/raw_syscall_helper_32.S | 46 | ||||
-rw-r--r-- | x86/sigreturn.c | 684 | ||||
-rw-r--r-- | x86/single_step_syscall.c | 181 | ||||
-rw-r--r-- | x86/syscall_arg_fault.c | 130 | ||||
-rw-r--r-- | x86/syscall_nt.c | 54 | ||||
-rw-r--r-- | x86/sysret_ss_attrs.c | 112 | ||||
-rw-r--r-- | x86/test_FCMOV.c | 93 | ||||
-rw-r--r-- | x86/test_FCOMI.c | 331 | ||||
-rw-r--r-- | x86/test_FISTTP.c | 137 | ||||
-rw-r--r-- | x86/test_syscall_vdso.c | 401 | ||||
-rw-r--r-- | x86/thunks.S | 67 | ||||
-rw-r--r-- | x86/thunks_32.S | 55 | ||||
-rw-r--r-- | x86/trivial_32bit_program.c | 18 | ||||
-rw-r--r-- | x86/trivial_64bit_program.c | 18 | ||||
-rw-r--r-- | x86/unwind_vdso.c | 211 |
21 files changed, 3729 insertions, 0 deletions
diff --git a/x86/.gitignore b/x86/.gitignore new file mode 100644 index 0000000..15034fe --- /dev/null +++ b/x86/.gitignore @@ -0,0 +1,2 @@ +*_32 +*_64 diff --git a/x86/Makefile b/x86/Makefile new file mode 100644 index 0000000..eabcff4 --- /dev/null +++ b/x86/Makefile @@ -0,0 +1,65 @@ +all: + +include ../lib.mk + +.PHONY: all all_32 all_64 warn_32bit_failure clean + +TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs ldt_gdt syscall_nt ptrace_syscall +TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault sigreturn test_syscall_vdso unwind_vdso \ + test_FCMOV test_FCOMI test_FISTTP + +TARGETS_C_32BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_32BIT_ONLY) +BINARIES_32 := $(TARGETS_C_32BIT_ALL:%=%_32) +BINARIES_64 := $(TARGETS_C_BOTHBITS:%=%_64) + +CFLAGS := -O2 -g -std=gnu99 -pthread -Wall + +UNAME_M := $(shell uname -m) +CAN_BUILD_I386 := $(shell ./check_cc.sh $(CC) trivial_32bit_program.c -m32) +CAN_BUILD_X86_64 := $(shell ./check_cc.sh $(CC) trivial_64bit_program.c) + +ifeq ($(CAN_BUILD_I386),1) +all: all_32 +TEST_PROGS += $(BINARIES_32) +endif + +ifeq ($(CAN_BUILD_X86_64),1) +all: all_64 +TEST_PROGS += $(BINARIES_64) +endif + +all_32: $(BINARIES_32) + +all_64: $(BINARIES_64) + +clean: + $(RM) $(BINARIES_32) $(BINARIES_64) + +$(TARGETS_C_32BIT_ALL:%=%_32): %_32: %.c + $(CC) -m32 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $^ -lrt -ldl -lm + +$(TARGETS_C_BOTHBITS:%=%_64): %_64: %.c + $(CC) -m64 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $^ -lrt -ldl + +# x86_64 users should be encouraged to install 32-bit libraries +ifeq ($(CAN_BUILD_I386)$(CAN_BUILD_X86_64),01) +all: warn_32bit_failure + +warn_32bit_failure: + @echo "Warning: you seem to have a broken 32-bit build" 2>&1; \ + echo "environment. This will reduce test coverage of 64-bit" 2>&1; \ + echo "kernels. If you are using a Debian-like distribution," 2>&1; \ + echo "try:"; 2>&1; \ + echo ""; \ + echo " apt-get install gcc-multilib libc6-i386 libc6-dev-i386"; \ + echo ""; \ + echo "If you are using a Fedora-like distribution, try:"; \ + echo ""; \ + echo " yum install glibc-devel.*i686"; \ + exit 0; +endif + +# Some tests have additional dependencies. +sysret_ss_attrs_64: thunks.S +ptrace_syscall_32: raw_syscall_helper_32.S +test_syscall_vdso_32: thunks_32.S diff --git a/x86/check_cc.sh b/x86/check_cc.sh new file mode 100755 index 0000000..172d329 --- /dev/null +++ b/x86/check_cc.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# check_cc.sh - Helper to test userspace compilation support +# Copyright (c) 2015 Andrew Lutomirski +# GPL v2 + +CC="$1" +TESTPROG="$2" +shift 2 + +if "$CC" -o /dev/null "$TESTPROG" -O0 "$@" 2>/dev/null; then + echo 1 +else + echo 0 +fi + +exit 0 diff --git a/x86/entry_from_vm86.c b/x86/entry_from_vm86.c new file mode 100644 index 0000000..d075ea0 --- /dev/null +++ b/x86/entry_from_vm86.c @@ -0,0 +1,238 @@ +/* + * entry_from_vm86.c - tests kernel entries from vm86 mode + * Copyright (c) 2014-2015 Andrew Lutomirski + * + * This exercises a few paths that need to special-case vm86 mode. + * + * GPL v2. + */ + +#define _GNU_SOURCE + +#include <assert.h> +#include <stdlib.h> +#include <sys/syscall.h> +#include <sys/signal.h> +#include <sys/ucontext.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include <sys/mman.h> +#include <err.h> +#include <stddef.h> +#include <stdbool.h> +#include <errno.h> +#include <sys/vm86.h> + +static unsigned long load_addr = 0x10000; +static int nerrs = 0; + +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"); +} + +static void clearhandler(int sig) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + if (sigaction(sig, &sa, 0)) + err(1, "sigaction"); +} + +static sig_atomic_t got_signal; + +static void sighandler(int sig, siginfo_t *info, void *ctx_void) +{ + ucontext_t *ctx = (ucontext_t*)ctx_void; + + if (ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_VM || + (ctx->uc_mcontext.gregs[REG_CS] & 3) != 3) { + printf("[FAIL]\tSignal frame should not reflect vm86 mode\n"); + nerrs++; + } + + const char *signame; + if (sig == SIGSEGV) + signame = "SIGSEGV"; + else if (sig == SIGILL) + signame = "SIGILL"; + else + signame = "unexpected signal"; + + printf("[INFO]\t%s: FLAGS = 0x%lx, CS = 0x%hx\n", signame, + (unsigned long)ctx->uc_mcontext.gregs[REG_EFL], + (unsigned short)ctx->uc_mcontext.gregs[REG_CS]); + + got_signal = 1; +} + +asm ( + ".pushsection .rodata\n\t" + ".type vmcode_bound, @object\n\t" + "vmcode:\n\t" + "vmcode_bound:\n\t" + ".code16\n\t" + "bound %ax, (2048)\n\t" + "int3\n\t" + "vmcode_sysenter:\n\t" + "sysenter\n\t" + "vmcode_syscall:\n\t" + "syscall\n\t" + "vmcode_sti:\n\t" + "sti\n\t" + "vmcode_int3:\n\t" + "int3\n\t" + "vmcode_int80:\n\t" + "int $0x80\n\t" + ".size vmcode, . - vmcode\n\t" + "end_vmcode:\n\t" + ".code32\n\t" + ".popsection" + ); + +extern unsigned char vmcode[], end_vmcode[]; +extern unsigned char vmcode_bound[], vmcode_sysenter[], vmcode_syscall[], + vmcode_sti[], vmcode_int3[], vmcode_int80[]; + +/* Returns false if the test was skipped. */ +static bool do_test(struct vm86plus_struct *v86, unsigned long eip, + unsigned int rettype, unsigned int retarg, + const char *text) +{ + long ret; + + printf("[RUN]\t%s from vm86 mode\n", text); + v86->regs.eip = eip; + ret = vm86(VM86_ENTER, v86); + + if (ret == -1 && (errno == ENOSYS || errno == EPERM)) { + printf("[SKIP]\tvm86 %s\n", + errno == ENOSYS ? "not supported" : "not allowed"); + return false; + } + + if (VM86_TYPE(ret) == VM86_INTx) { + char trapname[32]; + int trapno = VM86_ARG(ret); + if (trapno == 13) + strcpy(trapname, "GP"); + else if (trapno == 5) + strcpy(trapname, "BR"); + else if (trapno == 14) + strcpy(trapname, "PF"); + else + sprintf(trapname, "%d", trapno); + + printf("[INFO]\tExited vm86 mode due to #%s\n", trapname); + } else if (VM86_TYPE(ret) == VM86_UNKNOWN) { + printf("[INFO]\tExited vm86 mode due to unhandled GP fault\n"); + } else if (VM86_TYPE(ret) == VM86_TRAP) { + printf("[INFO]\tExited vm86 mode due to a trap (arg=%ld)\n", + VM86_ARG(ret)); + } else if (VM86_TYPE(ret) == VM86_SIGNAL) { + printf("[INFO]\tExited vm86 mode due to a signal\n"); + } else if (VM86_TYPE(ret) == VM86_STI) { + printf("[INFO]\tExited vm86 mode due to STI\n"); + } else { + printf("[INFO]\tExited vm86 mode due to type %ld, arg %ld\n", + VM86_TYPE(ret), VM86_ARG(ret)); + } + + if (rettype == -1 || + (VM86_TYPE(ret) == rettype && VM86_ARG(ret) == retarg)) { + printf("[OK]\tReturned correctly\n"); + } else { + printf("[FAIL]\tIncorrect return reason\n"); + nerrs++; + } + + return true; +} + +int main(void) +{ + struct vm86plus_struct v86; + unsigned char *addr = mmap((void *)load_addr, 4096, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, -1,0); + if (addr != (unsigned char *)load_addr) + err(1, "mmap"); + + memcpy(addr, vmcode, end_vmcode - vmcode); + addr[2048] = 2; + addr[2050] = 3; + + memset(&v86, 0, sizeof(v86)); + + v86.regs.cs = load_addr / 16; + v86.regs.ss = load_addr / 16; + v86.regs.ds = load_addr / 16; + v86.regs.es = load_addr / 16; + + assert((v86.regs.cs & 3) == 0); /* Looks like RPL = 0 */ + + /* #BR -- should deliver SIG??? */ + do_test(&v86, vmcode_bound - vmcode, VM86_INTx, 5, "#BR"); + + /* + * SYSENTER -- should cause #GP or #UD depending on CPU. + * Expected return type -1 means that we shouldn't validate + * the vm86 return value. This will avoid problems on non-SEP + * CPUs. + */ + sethandler(SIGILL, sighandler, 0); + do_test(&v86, vmcode_sysenter - vmcode, -1, 0, "SYSENTER"); + clearhandler(SIGILL); + + /* + * SYSCALL would be a disaster in VM86 mode. Fortunately, + * there is no kernel that both enables SYSCALL and sets + * EFER.SCE, so it's #UD on all systems. But vm86 is + * buggy (or has a "feature"), so the SIGILL will actually + * be delivered. + */ + sethandler(SIGILL, sighandler, 0); + do_test(&v86, vmcode_syscall - vmcode, VM86_SIGNAL, 0, "SYSCALL"); + clearhandler(SIGILL); + + /* STI with VIP set */ + v86.regs.eflags |= X86_EFLAGS_VIP; + v86.regs.eflags &= ~X86_EFLAGS_IF; + do_test(&v86, vmcode_sti - vmcode, VM86_STI, 0, "STI with VIP set"); + + /* INT3 -- should cause #BP */ + do_test(&v86, vmcode_int3 - vmcode, VM86_TRAP, 3, "INT3"); + + /* INT80 -- should exit with "INTx 0x80" */ + v86.regs.eax = (unsigned int)-1; + do_test(&v86, vmcode_int80 - vmcode, VM86_INTx, 0x80, "int80"); + + /* Execute a null pointer */ + v86.regs.cs = 0; + v86.regs.ss = 0; + sethandler(SIGSEGV, sighandler, 0); + got_signal = 0; + if (do_test(&v86, 0, VM86_SIGNAL, 0, "Execute null pointer") && + !got_signal) { + printf("[FAIL]\tDid not receive SIGSEGV\n"); + nerrs++; + } + clearhandler(SIGSEGV); + + /* Make sure nothing explodes if we fork. */ + if (fork() > 0) + return 0; + + return (nerrs == 0 ? 0 : 1); +} diff --git a/x86/ldt_gdt.c b/x86/ldt_gdt.c new file mode 100644 index 0000000..31a3035 --- /dev/null +++ b/x86/ldt_gdt.c @@ -0,0 +1,576 @@ +/* + * ldt_gdt.c - Test cases for LDT and GDT access + * Copyright (c) 2015 Andrew Lutomirski + */ + +#define _GNU_SOURCE +#include <err.h> +#include <stdio.h> +#include <stdint.h> +#include <signal.h> +#include <setjmp.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/syscall.h> +#include <asm/ldt.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdbool.h> +#include <pthread.h> +#include <sched.h> +#include <linux/futex.h> + +#define AR_ACCESSED (1<<8) + +#define AR_TYPE_RODATA (0 * (1<<9)) +#define AR_TYPE_RWDATA (1 * (1<<9)) +#define AR_TYPE_RODATA_EXPDOWN (2 * (1<<9)) +#define AR_TYPE_RWDATA_EXPDOWN (3 * (1<<9)) +#define AR_TYPE_XOCODE (4 * (1<<9)) +#define AR_TYPE_XRCODE (5 * (1<<9)) +#define AR_TYPE_XOCODE_CONF (6 * (1<<9)) +#define AR_TYPE_XRCODE_CONF (7 * (1<<9)) + +#define AR_DPL3 (3 * (1<<13)) + +#define AR_S (1 << 12) +#define AR_P (1 << 15) +#define AR_AVL (1 << 20) +#define AR_L (1 << 21) +#define AR_DB (1 << 22) +#define AR_G (1 << 23) + +static int nerrs; + +static void check_invalid_segment(uint16_t index, int ldt) +{ + uint32_t has_limit = 0, has_ar = 0, limit, ar; + uint32_t selector = (index << 3) | (ldt << 2) | 3; + + asm ("lsl %[selector], %[limit]\n\t" + "jnz 1f\n\t" + "movl $1, %[has_limit]\n\t" + "1:" + : [limit] "=r" (limit), [has_limit] "+rm" (has_limit) + : [selector] "r" (selector)); + asm ("larl %[selector], %[ar]\n\t" + "jnz 1f\n\t" + "movl $1, %[has_ar]\n\t" + "1:" + : [ar] "=r" (ar), [has_ar] "+rm" (has_ar) + : [selector] "r" (selector)); + + if (has_limit || has_ar) { + printf("[FAIL]\t%s entry %hu is valid but should be invalid\n", + (ldt ? "LDT" : "GDT"), index); + nerrs++; + } else { + printf("[OK]\t%s entry %hu is invalid\n", + (ldt ? "LDT" : "GDT"), index); + } +} + +static void check_valid_segment(uint16_t index, int ldt, + uint32_t expected_ar, uint32_t expected_limit, + bool verbose) +{ + uint32_t has_limit = 0, has_ar = 0, limit, ar; + uint32_t selector = (index << 3) | (ldt << 2) | 3; + + asm ("lsl %[selector], %[limit]\n\t" + "jnz 1f\n\t" + "movl $1, %[has_limit]\n\t" + "1:" + : [limit] "=r" (limit), [has_limit] "+rm" (has_limit) + : [selector] "r" (selector)); + asm ("larl %[selector], %[ar]\n\t" + "jnz 1f\n\t" + "movl $1, %[has_ar]\n\t" + "1:" + : [ar] "=r" (ar), [has_ar] "+rm" (has_ar) + : [selector] "r" (selector)); + + if (!has_limit || !has_ar) { + printf("[FAIL]\t%s entry %hu is invalid but should be valid\n", + (ldt ? "LDT" : "GDT"), index); + nerrs++; + return; + } + + if (ar != expected_ar) { + printf("[FAIL]\t%s entry %hu has AR 0x%08X but expected 0x%08X\n", + (ldt ? "LDT" : "GDT"), index, ar, expected_ar); + nerrs++; + } else if (limit != expected_limit) { + printf("[FAIL]\t%s entry %hu has limit 0x%08X but expected 0x%08X\n", + (ldt ? "LDT" : "GDT"), index, limit, expected_limit); + nerrs++; + } else if (verbose) { + printf("[OK]\t%s entry %hu has AR 0x%08X and limit 0x%08X\n", + (ldt ? "LDT" : "GDT"), index, ar, limit); + } +} + +static bool install_valid_mode(const struct user_desc *desc, uint32_t ar, + bool oldmode) +{ + int ret = syscall(SYS_modify_ldt, oldmode ? 1 : 0x11, + desc, sizeof(*desc)); + if (ret < -1) + errno = -ret; + if (ret == 0) { + uint32_t limit = desc->limit; + if (desc->limit_in_pages) + limit = (limit << 12) + 4095; + check_valid_segment(desc->entry_number, 1, ar, limit, true); + return true; + } else if (errno == ENOSYS) { + printf("[OK]\tmodify_ldt returned -ENOSYS\n"); + return false; + } else { + if (desc->seg_32bit) { + printf("[FAIL]\tUnexpected modify_ldt failure %d\n", + errno); + nerrs++; + return false; + } else { + printf("[OK]\tmodify_ldt rejected 16 bit segment\n"); + return false; + } + } +} + +static bool install_valid(const struct user_desc *desc, uint32_t ar) +{ + return install_valid_mode(desc, ar, false); +} + +static void install_invalid(const struct user_desc *desc, bool oldmode) +{ + int ret = syscall(SYS_modify_ldt, oldmode ? 1 : 0x11, + desc, sizeof(*desc)); + if (ret < -1) + errno = -ret; + if (ret == 0) { + check_invalid_segment(desc->entry_number, 1); + } else if (errno == ENOSYS) { + printf("[OK]\tmodify_ldt returned -ENOSYS\n"); + } else { + if (desc->seg_32bit) { + printf("[FAIL]\tUnexpected modify_ldt failure %d\n", + errno); + nerrs++; + } else { + printf("[OK]\tmodify_ldt rejected 16 bit segment\n"); + } + } +} + +static int safe_modify_ldt(int func, struct user_desc *ptr, + unsigned long bytecount) +{ + int ret = syscall(SYS_modify_ldt, 0x11, ptr, bytecount); + if (ret < -1) + errno = -ret; + return ret; +} + +static void fail_install(struct user_desc *desc) +{ + if (safe_modify_ldt(0x11, desc, sizeof(*desc)) == 0) { + printf("[FAIL]\tmodify_ldt accepted a bad descriptor\n"); + nerrs++; + } else if (errno == ENOSYS) { + printf("[OK]\tmodify_ldt returned -ENOSYS\n"); + } else { + printf("[OK]\tmodify_ldt failure %d\n", errno); + } +} + +static void do_simple_tests(void) +{ + struct user_desc desc = { + .entry_number = 0, + .base_addr = 0, + .limit = 10, + .seg_32bit = 1, + .contents = 2, /* Code, not conforming */ + .read_exec_only = 0, + .limit_in_pages = 0, + .seg_not_present = 0, + .useable = 0 + }; + install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE | AR_S | AR_P | AR_DB); + + desc.limit_in_pages = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE | + AR_S | AR_P | AR_DB | AR_G); + + check_invalid_segment(1, 1); + + desc.entry_number = 2; + install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE | + AR_S | AR_P | AR_DB | AR_G); + + check_invalid_segment(1, 1); + + desc.base_addr = 0xf0000000; + install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE | + AR_S | AR_P | AR_DB | AR_G); + + desc.useable = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE | + AR_S | AR_P | AR_DB | AR_G | AR_AVL); + + desc.seg_not_present = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE | + AR_S | AR_DB | AR_G | AR_AVL); + + desc.seg_32bit = 0; + install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE | + AR_S | AR_G | AR_AVL); + + desc.seg_32bit = 1; + desc.contents = 0; + install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA | + AR_S | AR_DB | AR_G | AR_AVL); + + desc.read_exec_only = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_RODATA | + AR_S | AR_DB | AR_G | AR_AVL); + + desc.contents = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_RODATA_EXPDOWN | + AR_S | AR_DB | AR_G | AR_AVL); + + desc.read_exec_only = 0; + desc.limit_in_pages = 0; + install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA_EXPDOWN | + AR_S | AR_DB | AR_AVL); + + desc.contents = 3; + install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE_CONF | + AR_S | AR_DB | AR_AVL); + + desc.read_exec_only = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_XOCODE_CONF | + AR_S | AR_DB | AR_AVL); + + desc.read_exec_only = 0; + desc.contents = 2; + install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE | + AR_S | AR_DB | AR_AVL); + + desc.read_exec_only = 1; + +#ifdef __x86_64__ + desc.lm = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_XOCODE | + AR_S | AR_DB | AR_AVL); + desc.lm = 0; +#endif + + bool entry1_okay = install_valid(&desc, AR_DPL3 | AR_TYPE_XOCODE | + AR_S | AR_DB | AR_AVL); + + if (entry1_okay) { + printf("[RUN]\tTest fork\n"); + pid_t child = fork(); + if (child == 0) { + nerrs = 0; + check_valid_segment(desc.entry_number, 1, + AR_DPL3 | AR_TYPE_XOCODE | + AR_S | AR_DB | AR_AVL, desc.limit, + true); + check_invalid_segment(1, 1); + exit(nerrs ? 1 : 0); + } else { + int status; + if (waitpid(child, &status, 0) != child || + !WIFEXITED(status)) { + printf("[FAIL]\tChild died\n"); + nerrs++; + } else if (WEXITSTATUS(status) != 0) { + printf("[FAIL]\tChild failed\n"); + nerrs++; + } else { + printf("[OK]\tChild succeeded\n"); + } + } + + printf("[RUN]\tTest size\n"); + int i; + for (i = 0; i < 8192; i++) { + desc.entry_number = i; + desc.limit = i; + if (safe_modify_ldt(0x11, &desc, sizeof(desc)) != 0) { + printf("[FAIL]\tFailed to install entry %d\n", i); + nerrs++; + break; + } + } + for (int j = 0; j < i; j++) { + check_valid_segment(j, 1, AR_DPL3 | AR_TYPE_XOCODE | + AR_S | AR_DB | AR_AVL, j, false); + } + printf("[DONE]\tSize test\n"); + } else { + printf("[SKIP]\tSkipping fork and size tests because we have no LDT\n"); + } + + /* Test entry_number too high. */ + desc.entry_number = 8192; + fail_install(&desc); + + /* Test deletion and actions mistakeable for deletion. */ + memset(&desc, 0, sizeof(desc)); + install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA | AR_S | AR_P); + + desc.seg_not_present = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA | AR_S); + + desc.seg_not_present = 0; + desc.read_exec_only = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_RODATA | AR_S | AR_P); + + desc.read_exec_only = 0; + desc.seg_not_present = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA | AR_S); + + desc.read_exec_only = 1; + desc.limit = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_RODATA | AR_S); + + desc.limit = 0; + desc.base_addr = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_RODATA | AR_S); + + desc.base_addr = 0; + install_invalid(&desc, false); + + desc.seg_not_present = 0; + desc.read_exec_only = 0; + desc.seg_32bit = 1; + install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA | AR_S | AR_P | AR_DB); + install_invalid(&desc, true); +} + +/* + * 0: thread is idle + * 1: thread armed + * 2: thread should clear LDT entry 0 + * 3: thread should exit + */ +static volatile unsigned int ftx; + +static void *threadproc(void *ctx) +{ + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(1, &cpuset); + if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) + err(1, "sched_setaffinity to CPU 1"); /* should never fail */ + + while (1) { + syscall(SYS_futex, &ftx, FUTEX_WAIT, 0, NULL, NULL, 0); + while (ftx != 2) { + if (ftx >= 3) + return NULL; + } + + /* clear LDT entry 0 */ + const struct user_desc desc = {}; + if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) != 0) + err(1, "modify_ldt"); + + /* If ftx == 2, set it to zero. If ftx == 100, quit. */ + unsigned int x = -2; + asm volatile ("lock xaddl %[x], %[ftx]" : + [x] "+r" (x), [ftx] "+m" (ftx)); + if (x != 2) + return NULL; + } +} + +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"); + +} + +static jmp_buf jmpbuf; + +static void sigsegv(int sig, siginfo_t *info, void *ctx_void) +{ + siglongjmp(jmpbuf, 1); +} + +static void do_multicpu_tests(void) +{ + cpu_set_t cpuset; + pthread_t thread; + int failures = 0, iters = 5, i; + unsigned short orig_ss; + + CPU_ZERO(&cpuset); + CPU_SET(1, &cpuset); + if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) { + printf("[SKIP]\tCannot set affinity to CPU 1\n"); + return; + } + + CPU_ZERO(&cpuset); + CPU_SET(0, &cpuset); + if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) { + printf("[SKIP]\tCannot set affinity to CPU 0\n"); + return; + } + + sethandler(SIGSEGV, sigsegv, 0); +#ifdef __i386__ + /* True 32-bit kernels send SIGILL instead of SIGSEGV on IRET faults. */ + sethandler(SIGILL, sigsegv, 0); +#endif + + printf("[RUN]\tCross-CPU LDT invalidation\n"); + + if (pthread_create(&thread, 0, threadproc, 0) != 0) + err(1, "pthread_create"); + + asm volatile ("mov %%ss, %0" : "=rm" (orig_ss)); + + for (i = 0; i < 5; i++) { + if (sigsetjmp(jmpbuf, 1) != 0) + continue; + + /* Make sure the thread is ready after the last test. */ + while (ftx != 0) + ; + + struct user_desc desc = { + .entry_number = 0, + .base_addr = 0, + .limit = 0xfffff, + .seg_32bit = 1, + .contents = 0, /* Data */ + .read_exec_only = 0, + .limit_in_pages = 1, + .seg_not_present = 0, + .useable = 0 + }; + + if (safe_modify_ldt(0x11, &desc, sizeof(desc)) != 0) { + if (errno != ENOSYS) + err(1, "modify_ldt"); + printf("[SKIP]\tmodify_ldt unavailable\n"); + break; + } + + /* Arm the thread. */ + ftx = 1; + syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0); + + asm volatile ("mov %0, %%ss" : : "r" (0x7)); + + /* Go! */ + ftx = 2; + + while (ftx != 0) + ; + + /* + * On success, modify_ldt will segfault us synchronously, + * and we'll escape via siglongjmp. + */ + + failures++; + asm volatile ("mov %0, %%ss" : : "rm" (orig_ss)); + }; + + ftx = 100; /* Kill the thread. */ + syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0); + + if (pthread_join(thread, NULL) != 0) + err(1, "pthread_join"); + + if (failures) { + printf("[FAIL]\t%d of %d iterations failed\n", failures, iters); + nerrs++; + } else { + printf("[OK]\tAll %d iterations succeeded\n", iters); + } +} + +static int finish_exec_test(void) +{ + /* + * In a sensible world, this would be check_invalid_segment(0, 1); + * For better or for worse, though, the LDT is inherited across exec. + * We can probably change this safely, but for now we test it. + */ + check_valid_segment(0, 1, + AR_DPL3 | AR_TYPE_XRCODE | AR_S | AR_P | AR_DB, + 42, true); + + return nerrs ? 1 : 0; +} + +static void do_exec_test(void) +{ + printf("[RUN]\tTest exec\n"); + + struct user_desc desc = { + .entry_number = 0, + .base_addr = 0, + .limit = 42, + .seg_32bit = 1, + .contents = 2, /* Code, not conforming */ + .read_exec_only = 0, + .limit_in_pages = 0, + .seg_not_present = 0, + .useable = 0 + }; + install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE | AR_S | AR_P | AR_DB); + + pid_t child = fork(); + if (child == 0) { + execl("/proc/self/exe", "ldt_gdt_test_exec", NULL); + printf("[FAIL]\tCould not exec self\n"); + exit(1); /* exec failed */ + } else { + int status; + if (waitpid(child, &status, 0) != child || + !WIFEXITED(status)) { + printf("[FAIL]\tChild died\n"); + nerrs++; + } else if (WEXITSTATUS(status) != 0) { + printf("[FAIL]\tChild failed\n"); + nerrs++; + } else { + printf("[OK]\tChild succeeded\n"); + } + } +} + +int main(int argc, char **argv) +{ + if (argc == 1 && !strcmp(argv[0], "ldt_gdt_test_exec")) + return finish_exec_test(); + + do_simple_tests(); + + do_multicpu_tests(); + + do_exec_test(); + + return nerrs ? 1 : 0; +} diff --git a/x86/ptrace_syscall.c b/x86/ptrace_syscall.c new file mode 100644 index 0000000..5105b49 --- /dev/null +++ b/x86/ptrace_syscall.c @@ -0,0 +1,294 @@ +#define _GNU_SOURCE + +#include <sys/ptrace.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/syscall.h> +#include <sys/user.h> +#include <unistd.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <err.h> +#include <string.h> +#include <asm/ptrace-abi.h> +#include <sys/auxv.h> + +/* Bitness-agnostic defines for user_regs_struct fields. */ +#ifdef __x86_64__ +# define user_syscall_nr orig_rax +# define user_arg0 rdi +# define user_arg1 rsi +# define user_arg2 rdx +# define user_arg3 r10 +# define user_arg4 r8 +# define user_arg5 r9 +# define user_ip rip +# define user_ax rax +#else +# define user_syscall_nr orig_eax +# define user_arg0 ebx +# define user_arg1 ecx +# define user_arg2 edx +# define user_arg3 esi +# define user_arg4 edi +# define user_arg5 ebp +# define user_ip eip +# define user_ax eax +#endif + +static int nerrs = 0; + +struct syscall_args32 { + uint32_t nr, arg0, arg1, arg2, arg3, arg4, arg5; +}; + +#ifdef __i386__ +extern void sys32_helper(struct syscall_args32 *, void *); +extern void int80_and_ret(void); +#endif + +/* + * Helper to invoke int80 with controlled regs and capture the final regs. + */ +static void do_full_int80(struct syscall_args32 *args) +{ +#ifdef __x86_64__ + register unsigned long bp asm("bp") = args->arg5; + asm volatile ("int $0x80" + : "+a" (args->nr), + "+b" (args->arg0), "+c" (args->arg1), "+d" (args->arg2), + "+S" (args->arg3), "+D" (args->arg4), "+r" (bp)); + args->arg5 = bp; +#else + sys32_helper(args, int80_and_ret); +#endif +} + +#ifdef __i386__ +static void (*vsyscall32)(void); + +/* + * Nasty helper to invoke AT_SYSINFO (i.e. __kernel_vsyscall) with + * controlled regs and capture the final regs. This is so nasty that it + * crashes my copy of gdb :) + */ +static void do_full_vsyscall32(struct syscall_args32 *args) +{ + sys32_helper(args, vsyscall32); +} +#endif + +static siginfo_t wait_trap(pid_t chld) +{ + siginfo_t si; + if (waitid(P_PID, chld, &si, WEXITED|WSTOPPED) != 0) + err(1, "waitid"); + if (si.si_pid != chld) + errx(1, "got unexpected pid in event\n"); + if (si.si_code != CLD_TRAPPED) + errx(1, "got unexpected event type %d\n", si.si_code); + return si; +} + +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"); +} + +static void clearhandler(int sig) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + if (sigaction(sig, &sa, 0)) + err(1, "sigaction"); +} + +#ifdef __x86_64__ +# define REG_BP REG_RBP +#else +# define REG_BP REG_EBP +#endif + +static void empty_handler(int sig, siginfo_t *si, void *ctx_void) +{ +} + +static void test_sys32_regs(void (*do_syscall)(struct syscall_args32 *)) +{ + struct syscall_args32 args = { + .nr = 224, /* gettid */ + .arg0 = 10, .arg1 = 11, .arg2 = 12, + .arg3 = 13, .arg4 = 14, .arg5 = 15, + }; + + do_syscall(&args); + + if (args.nr != getpid() || + args.arg0 != 10 || args.arg1 != 11 || args.arg2 != 12 || + args.arg3 != 13 || args.arg4 != 14 || args.arg5 != 15) { + printf("[FAIL]\tgetpid() failed to preseve regs\n"); + nerrs++; + } else { + printf("[OK]\tgetpid() preserves regs\n"); + } + + sethandler(SIGUSR1, empty_handler, 0); + + args.nr = 37; /* kill */ + args.arg0 = getpid(); + args.arg1 = SIGUSR1; + do_syscall(&args); + if (args.nr != 0 || + args.arg0 != getpid() || args.arg1 != SIGUSR1 || args.arg2 != 12 || + args.arg3 != 13 || args.arg4 != 14 || args.arg5 != 15) { + printf("[FAIL]\tkill(getpid(), SIGUSR1) failed to preseve regs\n"); + nerrs++; + } else { + printf("[OK]\tkill(getpid(), SIGUSR1) preserves regs\n"); + } + clearhandler(SIGUSR1); +} + +static void test_ptrace_syscall_restart(void) +{ + printf("[RUN]\tptrace-induced syscall restart\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 make one syscall\n"); + raise(SIGSTOP); + + syscall(SYS_gettid, 10, 11, 12, 13, 14, 15); + _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]\tSYSEMU\n"); + if (ptrace(PTRACE_SYSEMU, 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_gettid || + regs.user_arg0 != 10 || regs.user_arg1 != 11 || + regs.user_arg2 != 12 || regs.user_arg3 != 13 || + regs.user_arg4 != 14 || regs.user_arg5 != 15) { + 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"); + } + + printf("[RUN]\tRestart the syscall (ip = 0x%lx)\n", + (unsigned long)regs.user_ip); + + /* + * This does exactly what it appears to do if syscall is int80 or + * SYSCALL64. For SYSCALL32 or SYSENTER, though, this is highly + * magical. It needs to work so that ptrace and syscall restart + * work as expected. + */ + regs.user_ax = regs.user_syscall_nr; + regs.user_ip -= 2; + if (ptrace(PTRACE_SETREGS, chld, 0, ®s) != 0) + err(1, "PTRACE_SETREGS"); + + if (ptrace(PTRACE_SYSEMU, 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_gettid || + regs.user_arg0 != 10 || regs.user_arg1 != 11 || + regs.user_arg2 != 12 || regs.user_arg3 != 13 || + regs.user_arg4 != 14 || regs.user_arg5 != 15) { + printf("[FAIL]\tRestart nr or 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]\tRestarted nr and args are correct\n"); + } + + printf("[RUN]\tChange nr and args and restart the syscall (ip = 0x%lx)\n", + (unsigned long)regs.user_ip); + + regs.user_ax = SYS_getpid; + regs.user_arg0 = 20; + regs.user_arg1 = 21; + regs.user_arg2 = 22; + regs.user_arg3 = 23; + regs.user_arg4 = 24; + regs.user_arg5 = 25; + regs.user_ip -= 2; + + if (ptrace(PTRACE_SETREGS, chld, 0, ®s) != 0) + err(1, "PTRACE_SETREGS"); + + if (ptrace(PTRACE_SYSEMU, 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_getpid || + regs.user_arg0 != 20 || regs.user_arg1 != 21 || regs.user_arg2 != 22 || + regs.user_arg3 != 23 || regs.user_arg4 != 24 || regs.user_arg5 != 25) { + printf("[FAIL]\tRestart nr or 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]\tReplacement nr and args are correct\n"); + } + + if (ptrace(PTRACE_CONT, chld, 0, 0) != 0) + err(1, "PTRACE_CONT"); + if (waitpid(chld, &status, 0) != chld) + err(1, "waitpid"); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + printf("[FAIL]\tChild failed\n"); + nerrs++; + } else { + printf("[OK]\tChild exited cleanly\n"); + } +} + +int main() +{ + printf("[RUN]\tCheck int80 return regs\n"); + test_sys32_regs(do_full_int80); + +#if defined(__i386__) && (!defined(__GLIBC__) || __GLIBC__ > 2 || __GLIBC_MINOR__ >= 16) + vsyscall32 = (void *)getauxval(AT_SYSINFO); + printf("[RUN]\tCheck AT_SYSINFO return regs\n"); + test_sys32_regs(do_full_vsyscall32); +#endif + + test_ptrace_syscall_restart(); + + return 0; +} diff --git a/x86/raw_syscall_helper_32.S b/x86/raw_syscall_helper_32.S new file mode 100644 index 0000000..534e71e --- /dev/null +++ b/x86/raw_syscall_helper_32.S @@ -0,0 +1,46 @@ +.global sys32_helper +sys32_helper: + /* Args: syscall_args_32*, function pointer */ + pushl %ebp + pushl %ebx + pushl %esi + pushl %edi + movl 5*4(%esp), %eax /* pointer to args struct */ + + movl 1*4(%eax), %ebx + movl 2*4(%eax), %ecx + movl 3*4(%eax), %edx + movl 4*4(%eax), %esi + movl 5*4(%eax), %edi + movl 6*4(%eax), %ebp + movl 0*4(%eax), %eax + + call *(6*4)(%esp) /* Do the syscall */ + + /* Now we need to recover without losing any reg values */ + pushl %eax + movl 6*4(%esp), %eax + popl 0*4(%eax) + movl %ebx, 1*4(%eax) + movl %ecx, 2*4(%eax) + movl %edx, 3*4(%eax) + movl %esi, 4*4(%eax) + movl %edi, 5*4(%eax) + movl %ebp, 6*4(%eax) + + popl %edi + popl %esi + popl %ebx + popl %ebp + ret + + .type sys32_helper, @function + .size sys32_helper, .-sys32_helper + +.global int80_and_ret +int80_and_ret: + int $0x80 + ret + + .type int80_and_ret, @function + .size int80_and_ret, .-int80_and_ret diff --git a/x86/sigreturn.c b/x86/sigreturn.c new file mode 100644 index 0000000..b5aa1ba --- /dev/null +++ b/x86/sigreturn.c @@ -0,0 +1,684 @@ +/* + * sigreturn.c - tests for x86 sigreturn(2) and exit-to-userspace + * 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 series of tests that exercises the sigreturn(2) syscall and + * the IRET / SYSRET paths in the kernel. + * + * For now, this focuses on the effects of unusual CS and SS values, + * and it has a bunch of tests to make sure that ESP/RSP is restored + * properly. + * + * The basic idea behind these tests is to raise(SIGUSR1) to create a + * sigcontext frame, plug in the values to be tested, and then return, + * which implicitly invokes sigreturn(2) and programs the user context + * as desired. + * + * For tests for which we expect sigreturn and the subsequent return to + * user mode to succeed, we return to a short trampoline that generates + * SIGTRAP so that the meat of the tests can be ordinary C code in a + * SIGTRAP handler. + * + * The inner workings of each test is documented below. + * + * Do not run on outdated, unpatched kernels at risk of nasty crashes. + */ + +#define _GNU_SOURCE + +#include <sys/time.h> +#include <time.h> +#include <stdlib.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include <sys/mman.h> +#include <sys/signal.h> +#include <sys/ucontext.h> +#include <asm/ldt.h> +#include <err.h> +#include <setjmp.h> +#include <stddef.h> +#include <stdbool.h> +#include <sys/ptrace.h> +#include <sys/user.h> + +/* + * In principle, this test can run on Linux emulation layers (e.g. + * Illumos "LX branded zones"). Solaris-based kernels reserve LDT + * entries 0-5 for their own internal purposes, so start our LDT + * allocations above that reservation. (The tests don't pass on LX + * branded zones, but at least this lets them run.) + */ +#define LDT_OFFSET 6 + +/* An aligned stack accessible through some of our segments. */ +static unsigned char stack16[65536] __attribute__((aligned(4096))); + +/* + * An aligned int3 instruction used as a trampoline. Some of the tests + * want to fish out their ss values, so this trampoline copies ss to eax + * before the int3. + */ +asm (".pushsection .text\n\t" + ".type int3, @function\n\t" + ".align 4096\n\t" + "int3:\n\t" + "mov %ss,%eax\n\t" + "int3\n\t" + ".size int3, . - int3\n\t" + ".align 4096, 0xcc\n\t" + ".popsection"); +extern char int3[4096]; + +/* + * At startup, we prepapre: + * + * - ldt_nonexistent_sel: An LDT entry that doesn't exist (all-zero + * descriptor or out of bounds). + * - code16_sel: A 16-bit LDT code segment pointing to int3. + * - data16_sel: A 16-bit LDT data segment pointing to stack16. + * - npcode32_sel: A 32-bit not-present LDT code segment pointing to int3. + * - npdata32_sel: A 32-bit not-present LDT data segment pointing to stack16. + * - gdt_data16_idx: A 16-bit GDT data segment pointing to stack16. + * - gdt_npdata32_idx: A 32-bit not-present GDT data segment pointing to + * stack16. + * + * For no particularly good reason, xyz_sel is a selector value with the + * RPL and LDT bits filled in, whereas xyz_idx is just an index into the + * descriptor table. These variables will be zero if their respective + * segments could not be allocated. + */ +static unsigned short ldt_nonexistent_sel; +static unsigned short code16_sel, data16_sel, npcode32_sel, npdata32_sel; + +static unsigned short gdt_data16_idx, gdt_npdata32_idx; + +static unsigned short GDT3(int idx) +{ + return (idx << 3) | 3; +} + +static unsigned short LDT3(int idx) +{ + return (idx << 3) | 7; +} + +/* Our sigaltstack scratch space. */ +static char altstack_data[SIGSTKSZ]; + +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"); +} + +static void clearhandler(int sig) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + if (sigaction(sig, &sa, 0)) + err(1, "sigaction"); +} + +static void add_ldt(const struct user_desc *desc, unsigned short *var, + const char *name) +{ + if (syscall(SYS_modify_ldt, 1, desc, sizeof(*desc)) == 0) { + *var = LDT3(desc->entry_number); + } else { + printf("[NOTE]\tFailed to create %s segment\n", name); + *var = 0; + } +} + +static void setup_ldt(void) +{ + if ((unsigned long)stack16 > (1ULL << 32) - sizeof(stack16)) + errx(1, "stack16 is too high\n"); + if ((unsigned long)int3 > (1ULL << 32) - sizeof(int3)) + errx(1, "int3 is too high\n"); + + ldt_nonexistent_sel = LDT3(LDT_OFFSET + 2); + + const struct user_desc code16_desc = { + .entry_number = LDT_OFFSET + 0, + .base_addr = (unsigned long)int3, + .limit = 4095, + .seg_32bit = 0, + .contents = 2, /* Code, not conforming */ + .read_exec_only = 0, + .limit_in_pages = 0, + .seg_not_present = 0, + .useable = 0 + }; + add_ldt(&code16_desc, &code16_sel, "code16"); + + const struct user_desc data16_desc = { + .entry_number = LDT_OFFSET + 1, + .base_addr = (unsigned long)stack16, + .limit = 0xffff, + .seg_32bit = 0, + .contents = 0, /* Data, grow-up */ + .read_exec_only = 0, + .limit_in_pages = 0, + .seg_not_present = 0, + .useable = 0 + }; + add_ldt(&data16_desc, &data16_sel, "data16"); + + const struct user_desc npcode32_desc = { + .entry_number = LDT_OFFSET + 3, + .base_addr = (unsigned long)int3, + .limit = 4095, + .seg_32bit = 1, + .contents = 2, /* Code, not conforming */ + .read_exec_only = 0, + .limit_in_pages = 0, + .seg_not_present = 1, + .useable = 0 + }; + add_ldt(&npcode32_desc, &npcode32_sel, "npcode32"); + + const struct user_desc npdata32_desc = { + .entry_number = LDT_OFFSET + 4, + .base_addr = (unsigned long)stack16, + .limit = 0xffff, + .seg_32bit = 1, + .contents = 0, /* Data, grow-up */ + .read_exec_only = 0, + .limit_in_pages = 0, + .seg_not_present = 1, + .useable = 0 + }; + add_ldt(&npdata32_desc, &npdata32_sel, "npdata32"); + + struct user_desc gdt_data16_desc = { + .entry_number = -1, + .base_addr = (unsigned long)stack16, + .limit = 0xffff, + .seg_32bit = 0, + .contents = 0, /* Data, grow-up */ + .read_exec_only = 0, + .limit_in_pages = 0, + .seg_not_present = 0, + .useable = 0 + }; + + if (syscall(SYS_set_thread_area, &gdt_data16_desc) == 0) { + /* + * This probably indicates vulnerability to CVE-2014-8133. + * Merely getting here isn't definitive, though, and we'll + * diagnose the problem for real later on. + */ + printf("[WARN]\tset_thread_area allocated data16 at index %d\n", + gdt_data16_desc.entry_number); + gdt_data16_idx = gdt_data16_desc.entry_number; + } else { + printf("[OK]\tset_thread_area refused 16-bit data\n"); + } + + struct user_desc gdt_npdata32_desc = { + .entry_number = -1, + .base_addr = (unsigned long)stack16, + .limit = 0xffff, + .seg_32bit = 1, + .contents = 0, /* Data, grow-up */ + .read_exec_only = 0, + .limit_in_pages = 0, + .seg_not_present = 1, + .useable = 0 + }; + + if (syscall(SYS_set_thread_area, &gdt_npdata32_desc) == 0) { + /* + * As a hardening measure, newer kernels don't allow this. + */ + printf("[WARN]\tset_thread_area allocated npdata32 at index %d\n", + gdt_npdata32_desc.entry_number); + gdt_npdata32_idx = gdt_npdata32_desc.entry_number; + } else { + printf("[OK]\tset_thread_area refused 16-bit data\n"); + } +} + +/* State used by our signal handlers. */ +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; + +/* Abstractions for some 32-bit vs 64-bit differences. */ +#ifdef __x86_64__ +# define REG_IP REG_RIP +# define REG_SP REG_RSP +# define REG_AX REG_RAX + +struct selectors { + unsigned short cs, gs, fs, ss; +}; + +static unsigned short *ssptr(ucontext_t *ctx) +{ + struct selectors *sels = (void *)&ctx->uc_mcontext.gregs[REG_CSGSFS]; + return &sels->ss; +} + +static unsigned short *csptr(ucontext_t *ctx) +{ + struct selectors *sels = (void *)&ctx->uc_mcontext.gregs[REG_CSGSFS]; + return &sels->cs; +} +#else +# define REG_IP REG_EIP +# define REG_SP REG_ESP +# define REG_AX REG_EAX + +static greg_t *ssptr(ucontext_t *ctx) +{ + return &ctx->uc_mcontext.gregs[REG_SS]; +} + +static greg_t *csptr(ucontext_t *ctx) +{ + return &ctx->uc_mcontext.gregs[REG_CS]; +} +#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. + */ +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. */ +} + +/* Finds a usable code segment of the requested bitness. */ +int find_cs(int bitness) +{ + unsigned short my_cs; + + asm ("mov %%cs,%0" : "=r" (my_cs)); + + if (cs_bitness(my_cs) == bitness) + return my_cs; + if (cs_bitness(my_cs + (2 << 3)) == bitness) + return my_cs + (2 << 3); + if (my_cs > (2<<3) && cs_bitness(my_cs - (2 << 3)) == bitness) + return my_cs - (2 << 3); + if (cs_bitness(code16_sel) == bitness) + return code16_sel; + + printf("[WARN]\tCould not find %d-bit CS\n", bitness); + return -1; +} + +static int test_valid_sigreturn(int cs_bits, bool use_16bit_ss, int force_ss) +{ + int cs = find_cs(cs_bits); + if (cs == -1) { + printf("[SKIP]\tCode segment unavailable for %d-bit CS, %d-bit SS\n", + cs_bits, use_16bit_ss ? 16 : 32); + return 0; + } + + if (force_ss != -1) { + sig_ss = force_ss; + } else { + if (use_16bit_ss) { + if (!data16_sel) { + printf("[SKIP]\tData segment unavailable for %d-bit CS, 16-bit SS\n", + cs_bits); + return 0; + } + sig_ss = data16_sel; + } else { + asm volatile ("mov %%ss,%0" : "=r" (sig_ss)); + } + } + + sig_cs = cs; + + printf("[RUN]\tValid sigreturn: %d-bit CS (%hx), %d-bit SS (%hx%s)\n", + cs_bits, sig_cs, use_16bit_ss ? 16 : 32, sig_ss, + (sig_ss & 4) ? "" : ", GDT"); + + raise(SIGUSR1); + + nerrs = 0; + + /* + * Check that each register had an acceptable value when the + * int3 trampoline was invoked. + */ + for (int i = 0; i < NGREG; i++) { + greg_t req = requested_regs[i], res = resulting_regs[i]; + if (i == REG_TRAPNO || i == REG_IP) + continue; /* don't care */ + if (i == REG_SP) { + printf("\tSP: %llx -> %llx\n", (unsigned long long)req, + (unsigned long long)res); + + /* + * In many circumstances, the high 32 bits of rsp + * are zeroed. For example, we could be a real + * 32-bit program, or we could hit any of a number + * of poorly-documented IRET or segmented ESP + * oddities. If this happens, it's okay. + */ + if (res == (req & 0xFFFFFFFF)) + continue; /* OK; not expected to work */ + } + + bool ignore_reg = false; +#if __i386__ + if (i == REG_UESP) + ignore_reg = true; +#else + if (i == REG_CSGSFS) { + struct selectors *req_sels = + (void *)&requested_regs[REG_CSGSFS]; + struct selectors *res_sels = + (void *)&resulting_regs[REG_CSGSFS]; + if (req_sels->cs != res_sels->cs) { + printf("[FAIL]\tCS mismatch: requested 0x%hx; got 0x%hx\n", + req_sels->cs, res_sels->cs); + nerrs++; + } + + if (req_sels->ss != res_sels->ss) { + printf("[FAIL]\tSS mismatch: requested 0x%hx; got 0x%hx\n", + req_sels->ss, res_sels->ss); + nerrs++; + } + + continue; + } +#endif + + /* Sanity check on the kernel */ + if (i == REG_AX && requested_regs[i] != resulting_regs[i]) { + printf("[FAIL]\tAX (saved SP) mismatch: requested 0x%llx; got 0x%llx\n", + (unsigned long long)requested_regs[i], + (unsigned long long)resulting_regs[i]); + nerrs++; + continue; + } + + if (requested_regs[i] != resulting_regs[i] && !ignore_reg) { + /* + * SP is particularly interesting here. The + * usual cause of failures is that we hit the + * nasty IRET case of returning to a 16-bit SS, + * in which case bits 16:31 of the *kernel* + * stack pointer persist in ESP. + */ + printf("[FAIL]\tReg %d mismatch: requested 0x%llx; got 0x%llx\n", + i, (unsigned long long)requested_regs[i], + (unsigned long long)resulting_regs[i]); + nerrs++; + } + } + + if (nerrs == 0) + printf("[OK]\tall registers okay\n"); + + return nerrs; +} + +static int test_bad_iret(int cs_bits, unsigned short ss, int force_cs) +{ + int cs = force_cs == -1 ? find_cs(cs_bits) : force_cs; + if (cs == -1) + return 0; + + sig_cs = cs; + sig_ss = ss; + + printf("[RUN]\t%d-bit CS (%hx), bogus SS (%hx)\n", + cs_bits, sig_cs, sig_ss); + + sig_trapped = 0; + raise(SIGUSR1); + if (sig_trapped) { + char errdesc[32] = ""; + if (sig_err) { + const char *src = (sig_err & 1) ? " EXT" : ""; + const char *table; + if ((sig_err & 0x6) == 0x0) + table = "GDT"; + else if ((sig_err & 0x6) == 0x4) + table = "LDT"; + else if ((sig_err & 0x6) == 0x2) + table = "IDT"; + else + table = "???"; + + sprintf(errdesc, "%s%s index %d, ", + table, src, sig_err >> 3); + } + + char trapname[32]; + if (sig_trapno == 13) + strcpy(trapname, "GP"); + else if (sig_trapno == 11) + strcpy(trapname, "NP"); + else if (sig_trapno == 12) + strcpy(trapname, "SS"); + else if (sig_trapno == 32) + strcpy(trapname, "IRET"); /* X86_TRAP_IRET */ + else + sprintf(trapname, "%d", sig_trapno); + + printf("[OK]\tGot #%s(0x%lx) (i.e. %s%s)\n", + trapname, (unsigned long)sig_err, + errdesc, strsignal(sig_trapped)); + return 0; + } else { + printf("[FAIL]\tDid not get SIGSEGV\n"); + return 1; + } +} + +int main() +{ + int total_nerrs = 0; + unsigned short my_cs, my_ss; + + asm volatile ("mov %%cs,%0" : "=r" (my_cs)); + asm volatile ("mov %%ss,%0" : "=r" (my_ss)); + setup_ldt(); + + stack_t stack = { + .ss_sp = altstack_data, + .ss_size = SIGSTKSZ, + }; + if (sigaltstack(&stack, NULL) != 0) + err(1, "sigaltstack"); + + sethandler(SIGUSR1, sigusr1, 0); + sethandler(SIGTRAP, sigtrap, SA_ONSTACK); + + /* Easy cases: return to a 32-bit SS in each possible CS bitness. */ + total_nerrs += test_valid_sigreturn(64, false, -1); + total_nerrs += test_valid_sigreturn(32, false, -1); + total_nerrs += test_valid_sigreturn(16, false, -1); + + /* + * Test easy espfix cases: return to a 16-bit LDT SS in each possible + * CS bitness. NB: with a long mode CS, the SS bitness is irrelevant. + * + * This catches the original missing-espfix-on-64-bit-kernels issue + * as well as CVE-2014-8134. + */ + total_nerrs += test_valid_sigreturn(64, true, -1); + total_nerrs += test_valid_sigreturn(32, true, -1); + total_nerrs += test_valid_sigreturn(16, true, -1); + + if (gdt_data16_idx) { + /* + * For performance reasons, Linux skips espfix if SS points + * to the GDT. If we were able to allocate a 16-bit SS in + * the GDT, see if it leaks parts of the kernel stack pointer. + * + * This tests for CVE-2014-8133. + */ + total_nerrs += test_valid_sigreturn(64, true, + GDT3(gdt_data16_idx)); + total_nerrs += test_valid_sigreturn(32, true, + GDT3(gdt_data16_idx)); + total_nerrs += test_valid_sigreturn(16, true, + GDT3(gdt_data16_idx)); + } + + /* + * We're done testing valid sigreturn cases. Now we test states + * for which sigreturn itself will succeed but the subsequent + * entry to user mode will fail. + * + * Depending on the failure mode and the kernel bitness, these + * entry failures can generate SIGSEGV, SIGBUS, or SIGILL. + */ + clearhandler(SIGTRAP); + sethandler(SIGSEGV, sigtrap, SA_ONSTACK); + sethandler(SIGBUS, sigtrap, SA_ONSTACK); + sethandler(SIGILL, sigtrap, SA_ONSTACK); /* 32-bit kernels do this */ + + /* Easy failures: invalid SS, resulting in #GP(0) */ + test_bad_iret(64, ldt_nonexistent_sel, -1); + test_bad_iret(32, ldt_nonexistent_sel, -1); + test_bad_iret(16, ldt_nonexistent_sel, -1); + + /* These fail because SS isn't a data segment, resulting in #GP(SS) */ + test_bad_iret(64, my_cs, -1); + test_bad_iret(32, my_cs, -1); + test_bad_iret(16, my_cs, -1); + + /* Try to return to a not-present code segment, triggering #NP(SS). */ + test_bad_iret(32, my_ss, npcode32_sel); + + /* + * Try to return to a not-present but otherwise valid data segment. + * This will cause IRET to fail with #SS on the espfix stack. This + * exercises CVE-2014-9322. + * + * Note that, if espfix is enabled, 64-bit Linux will lose track + * of the actual cause of failure and report #GP(0) instead. + * This would be very difficult for Linux to avoid, because + * espfix64 causes IRET failures to be promoted to #DF, so the + * original exception frame is never pushed onto the stack. + */ + test_bad_iret(32, npdata32_sel, -1); + + /* + * Try to return to a not-present but otherwise valid data + * segment without invoking espfix. Newer kernels don't allow + * this to happen in the first place. On older kernels, though, + * this can trigger CVE-2014-9322. + */ + if (gdt_npdata32_idx) + test_bad_iret(32, GDT3(gdt_npdata32_idx), -1); + + return total_nerrs ? 1 : 0; +} diff --git a/x86/single_step_syscall.c b/x86/single_step_syscall.c new file mode 100644 index 0000000..50c2635 --- /dev/null +++ b/x86/single_step_syscall.c @@ -0,0 +1,181 @@ +/* + * 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 + * immediately issues #DB from CPL 0. This requires special handling in + * the kernel. + */ + +#define _GNU_SOURCE + +#include <sys/time.h> +#include <time.h> +#include <stdlib.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include <sys/mman.h> +#include <sys/signal.h> +#include <sys/ucontext.h> +#include <asm/ldt.h> +#include <err.h> +#include <setjmp.h> +#include <stddef.h> +#include <stdbool.h> +#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"); +} + +static volatile sig_atomic_t sig_traps; + +#ifdef __x86_64__ +# define REG_IP REG_RIP +# define WIDTH "q" +#else +# define REG_IP REG_EIP +# define WIDTH "l" +#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; + + if (get_eflags() & X86_EFLAGS_TF) { + set_eflags(get_eflags() & ~X86_EFLAGS_TF); + printf("[WARN]\tSIGTRAP handler had TF set\n"); + _exit(1); + } + + sig_traps++; + + if (sig_traps == 10000 || sig_traps == 10001) { + printf("[WARN]\tHit %d SIGTRAPs with si_addr 0x%lx, ip 0x%lx\n", + (int)sig_traps, + (unsigned long)info->si_addr, + (unsigned long)ctx->uc_mcontext.gregs[REG_IP]); + } +} + +static void check_result(void) +{ + unsigned long new_eflags = get_eflags(); + set_eflags(new_eflags & ~X86_EFLAGS_TF); + + if (!sig_traps) { + printf("[FAIL]\tNo SIGTRAP\n"); + exit(1); + } + + if (!(new_eflags & X86_EFLAGS_TF)) { + printf("[FAIL]\tTF was cleared\n"); + exit(1); + } + + printf("[OK]\tSurvived with TF set and %d traps\n", (int)sig_traps); + sig_traps = 0; +} + +int main() +{ + int tmp; + + sethandler(SIGTRAP, sigtrap, 0); + + printf("[RUN]\tSet TF and check nop\n"); + set_eflags(get_eflags() | X86_EFLAGS_TF); + asm volatile ("nop"); + check_result(); + +#ifdef __x86_64__ + printf("[RUN]\tSet TF and check syscall-less opportunistic sysret\n"); + set_eflags(get_eflags() | X86_EFLAGS_TF); + extern unsigned char post_nop[]; + asm volatile ("pushf" WIDTH "\n\t" + "pop" WIDTH " %%r11\n\t" + "nop\n\t" + "post_nop:" + : : "c" (post_nop) : "r11"); + check_result(); +#endif + + printf("[RUN]\tSet TF and check int80\n"); + set_eflags(get_eflags() | X86_EFLAGS_TF); + asm volatile ("int $0x80" : "=a" (tmp) : "a" (SYS_getpid)); + check_result(); + + /* + * This test is particularly interesting if fast syscalls use + * SYSENTER: it triggers a nasty design flaw in SYSENTER. + * Specifically, SYSENTER does not clear TF, so either SYSENTER + * or the next instruction traps at CPL0. (Of course, Intel + * mostly forgot to document exactly what happens here.) So we + * get a CPL0 fault with usergs (on 64-bit kernels) and possibly + * no stack. The only sane way the kernel can possibly handle + * it is to clear TF on return from the #DB handler, but this + * happens way too early to set TF in the saved pt_regs, so the + * kernel has to do something clever to avoid losing track of + * the TF bit. + * + * Needless to say, we've had bugs in this area. + */ + syscall(SYS_getpid); /* Force symbol binding without TF set. */ + printf("[RUN]\tSet TF and check a fast syscall\n"); + set_eflags(get_eflags() | X86_EFLAGS_TF); + syscall(SYS_getpid); + 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); + } + if (sig_traps) { + printf("[FAIL]\tGot SIGTRAP\n"); + exit(1); + } + printf("[OK]\tNothing unexpected happened\n"); + + return 0; +} diff --git a/x86/syscall_arg_fault.c b/x86/syscall_arg_fault.c new file mode 100644 index 0000000..7db4fc9 --- /dev/null +++ b/x86/syscall_arg_fault.c @@ -0,0 +1,130 @@ +/* + * syscall_arg_fault.c - tests faults 32-bit fast syscall stack args + * Copyright (c) 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. + */ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/signal.h> +#include <sys/ucontext.h> +#include <err.h> +#include <setjmp.h> +#include <errno.h> + +/* Our sigaltstack scratch space. */ +static unsigned char altstack_data[SIGSTKSZ]; + +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"); +} + +static volatile sig_atomic_t sig_traps; +static sigjmp_buf jmpbuf; + +static volatile sig_atomic_t n_errs; + +static void sigsegv(int sig, siginfo_t *info, void *ctx_void) +{ + ucontext_t *ctx = (ucontext_t*)ctx_void; + + if (ctx->uc_mcontext.gregs[REG_EAX] != -EFAULT) { + printf("[FAIL]\tAX had the wrong value: 0x%x\n", + ctx->uc_mcontext.gregs[REG_EAX]); + n_errs++; + } else { + printf("[OK]\tSeems okay\n"); + } + + siglongjmp(jmpbuf, 1); +} + +static void sigill(int sig, siginfo_t *info, void *ctx_void) +{ + printf("[SKIP]\tIllegal instruction\n"); + siglongjmp(jmpbuf, 1); +} + +int main() +{ + stack_t stack = { + .ss_sp = altstack_data, + .ss_size = SIGSTKSZ, + }; + if (sigaltstack(&stack, NULL) != 0) + err(1, "sigaltstack"); + + sethandler(SIGSEGV, sigsegv, SA_ONSTACK); + sethandler(SIGILL, sigill, SA_ONSTACK); + + /* + * Exercise another nasty special case. The 32-bit SYSCALL + * and SYSENTER instructions (even in compat mode) each + * clobber one register. A Linux system call has a syscall + * number and six arguments, and the user stack pointer + * needs to live in some register on return. That means + * that we need eight registers, but SYSCALL and SYSENTER + * only preserve seven registers. As a result, one argument + * ends up on the stack. The stack is user memory, which + * means that the kernel can fail to read it. + * + * The 32-bit fast system calls don't have a defined ABI: + * we're supposed to invoke them through the vDSO. So we'll + * fudge it: we set all regs to invalid pointer values and + * invoke the entry instruction. The return will fail no + * matter what, and we completely lose our program state, + * but we can fix it up with a signal handler. + */ + + printf("[RUN]\tSYSENTER with invalid state\n"); + if (sigsetjmp(jmpbuf, 1) == 0) { + asm volatile ( + "movl $-1, %%eax\n\t" + "movl $-1, %%ebx\n\t" + "movl $-1, %%ecx\n\t" + "movl $-1, %%edx\n\t" + "movl $-1, %%esi\n\t" + "movl $-1, %%edi\n\t" + "movl $-1, %%ebp\n\t" + "movl $-1, %%esp\n\t" + "sysenter" + : : : "memory", "flags"); + } + + printf("[RUN]\tSYSCALL with invalid state\n"); + if (sigsetjmp(jmpbuf, 1) == 0) { + asm volatile ( + "movl $-1, %%eax\n\t" + "movl $-1, %%ebx\n\t" + "movl $-1, %%ecx\n\t" + "movl $-1, %%edx\n\t" + "movl $-1, %%esi\n\t" + "movl $-1, %%edi\n\t" + "movl $-1, %%ebp\n\t" + "movl $-1, %%esp\n\t" + "syscall\n\t" + "pushl $0" /* make sure we segfault cleanly */ + : : : "memory", "flags"); + } + + return 0; +} diff --git a/x86/syscall_nt.c b/x86/syscall_nt.c new file mode 100644 index 0000000..60c06af --- /dev/null +++ b/x86/syscall_nt.c @@ -0,0 +1,54 @@ +/* + * syscall_nt.c - checks syscalls with NT set + * 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. + * + * Some obscure user-space code requires the ability to make system calls + * with FLAGS.NT set. Make sure it works. + */ + +#include <stdio.h> +#include <unistd.h> +#include <sys/syscall.h> +#include <asm/processor-flags.h> + +#ifdef __x86_64__ +# define WIDTH "q" +#else +# define WIDTH "l" +#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"); +} + +int main() +{ + printf("[RUN]\tSet NT and issue a syscall\n"); + set_eflags(get_eflags() | X86_EFLAGS_NT); + syscall(SYS_getpid); + if (get_eflags() & X86_EFLAGS_NT) { + printf("[OK]\tThe syscall worked and NT is still set\n"); + return 0; + } else { + printf("[FAIL]\tThe syscall worked but NT was cleared\n"); + return 1; + } +} diff --git a/x86/sysret_ss_attrs.c b/x86/sysret_ss_attrs.c new file mode 100644 index 0000000..ce42d5a --- /dev/null +++ b/x86/sysret_ss_attrs.c @@ -0,0 +1,112 @@ +/* + * sysret_ss_attrs.c - test that syscalls return valid hidden SS attributes + * Copyright (c) 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. + * + * On AMD CPUs, SYSRET can return with a valid SS descriptor with with + * the hidden attributes set to an unusable state. Make sure the kernel + * doesn't let this happen. + */ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <sys/mman.h> +#include <err.h> +#include <stddef.h> +#include <stdbool.h> +#include <pthread.h> + +static void *threadproc(void *ctx) +{ + /* + * Do our best to cause sleeps on this CPU to exit the kernel and + * re-enter with SS = 0. + */ + while (true) + ; + + return NULL; +} + +#ifdef __x86_64__ +extern unsigned long call32_from_64(void *stack, void (*function)(void)); + +asm (".pushsection .text\n\t" + ".code32\n\t" + "test_ss:\n\t" + "pushl $0\n\t" + "popl %eax\n\t" + "ret\n\t" + ".code64"); +extern void test_ss(void); +#endif + +int main() +{ + /* + * Start a busy-looping thread on the same CPU we're on. + * For simplicity, just stick everything to CPU 0. This will + * fail in some containers, but that's probably okay. + */ + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(0, &cpuset); + if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) + printf("[WARN]\tsched_setaffinity failed\n"); + + pthread_t thread; + if (pthread_create(&thread, 0, threadproc, 0) != 0) + err(1, "pthread_create"); + +#ifdef __x86_64__ + unsigned char *stack32 = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_32BIT | MAP_ANONYMOUS | MAP_PRIVATE, + -1, 0); + if (stack32 == MAP_FAILED) + err(1, "mmap"); +#endif + + printf("[RUN]\tSyscalls followed by SS validation\n"); + + for (int i = 0; i < 1000; i++) { + /* + * Go to sleep and return using sysret (if we're 64-bit + * or we're 32-bit on AMD on a 64-bit kernel). On AMD CPUs, + * SYSRET doesn't fix up the cached SS descriptor, so the + * kernel needs some kind of workaround to make sure that we + * end the system call with a valid stack segment. This + * can be a confusing failure because the SS *selector* + * is the same regardless. + */ + usleep(2); + +#ifdef __x86_64__ + /* + * On 32-bit, just doing a syscall through glibc is enough + * to cause a crash if our cached SS descriptor is invalid. + * On 64-bit, it's not, so try extra hard. + */ + call32_from_64(stack32 + 4088, test_ss); +#endif + } + + printf("[OK]\tWe survived\n"); + +#ifdef __x86_64__ + munmap(stack32, 4096); +#endif + + return 0; +} diff --git a/x86/test_FCMOV.c b/x86/test_FCMOV.c new file mode 100644 index 0000000..4adcca0 --- /dev/null +++ b/x86/test_FCMOV.c @@ -0,0 +1,93 @@ +#undef _GNU_SOURCE +#define _GNU_SOURCE 1 +#undef __USE_GNU +#define __USE_GNU 1 +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/select.h> +#include <sys/time.h> +#include <sys/wait.h> + +#define TEST(insn) \ +long double __attribute__((noinline)) insn(long flags) \ +{ \ + long double out; \ + asm ("\n" \ + " push %1""\n" \ + " popf""\n" \ + " fldpi""\n" \ + " fld1""\n" \ + " " #insn " %%st(1), %%st" "\n" \ + " ffree %%st(1)" "\n" \ + : "=t" (out) \ + : "r" (flags) \ + ); \ + return out; \ +} + +TEST(fcmovb) +TEST(fcmove) +TEST(fcmovbe) +TEST(fcmovu) +TEST(fcmovnb) +TEST(fcmovne) +TEST(fcmovnbe) +TEST(fcmovnu) + +enum { + CF = 1 << 0, + PF = 1 << 2, + ZF = 1 << 6, +}; + +void sighandler(int sig) +{ + printf("[FAIL]\tGot signal %d, exiting\n", sig); + exit(1); +} + +int main(int argc, char **argv, char **envp) +{ + int err = 0; + + /* SIGILL triggers on 32-bit kernels w/o fcomi emulation + * when run with "no387 nofxsr". Other signals are caught + * just in case. + */ + signal(SIGILL, sighandler); + signal(SIGFPE, sighandler); + signal(SIGSEGV, sighandler); + + printf("[RUN]\tTesting fcmovCC instructions\n"); + /* If fcmovCC() returns 1.0, the move wasn't done */ + err |= !(fcmovb(0) == 1.0); err |= !(fcmovnb(0) != 1.0); + err |= !(fcmove(0) == 1.0); err |= !(fcmovne(0) != 1.0); + err |= !(fcmovbe(0) == 1.0); err |= !(fcmovnbe(0) != 1.0); + err |= !(fcmovu(0) == 1.0); err |= !(fcmovnu(0) != 1.0); + + err |= !(fcmovb(CF) != 1.0); err |= !(fcmovnb(CF) == 1.0); + err |= !(fcmove(CF) == 1.0); err |= !(fcmovne(CF) != 1.0); + err |= !(fcmovbe(CF) != 1.0); err |= !(fcmovnbe(CF) == 1.0); + err |= !(fcmovu(CF) == 1.0); err |= !(fcmovnu(CF) != 1.0); + + err |= !(fcmovb(ZF) == 1.0); err |= !(fcmovnb(ZF) != 1.0); + err |= !(fcmove(ZF) != 1.0); err |= !(fcmovne(ZF) == 1.0); + err |= !(fcmovbe(ZF) != 1.0); err |= !(fcmovnbe(ZF) == 1.0); + err |= !(fcmovu(ZF) == 1.0); err |= !(fcmovnu(ZF) != 1.0); + + err |= !(fcmovb(PF) == 1.0); err |= !(fcmovnb(PF) != 1.0); + err |= !(fcmove(PF) == 1.0); err |= !(fcmovne(PF) != 1.0); + err |= !(fcmovbe(PF) == 1.0); err |= !(fcmovnbe(PF) != 1.0); + err |= !(fcmovu(PF) != 1.0); err |= !(fcmovnu(PF) == 1.0); + + if (!err) + printf("[OK]\tfcmovCC\n"); + else + printf("[FAIL]\tfcmovCC errors: %d\n", err); + + return err; +} diff --git a/x86/test_FCOMI.c b/x86/test_FCOMI.c new file mode 100644 index 0000000..db4933e --- /dev/null +++ b/x86/test_FCOMI.c @@ -0,0 +1,331 @@ +#undef _GNU_SOURCE +#define _GNU_SOURCE 1 +#undef __USE_GNU +#define __USE_GNU 1 +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/select.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <fenv.h> + +enum { + CF = 1 << 0, + PF = 1 << 2, + ZF = 1 << 6, + ARITH = CF | PF | ZF, +}; + +long res_fcomi_pi_1; +long res_fcomi_1_pi; +long res_fcomi_1_1; +long res_fcomi_nan_1; +/* sNaN is s|111 1111 1|1xx xxxx xxxx xxxx xxxx xxxx */ +/* qNaN is s|111 1111 1|0xx xxxx xxxx xxxx xxxx xxxx (some x must be nonzero) */ +int snan = 0x7fc11111; +int qnan = 0x7f811111; +unsigned short snan1[5]; +/* sNaN80 is s|111 1111 1111 1111 |10xx xx...xx (some x must be nonzero) */ +unsigned short snan80[5] = { 0x1111, 0x1111, 0x1111, 0x8111, 0x7fff }; + +int test(long flags) +{ + feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + + asm ("\n" + + " push %0""\n" + " popf""\n" + " fld1""\n" + " fldpi""\n" + " fcomi %%st(1), %%st" "\n" + " ffree %%st(0)" "\n" + " ffree %%st(1)" "\n" + " pushf""\n" + " pop res_fcomi_1_pi""\n" + + " push %0""\n" + " popf""\n" + " fldpi""\n" + " fld1""\n" + " fcomi %%st(1), %%st" "\n" + " ffree %%st(0)" "\n" + " ffree %%st(1)" "\n" + " pushf""\n" + " pop res_fcomi_pi_1""\n" + + " push %0""\n" + " popf""\n" + " fld1""\n" + " fld1""\n" + " fcomi %%st(1), %%st" "\n" + " ffree %%st(0)" "\n" + " ffree %%st(1)" "\n" + " pushf""\n" + " pop res_fcomi_1_1""\n" + : + : "r" (flags) + ); + if ((res_fcomi_1_pi & ARITH) != (0)) { + printf("[BAD]\tfcomi_1_pi with flags:%lx\n", flags); + return 1; + } + if ((res_fcomi_pi_1 & ARITH) != (CF)) { + printf("[BAD]\tfcomi_pi_1 with flags:%lx->%lx\n", flags, res_fcomi_pi_1 & ARITH); + return 1; + } + if ((res_fcomi_1_1 & ARITH) != (ZF)) { + printf("[BAD]\tfcomi_1_1 with flags:%lx\n", flags); + return 1; + } + if (fetestexcept(FE_INVALID) != 0) { + printf("[BAD]\tFE_INVALID is set in %s\n", __func__); + return 1; + } + return 0; +} + +int test_qnan(long flags) +{ + feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + + asm ("\n" + " push %0""\n" + " popf""\n" + " flds qnan""\n" + " fld1""\n" + " fnclex""\n" // fld of a qnan raised FE_INVALID, clear it + " fcomi %%st(1), %%st" "\n" + " ffree %%st(0)" "\n" + " ffree %%st(1)" "\n" + " pushf""\n" + " pop res_fcomi_nan_1""\n" + : + : "r" (flags) + ); + if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) { + printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags); + return 1; + } + if (fetestexcept(FE_INVALID) != FE_INVALID) { + printf("[BAD]\tFE_INVALID is not set in %s\n", __func__); + return 1; + } + return 0; +} + +int testu_qnan(long flags) +{ + feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + + asm ("\n" + " push %0""\n" + " popf""\n" + " flds qnan""\n" + " fld1""\n" + " fnclex""\n" // fld of a qnan raised FE_INVALID, clear it + " fucomi %%st(1), %%st" "\n" + " ffree %%st(0)" "\n" + " ffree %%st(1)" "\n" + " pushf""\n" + " pop res_fcomi_nan_1""\n" + : + : "r" (flags) + ); + if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) { + printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags); + return 1; + } + if (fetestexcept(FE_INVALID) != 0) { + printf("[BAD]\tFE_INVALID is set in %s\n", __func__); + return 1; + } + return 0; +} + +int testu_snan(long flags) +{ + feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + + asm ("\n" + " push %0""\n" + " popf""\n" +// " flds snan""\n" // WRONG, this will convert 32-bit fp snan to a *qnan* in 80-bit fp register! +// " fstpt snan1""\n" // if uncommented, it prints "snan1:7fff c111 1100 0000 0000" - c111, not 8111! +// " fnclex""\n" // flds of a snan raised FE_INVALID, clear it + " fldt snan80""\n" // fldt never raise FE_INVALID + " fld1""\n" + " fucomi %%st(1), %%st" "\n" + " ffree %%st(0)" "\n" + " ffree %%st(1)" "\n" + " pushf""\n" + " pop res_fcomi_nan_1""\n" + : + : "r" (flags) + ); + if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) { + printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags); + return 1; + } +// printf("snan:%x snan1:%04x %04x %04x %04x %04x\n", snan, snan1[4], snan1[3], snan1[2], snan1[1], snan1[0]); + if (fetestexcept(FE_INVALID) != FE_INVALID) { + printf("[BAD]\tFE_INVALID is not set in %s\n", __func__); + return 1; + } + return 0; +} + +int testp(long flags) +{ + feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + + asm ("\n" + + " push %0""\n" + " popf""\n" + " fld1""\n" + " fldpi""\n" + " fcomip %%st(1), %%st" "\n" + " ffree %%st(0)" "\n" + " pushf""\n" + " pop res_fcomi_1_pi""\n" + + " push %0""\n" + " popf""\n" + " fldpi""\n" + " fld1""\n" + " fcomip %%st(1), %%st" "\n" + " ffree %%st(0)" "\n" + " pushf""\n" + " pop res_fcomi_pi_1""\n" + + " push %0""\n" + " popf""\n" + " fld1""\n" + " fld1""\n" + " fcomip %%st(1), %%st" "\n" + " ffree %%st(0)" "\n" + " pushf""\n" + " pop res_fcomi_1_1""\n" + : + : "r" (flags) + ); + if ((res_fcomi_1_pi & ARITH) != (0)) { + printf("[BAD]\tfcomi_1_pi with flags:%lx\n", flags); + return 1; + } + if ((res_fcomi_pi_1 & ARITH) != (CF)) { + printf("[BAD]\tfcomi_pi_1 with flags:%lx->%lx\n", flags, res_fcomi_pi_1 & ARITH); + return 1; + } + if ((res_fcomi_1_1 & ARITH) != (ZF)) { + printf("[BAD]\tfcomi_1_1 with flags:%lx\n", flags); + return 1; + } + if (fetestexcept(FE_INVALID) != 0) { + printf("[BAD]\tFE_INVALID is set in %s\n", __func__); + return 1; + } + return 0; +} + +int testp_qnan(long flags) +{ + feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + + asm ("\n" + " push %0""\n" + " popf""\n" + " flds qnan""\n" + " fld1""\n" + " fnclex""\n" // fld of a qnan raised FE_INVALID, clear it + " fcomip %%st(1), %%st" "\n" + " ffree %%st(0)" "\n" + " pushf""\n" + " pop res_fcomi_nan_1""\n" + : + : "r" (flags) + ); + if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) { + printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags); + return 1; + } + if (fetestexcept(FE_INVALID) != FE_INVALID) { + printf("[BAD]\tFE_INVALID is not set in %s\n", __func__); + return 1; + } + return 0; +} + +int testup_qnan(long flags) +{ + feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + + asm ("\n" + " push %0""\n" + " popf""\n" + " flds qnan""\n" + " fld1""\n" + " fnclex""\n" // fld of a qnan raised FE_INVALID, clear it + " fucomip %%st(1), %%st" "\n" + " ffree %%st(0)" "\n" + " pushf""\n" + " pop res_fcomi_nan_1""\n" + : + : "r" (flags) + ); + if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) { + printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags); + return 1; + } + if (fetestexcept(FE_INVALID) != 0) { + printf("[BAD]\tFE_INVALID is set in %s\n", __func__); + return 1; + } + return 0; +} + +void sighandler(int sig) +{ + printf("[FAIL]\tGot signal %d, exiting\n", sig); + exit(1); +} + +int main(int argc, char **argv, char **envp) +{ + int err = 0; + + /* SIGILL triggers on 32-bit kernels w/o fcomi emulation + * when run with "no387 nofxsr". Other signals are caught + * just in case. + */ + signal(SIGILL, sighandler); + signal(SIGFPE, sighandler); + signal(SIGSEGV, sighandler); + + printf("[RUN]\tTesting f[u]comi[p] instructions\n"); + err |= test(0); + err |= test_qnan(0); + err |= testu_qnan(0); + err |= testu_snan(0); + err |= test(CF|ZF|PF); + err |= test_qnan(CF|ZF|PF); + err |= testu_qnan(CF|ZF|PF); + err |= testu_snan(CF|ZF|PF); + err |= testp(0); + err |= testp_qnan(0); + err |= testup_qnan(0); + err |= testp(CF|ZF|PF); + err |= testp_qnan(CF|ZF|PF); + err |= testup_qnan(CF|ZF|PF); + if (!err) + printf("[OK]\tf[u]comi[p]\n"); + else + printf("[FAIL]\tf[u]comi[p] errors: %d\n", err); + + return err; +} diff --git a/x86/test_FISTTP.c b/x86/test_FISTTP.c new file mode 100644 index 0000000..b8e61a0 --- /dev/null +++ b/x86/test_FISTTP.c @@ -0,0 +1,137 @@ +#undef _GNU_SOURCE +#define _GNU_SOURCE 1 +#undef __USE_GNU +#define __USE_GNU 1 +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/select.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <fenv.h> + +unsigned long long res64 = -1; +unsigned int res32 = -1; +unsigned short res16 = -1; + +int test(void) +{ + int ex; + + feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + asm volatile ("\n" + " fld1""\n" + " fisttp res16""\n" + " fld1""\n" + " fisttpl res32""\n" + " fld1""\n" + " fisttpll res64""\n" + : : : "memory" + ); + if (res16 != 1 || res32 != 1 || res64 != 1) { + printf("[BAD]\tfisttp 1\n"); + return 1; + } + ex = fetestexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + if (ex != 0) { + printf("[BAD]\tfisttp 1: wrong exception state\n"); + return 1; + } + + feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + asm volatile ("\n" + " fldpi""\n" + " fisttp res16""\n" + " fldpi""\n" + " fisttpl res32""\n" + " fldpi""\n" + " fisttpll res64""\n" + : : : "memory" + ); + if (res16 != 3 || res32 != 3 || res64 != 3) { + printf("[BAD]\tfisttp pi\n"); + return 1; + } + ex = fetestexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + if (ex != FE_INEXACT) { + printf("[BAD]\tfisttp pi: wrong exception state\n"); + return 1; + } + + feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + asm volatile ("\n" + " fldpi""\n" + " fchs""\n" + " fisttp res16""\n" + " fldpi""\n" + " fchs""\n" + " fisttpl res32""\n" + " fldpi""\n" + " fchs""\n" + " fisttpll res64""\n" + : : : "memory" + ); + if (res16 != 0xfffd || res32 != 0xfffffffd || res64 != 0xfffffffffffffffdULL) { + printf("[BAD]\tfisttp -pi\n"); + return 1; + } + ex = fetestexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + if (ex != FE_INEXACT) { + printf("[BAD]\tfisttp -pi: wrong exception state\n"); + return 1; + } + + feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + asm volatile ("\n" + " fldln2""\n" + " fisttp res16""\n" + " fldln2""\n" + " fisttpl res32""\n" + " fldln2""\n" + " fisttpll res64""\n" + : : : "memory" + ); + /* Test truncation to zero (round-to-nearest would give 1 here) */ + if (res16 != 0 || res32 != 0 || res64 != 0) { + printf("[BAD]\tfisttp ln2\n"); + return 1; + } + ex = fetestexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); + if (ex != FE_INEXACT) { + printf("[BAD]\tfisttp ln2: wrong exception state\n"); + return 1; + } + + return 0; +} + +void sighandler(int sig) +{ + printf("[FAIL]\tGot signal %d, exiting\n", sig); + exit(1); +} + +int main(int argc, char **argv, char **envp) +{ + int err = 0; + + /* SIGILL triggers on 32-bit kernels w/o fisttp emulation + * when run with "no387 nofxsr". Other signals are caught + * just in case. + */ + signal(SIGILL, sighandler); + signal(SIGFPE, sighandler); + signal(SIGSEGV, sighandler); + + printf("[RUN]\tTesting fisttp instructions\n"); + err |= test(); + if (!err) + printf("[OK]\tfisttp\n"); + else + printf("[FAIL]\tfisttp errors: %d\n", err); + + return err; +} diff --git a/x86/test_syscall_vdso.c b/x86/test_syscall_vdso.c new file mode 100644 index 0000000..4037035 --- /dev/null +++ b/x86/test_syscall_vdso.c @@ -0,0 +1,401 @@ +/* + * 32-bit syscall ABI conformance test. + * + * Copyright (c) 2015 Denys Vlasenko + * + * 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. + */ +/* + * Can be built statically: + * gcc -Os -Wall -static -m32 test_syscall_vdso.c thunks_32.S + */ +#undef _GNU_SOURCE +#define _GNU_SOURCE 1 +#undef __USE_GNU +#define __USE_GNU 1 +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/select.h> +#include <sys/time.h> +#include <elf.h> +#include <sys/ptrace.h> +#include <sys/wait.h> + +#if !defined(__i386__) +int main(int argc, char **argv, char **envp) +{ + printf("[SKIP]\tNot a 32-bit x86 userspace\n"); + return 0; +} +#else + +long syscall_addr; +long get_syscall(char **envp) +{ + Elf32_auxv_t *auxv; + while (*envp++ != NULL) + continue; + for (auxv = (void *)envp; auxv->a_type != AT_NULL; auxv++) + if (auxv->a_type == AT_SYSINFO) + return auxv->a_un.a_val; + printf("[WARN]\tAT_SYSINFO not supplied\n"); + return 0; +} + +asm ( + " .pushsection .text\n" + " .global int80\n" + "int80:\n" + " int $0x80\n" + " ret\n" + " .popsection\n" +); +extern char int80; + +struct regs64 { + uint64_t rax, rbx, rcx, rdx; + uint64_t rsi, rdi, rbp, rsp; + uint64_t r8, r9, r10, r11; + uint64_t r12, r13, r14, r15; +}; +struct regs64 regs64; +int kernel_is_64bit; + +asm ( + " .pushsection .text\n" + " .code64\n" + "get_regs64:\n" + " push %rax\n" + " mov $regs64, %eax\n" + " pop 0*8(%rax)\n" + " movq %rbx, 1*8(%rax)\n" + " movq %rcx, 2*8(%rax)\n" + " movq %rdx, 3*8(%rax)\n" + " movq %rsi, 4*8(%rax)\n" + " movq %rdi, 5*8(%rax)\n" + " movq %rbp, 6*8(%rax)\n" + " movq %rsp, 7*8(%rax)\n" + " movq %r8, 8*8(%rax)\n" + " movq %r9, 9*8(%rax)\n" + " movq %r10, 10*8(%rax)\n" + " movq %r11, 11*8(%rax)\n" + " movq %r12, 12*8(%rax)\n" + " movq %r13, 13*8(%rax)\n" + " movq %r14, 14*8(%rax)\n" + " movq %r15, 15*8(%rax)\n" + " ret\n" + "poison_regs64:\n" + " movq $0x7f7f7f7f, %r8\n" + " shl $32, %r8\n" + " orq $0x7f7f7f7f, %r8\n" + " movq %r8, %r9\n" + " movq %r8, %r10\n" + " movq %r8, %r11\n" + " movq %r8, %r12\n" + " movq %r8, %r13\n" + " movq %r8, %r14\n" + " movq %r8, %r15\n" + " ret\n" + " .code32\n" + " .popsection\n" +); +extern void get_regs64(void); +extern void poison_regs64(void); +extern unsigned long call64_from_32(void (*function)(void)); +void print_regs64(void) +{ + if (!kernel_is_64bit) + return; + printf("ax:%016llx bx:%016llx cx:%016llx dx:%016llx\n", regs64.rax, regs64.rbx, regs64.rcx, regs64.rdx); + printf("si:%016llx di:%016llx bp:%016llx sp:%016llx\n", regs64.rsi, regs64.rdi, regs64.rbp, regs64.rsp); + printf(" 8:%016llx 9:%016llx 10:%016llx 11:%016llx\n", regs64.r8 , regs64.r9 , regs64.r10, regs64.r11); + printf("12:%016llx 13:%016llx 14:%016llx 15:%016llx\n", regs64.r12, regs64.r13, regs64.r14, regs64.r15); +} + +int check_regs64(void) +{ + int err = 0; + int num = 8; + uint64_t *r64 = ®s64.r8; + + if (!kernel_is_64bit) + return 0; + + do { + if (*r64 == 0x7f7f7f7f7f7f7f7fULL) + continue; /* register did not change */ + if (syscall_addr != (long)&int80) { + /* + * Non-INT80 syscall entrypoints are allowed to clobber R8+ regs: + * either clear them to 0, or for R11, load EFLAGS. + */ + if (*r64 == 0) + continue; + if (num == 11) { + printf("[NOTE]\tR11 has changed:%016llx - assuming clobbered by SYSRET insn\n", *r64); + continue; + } + } else { + /* INT80 syscall entrypoint can be used by + * 64-bit programs too, unlike SYSCALL/SYSENTER. + * Therefore it must preserve R12+ + * (they are callee-saved registers in 64-bit C ABI). + * + * This was probably historically not intended, + * but R8..11 are clobbered (cleared to 0). + * IOW: they are the only registers which aren't + * preserved across INT80 syscall. + */ + if (*r64 == 0 && num <= 11) + continue; + } + printf("[FAIL]\tR%d has changed:%016llx\n", num, *r64); + err++; + } while (r64++, ++num < 16); + + if (!err) + printf("[OK]\tR8..R15 did not leak kernel data\n"); + return err; +} + +int nfds; +fd_set rfds; +fd_set wfds; +fd_set efds; +struct timespec timeout; +sigset_t sigmask; +struct { + sigset_t *sp; + int sz; +} sigmask_desc; + +void prep_args() +{ + nfds = 42; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + FD_SET(0, &rfds); + FD_SET(1, &wfds); + FD_SET(2, &efds); + timeout.tv_sec = 0; + timeout.tv_nsec = 123; + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGINT); + sigaddset(&sigmask, SIGUSR2); + sigaddset(&sigmask, SIGRTMAX); + sigmask_desc.sp = &sigmask; + sigmask_desc.sz = 8; /* bytes */ +} + +static void print_flags(const char *name, unsigned long r) +{ + static const char *bitarray[] = { + "\n" ,"c\n" ,/* Carry Flag */ + "0 " ,"1 " ,/* Bit 1 - always on */ + "" ,"p " ,/* Parity Flag */ + "0 " ,"3? " , + "" ,"a " ,/* Auxiliary carry Flag */ + "0 " ,"5? " , + "" ,"z " ,/* Zero Flag */ + "" ,"s " ,/* Sign Flag */ + "" ,"t " ,/* Trap Flag */ + "" ,"i " ,/* Interrupt Flag */ + "" ,"d " ,/* Direction Flag */ + "" ,"o " ,/* Overflow Flag */ + "0 " ,"1 " ,/* I/O Privilege Level (2 bits) */ + "0" ,"1" ,/* I/O Privilege Level (2 bits) */ + "" ,"n " ,/* Nested Task */ + "0 " ,"15? ", + "" ,"r " ,/* Resume Flag */ + "" ,"v " ,/* Virtual Mode */ + "" ,"ac " ,/* Alignment Check/Access Control */ + "" ,"vif ",/* Virtual Interrupt Flag */ + "" ,"vip ",/* Virtual Interrupt Pending */ + "" ,"id " ,/* CPUID detection */ + NULL + }; + const char **bitstr; + int bit; + + printf("%s=%016lx ", name, r); + bitstr = bitarray + 42; + bit = 21; + if ((r >> 22) != 0) + printf("(extra bits are set) "); + do { + if (bitstr[(r >> bit) & 1][0]) + fputs(bitstr[(r >> bit) & 1], stdout); + bitstr -= 2; + bit--; + } while (bit >= 0); +} + +int run_syscall(void) +{ + long flags, bad_arg; + + prep_args(); + + if (kernel_is_64bit) + call64_from_32(poison_regs64); + /*print_regs64();*/ + + asm("\n" + /* Try 6-arg syscall: pselect. It should return quickly */ + " push %%ebp\n" + " mov $308, %%eax\n" /* PSELECT */ + " mov nfds, %%ebx\n" /* ebx arg1 */ + " mov $rfds, %%ecx\n" /* ecx arg2 */ + " mov $wfds, %%edx\n" /* edx arg3 */ + " mov $efds, %%esi\n" /* esi arg4 */ + " mov $timeout, %%edi\n" /* edi arg5 */ + " mov $sigmask_desc, %%ebp\n" /* %ebp arg6 */ + " push $0x200ed7\n" /* set almost all flags */ + " popf\n" /* except TF, IOPL, NT, RF, VM, AC, VIF, VIP */ + " call *syscall_addr\n" + /* Check that registers are not clobbered */ + " pushf\n" + " pop %%eax\n" + " cld\n" + " cmp nfds, %%ebx\n" /* ebx arg1 */ + " mov $1, %%ebx\n" + " jne 1f\n" + " cmp $rfds, %%ecx\n" /* ecx arg2 */ + " mov $2, %%ebx\n" + " jne 1f\n" + " cmp $wfds, %%edx\n" /* edx arg3 */ + " mov $3, %%ebx\n" + " jne 1f\n" + " cmp $efds, %%esi\n" /* esi arg4 */ + " mov $4, %%ebx\n" + " jne 1f\n" + " cmp $timeout, %%edi\n" /* edi arg5 */ + " mov $5, %%ebx\n" + " jne 1f\n" + " cmpl $sigmask_desc, %%ebp\n" /* %ebp arg6 */ + " mov $6, %%ebx\n" + " jne 1f\n" + " mov $0, %%ebx\n" + "1:\n" + " pop %%ebp\n" + : "=a" (flags), "=b" (bad_arg) + : + : "cx", "dx", "si", "di" + ); + + if (kernel_is_64bit) { + memset(®s64, 0x77, sizeof(regs64)); + call64_from_32(get_regs64); + /*print_regs64();*/ + } + + /* + * On paravirt kernels, flags are not preserved across syscalls. + * Thus, we do not consider it a bug if some are changed. + * We just show ones which do. + */ + if ((0x200ed7 ^ flags) != 0) { + print_flags("[WARN]\tFlags before", 0x200ed7); + print_flags("[WARN]\tFlags after", flags); + print_flags("[WARN]\tFlags change", (0x200ed7 ^ flags)); + } + + if (bad_arg) { + printf("[FAIL]\targ#%ld clobbered\n", bad_arg); + return 1; + } + printf("[OK]\tArguments are preserved across syscall\n"); + + return check_regs64(); +} + +int run_syscall_twice() +{ + int exitcode = 0; + long sv; + + if (syscall_addr) { + printf("[RUN]\tExecuting 6-argument 32-bit syscall via VDSO\n"); + exitcode = run_syscall(); + } + sv = syscall_addr; + syscall_addr = (long)&int80; + printf("[RUN]\tExecuting 6-argument 32-bit syscall via INT 80\n"); + exitcode += run_syscall(); + syscall_addr = sv; + return exitcode; +} + +void ptrace_me() +{ + pid_t pid; + + fflush(NULL); + pid = fork(); + if (pid < 0) + exit(1); + if (pid == 0) { + /* child */ + if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) != 0) + exit(0); + raise(SIGSTOP); + return; + } + /* parent */ + printf("[RUN]\tRunning tests under ptrace\n"); + while (1) { + int status; + pid = waitpid(-1, &status, __WALL); + if (WIFEXITED(status)) + exit(WEXITSTATUS(status)); + if (WIFSIGNALED(status)) + exit(WTERMSIG(status)); + if (pid <= 0 || !WIFSTOPPED(status)) /* paranoia */ + exit(255); + /* + * Note: we do not inject sig = WSTOPSIG(status). + * We probably should, but careful: do not inject SIGTRAP + * generated by syscall entry/exit stops. + * That kills the child. + */ + ptrace(PTRACE_SYSCALL, pid, 0L, 0L /*sig*/); + } +} + +int main(int argc, char **argv, char **envp) +{ + int exitcode = 0; + int cs; + + asm("\n" + " movl %%cs, %%eax\n" + : "=a" (cs) + ); + kernel_is_64bit = (cs == 0x23); + if (!kernel_is_64bit) + printf("[NOTE]\tNot a 64-bit kernel, won't test R8..R15 leaks\n"); + + /* This only works for non-static builds: + * syscall_addr = dlsym(dlopen("linux-gate.so.1", RTLD_NOW), "__kernel_vsyscall"); + */ + syscall_addr = get_syscall(envp); + + exitcode += run_syscall_twice(); + ptrace_me(); + exitcode += run_syscall_twice(); + + return exitcode; +} +#endif diff --git a/x86/thunks.S b/x86/thunks.S new file mode 100644 index 0000000..ce8a995 --- /dev/null +++ b/x86/thunks.S @@ -0,0 +1,67 @@ +/* + * thunks.S - assembly helpers for mixed-bitness code + * Copyright (c) 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. + * + * These are little helpers that make it easier to switch bitness on + * the fly. + */ + + .text + + .global call32_from_64 + .type call32_from_64, @function +call32_from_64: + // rdi: stack to use + // esi: function to call + + // Save registers + pushq %rbx + pushq %rbp + pushq %r12 + pushq %r13 + pushq %r14 + pushq %r15 + pushfq + + // Switch stacks + mov %rsp,(%rdi) + mov %rdi,%rsp + + // Switch to compatibility mode + pushq $0x23 /* USER32_CS */ + pushq $1f + lretq + +1: + .code32 + // Call the function + call *%esi + // Switch back to long mode + jmp $0x33,$1f + .code64 + +1: + // Restore the stack + mov (%rsp),%rsp + + // Restore registers + popfq + popq %r15 + popq %r14 + popq %r13 + popq %r12 + popq %rbp + popq %rbx + + ret + +.size call32_from_64, .-call32_from_64 diff --git a/x86/thunks_32.S b/x86/thunks_32.S new file mode 100644 index 0000000..29b644b --- /dev/null +++ b/x86/thunks_32.S @@ -0,0 +1,55 @@ +/* + * thunks_32.S - assembly helpers for mixed-bitness code + * Copyright (c) 2015 Denys Vlasenko + * + * 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. + * + * These are little helpers that make it easier to switch bitness on + * the fly. + */ + + .text + .code32 + + .global call64_from_32 + .type call32_from_64, @function + + // 4(%esp): function to call +call64_from_32: + // Fetch function address + mov 4(%esp), %eax + + // Save registers which are callee-clobbered by 64-bit ABI + push %ecx + push %edx + push %esi + push %edi + + // Switch to long mode + jmp $0x33,$1f +1: .code64 + + // Call the function + call *%rax + + // Switch to compatibility mode + push $0x23 /* USER32_CS */ + .code32; push $1f; .code64 /* hack: can't have X86_64_32S relocation in 32-bit ELF */ + lretq +1: .code32 + + pop %edi + pop %esi + pop %edx + pop %ecx + + ret + +.size call64_from_32, .-call64_from_32 diff --git a/x86/trivial_32bit_program.c b/x86/trivial_32bit_program.c new file mode 100644 index 0000000..fabdf0f --- /dev/null +++ b/x86/trivial_32bit_program.c @@ -0,0 +1,18 @@ +/* + * Trivial program to check that we have a valid 32-bit build environment. + * Copyright (c) 2015 Andy Lutomirski + * GPL v2 + */ + +#ifndef __i386__ +# error wrong architecture +#endif + +#include <stdio.h> + +int main() +{ + printf("\n"); + + return 0; +} diff --git a/x86/trivial_64bit_program.c b/x86/trivial_64bit_program.c new file mode 100644 index 0000000..05c6a41 --- /dev/null +++ b/x86/trivial_64bit_program.c @@ -0,0 +1,18 @@ +/* + * Trivial program to check that we have a valid 64-bit build environment. + * Copyright (c) 2015 Andy Lutomirski + * GPL v2 + */ + +#ifndef __x86_64__ +# error wrong architecture +#endif + +#include <stdio.h> + +int main() +{ + printf("\n"); + + return 0; +} diff --git a/x86/unwind_vdso.c b/x86/unwind_vdso.c new file mode 100644 index 0000000..00a26a8 --- /dev/null +++ b/x86/unwind_vdso.c @@ -0,0 +1,211 @@ +/* + * unwind_vdso.c - tests unwind info for AT_SYSINFO in the vDSO + * 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 tests __kernel_vsyscall's unwind info. + */ + +#define _GNU_SOURCE + +#include <features.h> +#include <stdio.h> + +#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16 + +int main() +{ + /* We need getauxval(). */ + printf("[SKIP]\tGLIBC before 2.16 cannot compile this test\n"); + return 0; +} + +#else + +#include <sys/time.h> +#include <stdlib.h> +#include <syscall.h> +#include <unistd.h> +#include <string.h> +#include <inttypes.h> +#include <sys/mman.h> +#include <signal.h> +#include <sys/ucontext.h> +#include <err.h> +#include <stddef.h> +#include <stdbool.h> +#include <sys/ptrace.h> +#include <sys/user.h> +#include <sys/ucontext.h> +#include <link.h> +#include <sys/auxv.h> +#include <dlfcn.h> +#include <unwind.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"); +} + +#ifdef __x86_64__ +# define WIDTH "q" +#else +# define WIDTH "l" +#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 volatile sig_atomic_t nerrs; +static unsigned long sysinfo; +static bool got_sysinfo = false; +static unsigned long return_address; + +struct unwind_state { + unsigned long ip; /* trap source */ + int depth; /* -1 until we hit the trap source */ +}; + +_Unwind_Reason_Code trace_fn(struct _Unwind_Context * ctx, void *opaque) +{ + struct unwind_state *state = opaque; + unsigned long ip = _Unwind_GetIP(ctx); + + if (state->depth == -1) { + if (ip == state->ip) + state->depth = 0; + else + return _URC_NO_REASON; /* Not there yet */ + } + printf("\t 0x%lx\n", ip); + + if (ip == return_address) { + /* Here we are. */ + unsigned long eax = _Unwind_GetGR(ctx, 0); + unsigned long ecx = _Unwind_GetGR(ctx, 1); + unsigned long edx = _Unwind_GetGR(ctx, 2); + unsigned long ebx = _Unwind_GetGR(ctx, 3); + unsigned long ebp = _Unwind_GetGR(ctx, 5); + unsigned long esi = _Unwind_GetGR(ctx, 6); + unsigned long edi = _Unwind_GetGR(ctx, 7); + bool ok = (eax == SYS_getpid || eax == getpid()) && + ebx == 1 && ecx == 2 && edx == 3 && + esi == 4 && edi == 5 && ebp == 6; + + if (!ok) + nerrs++; + printf("[%s]\t NR = %ld, args = %ld, %ld, %ld, %ld, %ld, %ld\n", + (ok ? "OK" : "FAIL"), + eax, ebx, ecx, edx, esi, edi, ebp); + + return _URC_NORMAL_STOP; + } else { + state->depth++; + return _URC_NO_REASON; + } +} + +static void sigtrap(int sig, siginfo_t *info, void *ctx_void) +{ + ucontext_t *ctx = (ucontext_t *)ctx_void; + struct unwind_state state; + unsigned long ip = ctx->uc_mcontext.gregs[REG_EIP]; + + if (!got_sysinfo && ip == sysinfo) { + got_sysinfo = true; + + /* Find the return address. */ + return_address = *(unsigned long *)(unsigned long)ctx->uc_mcontext.gregs[REG_ESP]; + + printf("\tIn vsyscall at 0x%lx, returning to 0x%lx\n", + ip, return_address); + } + + if (!got_sysinfo) + return; /* Not there yet */ + + if (ip == return_address) { + ctx->uc_mcontext.gregs[REG_EFL] &= ~X86_EFLAGS_TF; + printf("\tVsyscall is done\n"); + return; + } + + printf("\tSIGTRAP at 0x%lx\n", ip); + + state.ip = ip; + state.depth = -1; + _Unwind_Backtrace(trace_fn, &state); +} + +int main() +{ + sysinfo = getauxval(AT_SYSINFO); + printf("\tAT_SYSINFO is 0x%lx\n", sysinfo); + + Dl_info info; + if (!dladdr((void *)sysinfo, &info)) { + printf("[WARN]\tdladdr failed on AT_SYSINFO\n"); + } else { + printf("[OK]\tAT_SYSINFO maps to %s, loaded at 0x%p\n", + info.dli_fname, info.dli_fbase); + } + + sethandler(SIGTRAP, sigtrap, 0); + + syscall(SYS_getpid); /* Force symbol binding without TF set. */ + printf("[RUN]\tSet TF and check a fast syscall\n"); + set_eflags(get_eflags() | X86_EFLAGS_TF); + syscall(SYS_getpid, 1, 2, 3, 4, 5, 6); + if (!got_sysinfo) { + set_eflags(get_eflags() & ~X86_EFLAGS_TF); + + /* + * The most likely cause of this is that you're on Debian or + * a Debian-based distro, you're missing libc6-i686, and you're + * affected by libc/19006 (https://sourceware.org/PR19006). + */ + printf("[WARN]\tsyscall(2) didn't enter AT_SYSINFO\n"); + } + + if (get_eflags() & X86_EFLAGS_TF) { + printf("[FAIL]\tTF is still set\n"); + nerrs++; + } + + if (nerrs) { + printf("[FAIL]\tThere were errors\n"); + return 1; + } else { + printf("[OK]\tAll is well\n"); + return 0; + } +} + +#endif /* New enough libc */ |