selftests/x86/ldt_gdt: Test set_thread_area() deletion of an active segment
authorAndy Lutomirski <luto@kernel.org>
Tue, 26 Apr 2016 19:23:31 +0000 (12:23 -0700)
committerIngo Molnar <mingo@kernel.org>
Fri, 29 Apr 2016 09:56:42 +0000 (11:56 +0200)
Now that set_thread_area() is supposed to give deterministic behavior
when it modifies in-use segments, test it.

Signed-off-by: Andy Lutomirski <luto@kernel.org>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Denys Vlasenko <dvlasenk@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: http://lkml.kernel.org/r/f2bc11af1ee1a0f815ed910840cbdba06b640a20.1461698311.git.luto@kernel.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>
tools/testing/selftests/x86/ldt_gdt.c

index 31a3035cd4eb33485dc01a0247e367606fc7ae50..4af47079cf04305cec7e6a8d2aa0960a6fcfe352 100644 (file)
@@ -21,6 +21,9 @@
 #include <pthread.h>
 #include <sched.h>
 #include <linux/futex.h>
+#include <sys/mman.h>
+#include <asm/prctl.h>
+#include <sys/prctl.h>
 
 #define AR_ACCESSED            (1<<8)
 
 
 static int nerrs;
 
+/* Points to an array of 1024 ints, each holding its own index. */
+static const unsigned int *counter_page;
+static struct user_desc *low_user_desc;
+static struct user_desc *low_user_desc_clear;  /* Use to delete GDT entry */
+static int gdt_entry_num;
+
 static void check_invalid_segment(uint16_t index, int ldt)
 {
        uint32_t has_limit = 0, has_ar = 0, limit, ar;
@@ -561,16 +570,257 @@ static void do_exec_test(void)
        }
 }
 
