/* * Copyright (c) 2014-2015 Andy Lutomirski * GPL v2 */ #include #include #include #include #include #include #include static unsigned short GDT3(int idx) { return (idx << 3) | 3; } static unsigned short LDT3(int idx) { return (idx << 3) | 7; } static int create_tls(int idx, unsigned int base) { struct user_desc desc = { .entry_number = idx, .base_addr = base, .limit = 0xfffff, .seg_32bit = 1, .contents = 0, /* Data, grow-up */ .read_exec_only = 0, .limit_in_pages = 1, .seg_not_present = 0, .useable = 0, }; if (syscall(SYS_set_thread_area, &desc) != 0) err(1, "set_thread_area"); return desc.entry_number; } static void do_gs_test(int idx, int np) { unsigned short orig_gs, new_gs; int ax; /* * Install a valid LDT entry (set_thread_area's hardening measures * defeat the #NP part of this test). */ struct user_desc desc; memset(&desc, 0, sizeof(desc)); desc.entry_number = 0; desc.limit = 0xffff; desc.seg_32bit = 1; desc.contents = 0; /* Data, grow-up */ desc.read_exec_only = 0; desc.limit_in_pages = 1; desc.seg_not_present = 0; if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) != 0) err(1, "modify_ldt"); if (np) { /* valid but not present */ desc.seg_not_present = 1; } else { /* "empty" user_desc -> destroy the segment. */ desc.limit = 0; desc.seg_32bit = 0; desc.limit_in_pages = 0; desc.read_exec_only = 1; desc.seg_not_present = 1; } struct timespec req = { .tv_sec = 0, .tv_nsec = 100000, }; printf("[RUN]\tGS %s\n", (np ? "not present" : "deleted")); asm volatile ("mov %%gs,%0" : "=rm" (orig_gs)); asm volatile ("mov %0,%%gs" : : "rm" (LDT3(0))); asm volatile ("int $0x80" : "=a" (ax) : "a" (SYS_modify_ldt), "b" (1), "c" (&desc), "d" (sizeof(desc))); if (ax != 0) err(1, "set_thread_area"); /* * Force rescheduling. On 32-bit kernels, fast syscalls * destroy DS and ES, so force int 80. */ asm volatile ("int $0x80" : "=a" (ax) : "a" (SYS_nanosleep), "b" (&req), "c" (0)); asm volatile ("mov %%gs,%0" : "=rm" (new_gs)); asm volatile ("mov %0,%%gs" : : "rm" (orig_gs)); printf("[OK]\tGS changed from %x to %x\n", (unsigned)GDT3(idx), (unsigned)new_gs); } int main() { int ret; int idx = create_tls(-1, 0); printf("Allocated GDT index %d\n", idx); unsigned short orig_es; asm volatile ("mov %%es,%0" : "=rm" (orig_es)); int errors = 0; int total = 1000; for (int i = 0; i < total; i++) { struct timespec req = { .tv_sec = 0, .tv_nsec = 100000, }; int ret; asm volatile ("mov %0,%%es" : : "rm" (GDT3(idx))); /* * Force rescheduling. On 32-bit kernels, fast syscalls * destroy DS and ES, so force int 80. */ asm volatile ("int $0x80" : "=a" (ret) : "a" (SYS_nanosleep), "b" (&req), "c" (0)); unsigned short es; asm volatile ("mov %%es,%0" : "=rm" (es)); asm volatile ("mov %0,%%es" : : "rm" (orig_es)); if (es != GDT3(idx)) { if (errors == 0) printf("[FAIL]\tES changed from 0x%hx to 0x%hx\n", GDT3(idx), es); errors++; } } if (errors) { printf("[FAIL]\tES was corrupted %d/%d times\n", errors, total); ret = 1; } else { printf("[OK]\tES was preserved\n"); ret = 0; } do_gs_test(idx, 1); create_tls(idx, 0); do_gs_test(idx, 0); return ret; }