Messages in this thread | | | Date | Mon, 09 Mar 2015 10:26:46 -0600 | From | Shuah Khan <> | Subject | Re: [PATCH] x86, selftests: Add sigreturn_32 selftest |
| |
On 03/09/2015 10:08 AM, Andy Lutomirski wrote: > This is my sigreturn test, added mostly unchanged from its old home. > > The integration with the selftest build process seems okay if not > particularly elegant. > > I'm not using the ksft_ helpers at all yet. I can do that later.
Andy,
Could you please write a brief description of what the test does for a commit log. What you have for a commit log isn't very useful and serves as notes for reviewers.
-- Shuah > > Signed-off-by: Andy Lutomirski <luto@amacapital.net> > --- > tools/testing/selftests/Makefile | 1 + > tools/testing/selftests/x86/.gitignore | 2 + > tools/testing/selftests/x86/Makefile | 18 ++ > tools/testing/selftests/x86/run_tests.sh | 6 + > tools/testing/selftests/x86/sigreturn.c | 531 +++++++++++++++++++++++++++++++ > 5 files changed, 558 insertions(+) > create mode 100644 tools/testing/selftests/x86/.gitignore > create mode 100644 tools/testing/selftests/x86/Makefile > create mode 100755 tools/testing/selftests/x86/run_tests.sh > create mode 100644 tools/testing/selftests/x86/sigreturn.c > > diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile > index 4e511221a0c1..2ad56d451469 100644 > --- a/tools/testing/selftests/Makefile > +++ b/tools/testing/selftests/Makefile > @@ -17,6 +17,7 @@ TARGETS += sysctl > TARGETS += timers > TARGETS += user > TARGETS += vm > +TARGETS += x86 > #Please keep the TARGETS list alphabetically sorted > > TARGETS_HOTPLUG = cpu-hotplug > diff --git a/tools/testing/selftests/x86/.gitignore b/tools/testing/selftests/x86/.gitignore > new file mode 100644 > index 000000000000..15034fef9698 > --- /dev/null > +++ b/tools/testing/selftests/x86/.gitignore > @@ -0,0 +1,2 @@ > +*_32 > +*_64 > diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile > new file mode 100644 > index 000000000000..3c4fc3158ddc > --- /dev/null > +++ b/tools/testing/selftests/x86/Makefile > @@ -0,0 +1,18 @@ > +.PHONY: all clean run_tests > + > +TARGETS_C_32ONLY := sigreturn > + > +BINARIES := $(TARGETS_C_32ONLY:%=%_32) > + > +CFLAGS := -O2 -g -std=gnu99 -pthread -Wall > + > +all: $(BINARIES) > + > +clean: > + $(RM) $(BINARIES) > + > +run_tests: > + ./run_tests.sh > + > +$(TARGETS_C_32ONLY:%=%_32): %_32: %.c > + gcc -m32 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $^ -lrt -ldl > diff --git a/tools/testing/selftests/x86/run_tests.sh b/tools/testing/selftests/x86/run_tests.sh > new file mode 100755 > index 000000000000..2dd495300363 > --- /dev/null > +++ b/tools/testing/selftests/x86/run_tests.sh > @@ -0,0 +1,6 @@ > +#!/bin/bash > + > +# This is deliberately minimal. IMO kselftests should provide a standard > +# script here. > +./sigreturn_32 > +exit $? > diff --git a/tools/testing/selftests/x86/sigreturn.c b/tools/testing/selftests/x86/sigreturn.c > new file mode 100644 > index 000000000000..655cc35847f5 > --- /dev/null > +++ b/tools/testing/selftests/x86/sigreturn.c > @@ -0,0 +1,531 @@ > +/* > + * Sigreturn test. > + * Copyright (c) 2014-2015 Andrew Lutomirski. > + * > + * This abuses sigreturn to test interesting cases when returning to > + * user space. It exercises espfix and various iret exceptions. > + * > + * GPL v2. > + */ > + > +#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> > + > +struct selectors { > + unsigned short cs, gs, fs, ss; > +}; > + > +static bool has_code16, has_data16, has_npcode32, has_npdata32; > + > +static int gdt_data16_idx, gdt_npdata32_idx; > + > +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 unsigned char stack16[65536] __attribute__((aligned(4096))); > + > +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]; > + > +static void add_ldt(const struct user_desc *desc, bool *var, const char *name) > +{ > + if (syscall(SYS_modify_ldt, 1, desc, sizeof(*desc)) == 0) { > + *var = true; > + } else { > + printf("[NOTE]\tFailed to create %s segment\n", name); > + *var = false; > + } > +} > + > +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"); > + > + /* Borrowed from a test case by hpa */ > + const struct user_desc code16_desc = { > + .entry_number = 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, &has_code16, "code16"); > + > + const struct user_desc 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 > + }; > + add_ldt(&data16_desc, &has_data16, "data16"); > + > + const struct user_desc npcode32_desc = { > + .entry_number = 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, &has_npcode32, "npcode32"); > + > + const struct user_desc npdata32_desc = { > + .entry_number = 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, &has_npdata32, "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) { > + 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) { > + 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"); > + } > +} > + > +static gregset_t initial_regs, requested_regs, resulting_regs; > + > +/* Per POSIX, these should be volatile sigatomic_t. Go away, pedants. */ > +static volatile unsigned short sig_cs, sig_ss; > +static volatile sig_atomic_t sig_trapped, sig_err, sig_trapno; > + > +#ifdef __x86_64__ > +# define REG_IP REG_RIP > +# define REG_SP REG_RSP > +# define REG_AX REG_RAX > + > +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 > + > +static int nerrs; > + > +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 == 0x7 || sig_cs == 0x1f) ? 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; > +} > + > +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) { > + 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; > +} > + > +static char altstack_data[SIGSTKSZ]; > + > +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. */ > +} > + > +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(0x7) == bitness) > + return 0x7; > + > + printf("[WARN]\tCould not find %d-bit CS\n", bitness); > + return -1; > +} > + > +static int do_test(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 (!has_data16) { > + printf("[SKIP]\tData segment unavailable for %d-bit CS, 16-bit SS\n", > + cs_bits); > + return 0; > + } > + sig_ss = (1 << 3) | 7; /* LDT selector 1, RPL = 3 */ > + } else { > + asm volatile ("mov %%ss,%0" : "=r" (sig_ss)); > + } > + } > + > + sig_cs = cs; > + > + printf("[RUN]\t%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; > + > + 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); > + 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) { > + 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(void) > +{ > + int total_nerrs = 0; > + unsigned short my_cs, my_ss; > + > +#ifdef __x86_64__ > + printf("[WARN]\t***** The 64-bit version requires a special kernel. *****\n"); > + printf("[WARN]\t***** Build with -m32. *****\n"); > + usleep(1000000); > +#endif > + > + 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); > + > + total_nerrs += do_test(64, false, -1); > + total_nerrs += do_test(32, false, -1); > + total_nerrs += do_test(16, false, -1); > + total_nerrs += do_test(64, true, -1); > + total_nerrs += do_test(32, true, -1); > + total_nerrs += do_test(16, true, -1); > + > + if (gdt_data16_idx) { > + total_nerrs += do_test(64, true, (gdt_data16_idx << 3) | 3); > + total_nerrs += do_test(32, true, (gdt_data16_idx << 3) | 3); > + total_nerrs += do_test(16, true, (gdt_data16_idx << 3) | 3); > + } > + > + clearhandler(SIGTRAP); > + sethandler(SIGSEGV, sigtrap, SA_ONSTACK); > + sethandler(SIGBUS, sigtrap, SA_ONSTACK); > + sethandler(SIGILL, sigtrap, SA_ONSTACK); /* 32-bit kernels do this */ > + > + test_bad_iret(64, (2 << 3) | 7, -1); > + test_bad_iret(32, (2 << 3) | 7, -1); > + test_bad_iret(16, (2 << 3) | 7, -1); > + > + test_bad_iret(64, my_cs, -1); > + test_bad_iret(32, my_cs, -1); > + test_bad_iret(16, my_cs, -1); > + > + /* IRET will fail with #NP */ > + test_bad_iret(32, my_ss, (3 << 3) | 7); > + > + /* IRET will fail with #SS on the espfix stack */ > + test_bad_iret(32, (4 << 3) | 7, -1); > + > + /* IRET will fail with #SS on the normal stack */ > + if (gdt_npdata32_idx) > + test_bad_iret(32, (gdt_npdata32_idx << 3) | 3, -1); > + > + return total_nerrs ? 1 : 0; > +} >
-- Shuah Khan Sr. Linux Kernel Developer Open Source Innovation Group Samsung Research America (Silicon Valley) shuahkh@osg.samsung.com | (970) 217-8978
| |