+static void setup_counter_page(void)
+{
+       unsigned int *page = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
+                        MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1, 0);
+       if (page == MAP_FAILED)
+               err(1, "mmap");
+
+       for (int i = 0; i < 1024; i++)
+               page[i] = i;
+       counter_page = page;
+}
+
+static int invoke_set_thread_area(void)
+{
+       int ret;
+       asm volatile ("int $0x80"
+                     : "=a" (ret), "+m" (low_user_desc) :
+                       "a" (243), "b" (low_user_desc)
+                     : "flags");
+       return ret;
+}
+
+static void setup_low_user_desc(void)
+{
+       low_user_desc = mmap(NULL, 2 * sizeof(struct user_desc),
+                            PROT_READ | PROT_WRITE,
+                            MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1, 0);
+       if (low_user_desc == MAP_FAILED)
+               err(1, "mmap");
+
+       low_user_desc->entry_number     = -1;
+       low_user_desc->base_addr        = (unsigned long)&counter_page[1];
+       low_user_desc->limit            = 0xfffff;
+       low_user_desc->seg_32bit        = 1;
+       low_user_desc->contents         = 0; /* Data, grow-up*/
+       low_user_desc->read_exec_only   = 0;
+       low_user_desc->limit_in_pages   = 1;
+       low_user_desc->seg_not_present  = 0;
+       low_user_desc->useable          = 0;
+
+       if (invoke_set_thread_area() == 0) {
+               gdt_entry_num = low_user_desc->entry_number;
+               printf("[NOTE]\tset_thread_area is available; will use GDT index %d\n", gdt_entry_num);
+       } else {
+               printf("[NOTE]\tset_thread_area is unavailable\n");
+       }
+
+       low_user_desc_clear = low_user_desc + 1;
+       low_user_desc_clear->entry_number = gdt_entry_num;
+       low_user_desc_clear->read_exec_only = 1;
+       low_user_desc_clear->seg_not_present = 1;
+}
+
+static void test_gdt_invalidation(void)
+{
+       if (!gdt_entry_num)
+               return; /* 64-bit only system -- we can't use set_thread_area */
+
+       unsigned short prev_sel;
+       unsigned short sel;
+       unsigned int eax;
+       const char *result;
+#ifdef __x86_64__
+       unsigned long saved_base;
+       unsigned long new_base;
+#endif
+
+       /* Test DS */
+       invoke_set_thread_area();
+       eax = 243;
+       sel = (gdt_entry_num << 3) | 3;
+       asm volatile ("movw %%ds, %[prev_sel]\n\t"
+                     "movw %[sel], %%ds\n\t"
+#ifdef __i386__
+                     "pushl %%ebx\n\t"
+#endif
+                     "movl %[arg1], %%ebx\n\t"
+                     "int $0x80\n\t"   /* Should invalidate ds */
+#ifdef __i386__
+                     "popl %%ebx\n\t"
+#endif
+                     "movw %%ds, %[sel]\n\t"
+                     "movw %[prev_sel], %%ds"
+                     : [prev_sel] "=&r" (prev_sel), [sel] "+r" (sel),
+                       "+a" (eax)
+                     : "m" (low_user_desc_clear),
+                       [arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear)
+                     : "flags");
+
+       if (sel != 0) {
+               result = "FAIL";
+               nerrs++;
+       } else {
+               result = "OK";
+       }
+       printf("[%s]\tInvalidate DS with set_thread_area: new DS = 0x%hx\n",
+              result, sel);
+
+       /* Test ES */
+       invoke_set_thread_area();
+       eax = 243;
+       sel = (gdt_entry_num << 3) | 3;
+       asm volatile ("movw %%es, %[prev_sel]\n\t"
+                     "movw %[sel], %%es\n\t"
+#ifdef __i386__
+                     "pushl %%ebx\n\t"
+#endif
+                     "movl %[arg1], %%ebx\n\t"
+                     "int $0x80\n\t"   /* Should invalidate es */
+#ifdef __i386__
+                     "popl %%ebx\n\t"
+#endif
+                     "movw %%es, %[sel]\n\t"
+                     "movw %[prev_sel], %%es"
+                     : [prev_sel] "=&r" (prev_sel), [sel] "+r" (sel),
+                       "+a" (eax)
+                     : "m" (low_user_desc_clear),
+                       [arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear)
+                     : "flags");
+
+       if (sel != 0) {
+               result = "FAIL";
+               nerrs++;
+       } else {
+               result = "OK";
+       }
+       printf("[%s]\tInvalidate ES with set_thread_area: new ES = 0x%hx\n",
+              result, sel);
+
+       /* Test FS */
+       invoke_set_thread_area();
+       eax = 243;
+       sel = (gdt_entry_num << 3) | 3;
+#ifdef __x86_64__
+       syscall(SYS_arch_prctl, ARCH_GET_FS, &saved_base);
+#endif
+       asm volatile ("movw %%fs, %[prev_sel]\n\t"
+                     "movw %[sel], %%fs\n\t"
+#ifdef __i386__
+                     "pushl %%ebx\n\t"
+#endif
+                     "movl %[arg1], %%ebx\n\t"
+                     "int $0x80\n\t"   /* Should invalidate fs */
+#ifdef __i386__
+                     "popl %%ebx\n\t"
+#endif
+                     "movw %%fs, %[sel]\n\t"
+                     : [prev_sel] "=&r" (prev_sel), [sel] "+r" (sel),
+                       "+a" (eax)
+                     : "m" (low_user_desc_clear),
+                       [arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear)
+                     : "flags");
+
+#ifdef __x86_64__
+       syscall(SYS_arch_prctl, ARCH_GET_FS, &new_base);
+#endif
+
+       /* Restore FS/BASE for glibc */
+       asm volatile ("movw %[prev_sel], %%fs" : : [prev_sel] "rm" (prev_sel));
+#ifdef __x86_64__
+       if (saved_base)
+               syscall(SYS_arch_prctl, ARCH_SET_FS, saved_base);
+#endif
+
+       if (sel != 0) {
+               result = "FAIL";
+               nerrs++;
+       } else {
+               result = "OK";
+       }
+       printf("[%s]\tInvalidate FS with set_thread_area: new FS = 0x%hx\n",
+              result, sel);
+
+#ifdef __x86_64__
+       if (sel == 0 && new_base != 0) {
+               nerrs++;
+               printf("[FAIL]\tNew FSBASE was 0x%lx\n", new_base);
+       } else {
+               printf("[OK]\tNew FSBASE was zero\n");
+       }
+#endif
+
+       /* Test GS */
+       invoke_set_thread_area();
+       eax = 243;
+       sel = (gdt_entry_num << 3) | 3;
+#ifdef __x86_64__
+       syscall(SYS_arch_prctl, ARCH_GET_GS, &saved_base);
+#endif
+       asm volatile ("movw %%gs, %[prev_sel]\n\t"
+                     "movw %[sel], %%gs\n\t"
+#ifdef __i386__
+                     "pushl %%ebx\n\t"
+#endif
+                     "movl %[arg1], %%ebx\n\t"
+                     "int $0x80\n\t"   /* Should invalidate gs */
+#ifdef __i386__
+                     "popl %%ebx\n\t"
+#endif
+                     "movw %%gs, %[sel]\n\t"
+                     : [prev_sel] "=&r" (prev_sel), [sel] "+r" (sel),
+                       "+a" (eax)
+                     : "m" (low_user_desc_clear),
+                       [arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear)
+                     : "flags");
+
+#ifdef __x86_64__
+       syscall(SYS_arch_prctl, ARCH_GET_GS, &new_base);
+#endif
+
+       /* Restore GS/BASE for glibc */
+       asm volatile ("movw %[prev_sel], %%gs" : : [prev_sel] "rm" (prev_sel));
+#ifdef __x86_64__
+       if (saved_base)
+               syscall(SYS_arch_prctl, ARCH_SET_GS, saved_base);
+#endif
+
+       if (sel != 0) {
+               result = "FAIL";
+               nerrs++;
+       } else {
+               result = "OK";
+       }
+       printf("[%s]\tInvalidate GS with set_thread_area: new GS = 0x%hx\n",
+              result, sel);
+
+#ifdef __x86_64__
+       if (sel == 0 && new_base != 0) {
+               nerrs++;
+               printf("[FAIL]\tNew GSBASE was 0x%lx\n", new_base);
+       } else {
+               printf("[OK]\tNew GSBASE was zero\n");
+       }
+#endif
+}
+
 int main(int argc, char **argv)
 {
        if (argc == 1 && !strcmp(argv[0], "ldt_gdt_test_exec"))
                return finish_exec_test();
 
+       setup_counter_page();
+       setup_low_user_desc();
+
        do_simple_tests();
 
        do_multicpu_tests();
 
        do_exec_test();
 
+       test_gdt_invalidation();
+
        return nerrs ? 1 : 0;
 }