diff options
Diffstat (limited to 'tools/testing/selftests/x86')
-rw-r--r-- | tools/testing/selftests/x86/Makefile | 21 | ||||
-rw-r--r-- | tools/testing/selftests/x86/protection_keys.c | 21 | ||||
-rw-r--r-- | tools/testing/selftests/x86/sysret_rip.c | 195 |
3 files changed, 210 insertions, 27 deletions
diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile index 8c1cb423cfe6..3a5ebae5303e 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile @@ -5,18 +5,21 @@ include ../lib.mk .PHONY: all all_32 all_64 warn_32bit_failure clean TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_syscall test_mremap_vdso \ - check_initial_reg_state sigreturn ldt_gdt iopl \ + check_initial_reg_state sigreturn ldt_gdt iopl mpx-mini-test \ protection_keys test_vdso TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \ test_FCMOV test_FCOMI test_FISTTP \ vdso_restorer -TARGETS_C_64BIT_ONLY := fsgsbase +TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip TARGETS_C_32BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_32BIT_ONLY) TARGETS_C_64BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_64BIT_ONLY) BINARIES_32 := $(TARGETS_C_32BIT_ALL:%=%_32) BINARIES_64 := $(TARGETS_C_64BIT_ALL:%=%_64) +BINARIES_32 := $(patsubst %,$(OUTPUT)/%,$(BINARIES_32)) +BINARIES_64 := $(patsubst %,$(OUTPUT)/%,$(BINARIES_64)) + CFLAGS := -O2 -g -std=gnu99 -pthread -Wall UNAME_M := $(shell uname -m) @@ -40,10 +43,10 @@ all_64: $(BINARIES_64) clean: $(RM) $(BINARIES_32) $(BINARIES_64) -$(TARGETS_C_32BIT_ALL:%=%_32): %_32: %.c +$(BINARIES_32): $(OUTPUT)/%_32: %.c $(CC) -m32 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $^ -lrt -ldl -lm -$(TARGETS_C_64BIT_ALL:%=%_64): %_64: %.c +$(BINARIES_64): $(OUTPUT)/%_64: %.c $(CC) -m64 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $^ -lrt -ldl # x86_64 users should be encouraged to install 32-bit libraries @@ -65,12 +68,12 @@ warn_32bit_failure: 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 +$(OUTPUT)/sysret_ss_attrs_64: thunks.S +$(OUTPUT)/ptrace_syscall_32: raw_syscall_helper_32.S +$(OUTPUT)/test_syscall_vdso_32: thunks_32.S # check_initial_reg_state is special: it needs a custom entry, and it # needs to be static so that its interpreter doesn't destroy its initial # state. -check_initial_reg_state_32: CFLAGS += -Wl,-ereal_start -static -check_initial_reg_state_64: CFLAGS += -Wl,-ereal_start -static +$(OUTPUT)/check_initial_reg_state_32: CFLAGS += -Wl,-ereal_start -static +$(OUTPUT)/check_initial_reg_state_64: CFLAGS += -Wl,-ereal_start -static diff --git a/tools/testing/selftests/x86/protection_keys.c b/tools/testing/selftests/x86/protection_keys.c index bdd58c78902e..3237bc010e1c 100644 --- a/tools/testing/selftests/x86/protection_keys.c +++ b/tools/testing/selftests/x86/protection_keys.c @@ -192,7 +192,7 @@ void lots_o_noops_around_write(int *write_to_me) #define SYS_pkey_alloc 381 #define SYS_pkey_free 382 #define REG_IP_IDX REG_EIP -#define si_pkey_offset 0x18 +#define si_pkey_offset 0x14 #else #define SYS_mprotect_key 329 #define SYS_pkey_alloc 330 @@ -462,7 +462,7 @@ void pkey_disable_set(int pkey, int flags) unsigned long syscall_flags = 0; int ret; int pkey_rights; - u32 orig_pkru; + u32 orig_pkru = rdpkru(); dprintf1("START->%s(%d, 0x%x)\n", __func__, pkey, flags); @@ -812,8 +812,6 @@ void setup_hugetlbfs(void) { int err; int fd; - int validated_nr_pages; - int i; char buf[] = "123"; if (geteuid() != 0) { @@ -1116,11 +1114,6 @@ void test_pkey_syscalls_on_non_allocated_pkey(int *ptr, u16 pkey) err = sys_pkey_free(i); pkey_assert(err); - /* not enforced when pkey_get() is not a syscall - err = pkey_get(i, 0); - pkey_assert(err < 0); - */ - err = sys_pkey_free(i); pkey_assert(err); @@ -1133,14 +1126,8 @@ void test_pkey_syscalls_on_non_allocated_pkey(int *ptr, u16 pkey) void test_pkey_syscalls_bad_args(int *ptr, u16 pkey) { int err; - int bad_flag = (PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE) + 1; int bad_pkey = NR_PKEYS+99; - /* not enforced when pkey_get() is not a syscall - err = pkey_get(bad_pkey, bad_flag); - pkey_assert(err < 0); - */ - /* pass a known-invalid pkey in: */ err = sys_mprotect_pkey(ptr, PAGE_SIZE, PROT_READ, bad_pkey); pkey_assert(err); @@ -1149,8 +1136,6 @@ void test_pkey_syscalls_bad_args(int *ptr, u16 pkey) /* Assumes that all pkeys other than 'pkey' are unallocated */ void test_pkey_alloc_exhaust(int *ptr, u16 pkey) { - unsigned long flags; - unsigned long init_val; int err; int allocated_pkeys[NR_PKEYS] = {0}; int nr_allocated_pkeys = 0; @@ -1367,7 +1352,7 @@ void run_tests_once(void) tracing_off(); close_test_fds(); - printf("test %2d PASSED (itertation %d)\n", test_nr, iteration_nr); + printf("test %2d PASSED (iteration %d)\n", test_nr, iteration_nr); dprintf1("======================\n\n"); } iteration_nr++; diff --git a/tools/testing/selftests/x86/sysret_rip.c b/tools/testing/selftests/x86/sysret_rip.c new file mode 100644 index 000000000000..d85ec5b3671c --- /dev/null +++ b/tools/testing/selftests/x86/sysret_rip.c @@ -0,0 +1,195 @@ +/* + * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls + * Copyright (c) 2014-2016 Andrew Lutomirski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include <sys/signal.h> +#include <sys/ucontext.h> +#include <sys/syscall.h> +#include <err.h> +#include <stddef.h> +#include <stdbool.h> +#include <setjmp.h> +#include <sys/user.h> +#include <sys/mman.h> +#include <assert.h> + + +asm ( + ".pushsection \".text\", \"ax\"\n\t" + ".balign 4096\n\t" + "test_page: .globl test_page\n\t" + ".fill 4094,1,0xcc\n\t" + "test_syscall_insn:\n\t" + "syscall\n\t" + ".ifne . - test_page - 4096\n\t" + ".error \"test page is not one page long\"\n\t" + ".endif\n\t" + ".popsection" + ); + +extern const char test_page[]; +static void const *current_test_page_addr = test_page; + +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"); +} + +/* State used by our signal handlers. */ +static gregset_t initial_regs; + +static volatile unsigned long rip; + +static void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void) +{ + ucontext_t *ctx = (ucontext_t*)ctx_void; + + if (rip != ctx->uc_mcontext.gregs[REG_RIP]) { + printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n", + rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]); + fflush(stdout); + _exit(1); + } + + memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t)); + + printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip); +} + +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)); + + /* Set IP and CX to match so that SYSRET can happen. */ + ctx->uc_mcontext.gregs[REG_RIP] = rip; + ctx->uc_mcontext.gregs[REG_RCX] = rip; + + /* R11 and EFLAGS should already match. */ + assert(ctx->uc_mcontext.gregs[REG_EFL] == + ctx->uc_mcontext.gregs[REG_R11]); + + sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND); + + return; +} + +static void test_sigreturn_to(unsigned long ip) +{ + rip = ip; + printf("[RUN]\tsigreturn to 0x%lx\n", ip); + raise(SIGUSR1); +} + +static jmp_buf jmpbuf; + +static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void) +{ + ucontext_t *ctx = (ucontext_t*)ctx_void; + + if (rip != ctx->uc_mcontext.gregs[REG_RIP]) { + printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n", + rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]); + fflush(stdout); + _exit(1); + } + + siglongjmp(jmpbuf, 1); +} + +static void test_syscall_fallthrough_to(unsigned long ip) +{ + void *new_address = (void *)(ip - 4096); + void *ret; + + printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip); + + ret = mremap((void *)current_test_page_addr, 4096, 4096, + MREMAP_MAYMOVE | MREMAP_FIXED, new_address); + if (ret == MAP_FAILED) { + if (ip <= (1UL << 47) - PAGE_SIZE) { + err(1, "mremap to %p", new_address); + } else { + printf("[OK]\tmremap to %p failed\n", new_address); + return; + } + } + + if (ret != new_address) + errx(1, "mremap malfunctioned: asked for %p but got %p\n", + new_address, ret); + + current_test_page_addr = new_address; + rip = ip; + + if (sigsetjmp(jmpbuf, 1) == 0) { + asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid), + [syscall_insn] "rm" (ip - 2)); + errx(1, "[FAIL]\tSyscall trampoline returned"); + } + + printf("[OK]\tWe survived\n"); +} + +int main() +{ + /* + * When the kernel returns from a slow-path syscall, it will + * detect whether SYSRET is appropriate. If it incorrectly + * thinks that SYSRET is appropriate when RIP is noncanonical, + * it'll crash on Intel CPUs. + */ + sethandler(SIGUSR1, sigusr1, 0); + for (int i = 47; i < 64; i++) + test_sigreturn_to(1UL<<i); + + clearhandler(SIGUSR1); + + sethandler(SIGSEGV, sigsegv_for_fallthrough, 0); + + /* One extra test to check that we didn't screw up the mremap logic. */ + test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE); + + /* These are the interesting cases. */ + for (int i = 47; i < 64; i++) { + test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE); + test_syscall_fallthrough_to(1UL<<i); + } + + return 0; +} |