[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH v5 3/4] selftests/x86, x86/ldt: Add a selftest for modify_ldt
On Mon, Jul 27, 2015 at 10:29 PM, Andy Lutomirski <luto@xxxxxxxxxx> wrote: > This tests general modify_ldt behavior (only writes, so far) as > well as synchronous updates via IPI. It fails on old kernels. > > I called this ldt_gdt because I'll add set_thread_area tests to > it at some point. > > Signed-off-by: Andy Lutomirski <luto@xxxxxxxxxx> Looks great! Reviewed-by: Kees Cook <keescook@xxxxxxxxxxxx> -Kees > --- > tools/testing/selftests/x86/Makefile | 2 +- > tools/testing/selftests/x86/ldt_gdt.c | 520 > ++++++++++++++++++++++++++++++++++ > 2 files changed, 521 insertions(+), 1 deletion(-) > create mode 100644 tools/testing/selftests/x86/ldt_gdt.c > > diff --git a/tools/testing/selftests/x86/Makefile > b/tools/testing/selftests/x86/Makefile > index caa60d56d7d1..4138387b892c 100644 > --- a/tools/testing/selftests/x86/Makefile > +++ b/tools/testing/selftests/x86/Makefile > @@ -4,7 +4,7 @@ include ../lib.mk > > .PHONY: all all_32 all_64 warn_32bit_failure clean > > -TARGETS_C_BOTHBITS := sigreturn single_step_syscall sysret_ss_attrs > +TARGETS_C_BOTHBITS := sigreturn single_step_syscall sysret_ss_attrs ldt_gdt > TARGETS_C_32BIT_ONLY := entry_from_vm86 > > TARGETS_C_32BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_32BIT_ONLY) > diff --git a/tools/testing/selftests/x86/ldt_gdt.c > b/tools/testing/selftests/x86/ldt_gdt.c > new file mode 100644 > index 000000000000..c27adfc9ae72 > --- /dev/null > +++ b/tools/testing/selftests/x86/ldt_gdt.c > @@ -0,0 +1,520 @@ > +/* > + * 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); > + } > +} > + > +int main() > +{ > + do_simple_tests(); > + > + do_multicpu_tests(); > + > + return nerrs ? 1 : 0; > +} > -- > 2.4.3 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-kernel" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html > Please read the FAQ at http://www.tux.org/lkml/ -- Kees Cook Chrome OS Security _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxx http://lists.xen.org/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |