selftests/x86/fsgsbase: Test RD/WRGSBASE
authorAndy Lutomirski <luto@kernel.org>
Wed, 8 May 2019 10:02:29 +0000 (03:02 -0700)
committerThomas Gleixner <tglx@linutronix.de>
Sat, 22 Jun 2019 09:38:55 +0000 (11:38 +0200)
This validates that GS and GSBASE are independently preserved across
context switches.

[ chang: Use FSGSBASE instructions directly instead of .byte ]

Signed-off-by: Andy Lutomirski <luto@kernel.org>
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Andi Kleen <ak@linux.intel.com>
Cc: Ravi Shankar <ravi.v.shankar@intel.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Link: https://lkml.kernel.org/r/1557309753-24073-15-git-send-email-chang.seok.bae@intel.com
tools/testing/selftests/x86/fsgsbase.c

index b02ddce49bbb85bd33f73a24f30a29e4782fd9cf..afd029897c793f969647f8f6fe0493a71d2e55e2 100644 (file)
@@ -26,6 +26,7 @@
 #include <stddef.h>
 #include <sys/ptrace.h>
 #include <sys/wait.h>
+#include <setjmp.h>
 
 #ifndef __x86_64__
 # error This test is 64-bit only
@@ -74,6 +75,43 @@ static void sigsegv(int sig, siginfo_t *si, void *ctx_void)
 
 }
 
+static jmp_buf jmpbuf;
+
+static void sigill(int sig, siginfo_t *si, void *ctx_void)
+{
+       siglongjmp(jmpbuf, 1);
+}
+
+static bool have_fsgsbase;
+
+static inline unsigned long rdgsbase(void)
+{
+       unsigned long gsbase;
+
+       asm volatile("rdgsbase %0" : "=r" (gsbase) :: "memory");
+
+       return gsbase;
+}
+
+static inline unsigned long rdfsbase(void)
+{
+       unsigned long fsbase;
+
+       asm volatile("rdfsbase %0" : "=r" (fsbase) :: "memory");
+
+       return fsbase;
+}
+
+static inline void wrgsbase(unsigned long gsbase)
+{
+       asm volatile("wrgsbase %0" :: "r" (gsbase) : "memory");
+}
+
+static inline void wrfsbase(unsigned long fsbase)
+{
+       asm volatile("wrfsbase %0" :: "r" (fsbase) : "memory");
+}
+
 enum which_base { FS, GS };
 
 static unsigned long read_base(enum which_base which)
@@ -202,14 +240,16 @@ static void do_remote_base()
               to_set, hard_zero ? " and clear gs" : "", sel);
 }
 
-void do_unexpected_base(void)
+static __thread int set_thread_area_entry_number = -1;
+
+static void do_unexpected_base(void)
 {
        /*
         * The goal here is to try to arrange for GS == 0, GSBASE !=
         * 0, and for the the kernel the think that GSBASE == 0.
         *
         * To make the test as reliable as possible, this uses
-        * explicit descriptorss.  (This is not the only way.  This
+        * explicit descriptors.  (This is not the only way.  This
         * could use ARCH_SET_GS with a low, nonzero base, but the
         * relevant side effect of ARCH_SET_GS could change.)
         */
@@ -242,7 +282,7 @@ void do_unexpected_base(void)
                        MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
                memcpy(low_desc, &desc, sizeof(desc));
 
-               low_desc->entry_number = -1;
+               low_desc->entry_number = set_thread_area_entry_number;
 
                /* 32-bit set_thread_area */
                long ret;
@@ -257,6 +297,8 @@ void do_unexpected_base(void)
                        return;
                }
                printf("\tother thread: using GDT slot %d\n", desc.entry_number);
+               set_thread_area_entry_number = desc.entry_number;
+
                asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)((desc.entry_number << 3) | 0x3)));
        }
 
@@ -268,6 +310,34 @@ void do_unexpected_base(void)
        asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
 }
 
+void test_wrbase(unsigned short index, unsigned long base)
+{
+       unsigned short newindex;
+       unsigned long newbase;
+
+       printf("[RUN]\tGS = 0x%hx, GSBASE = 0x%lx\n", index, base);
+
+       asm volatile ("mov %0, %%gs" : : "rm" (index));
+       wrgsbase(base);
+
+       remote_base = 0;
+       ftx = 1;
+       syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
+       while (ftx != 0)
+               syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
+
+       asm volatile ("mov %%gs, %0" : "=rm" (newindex));
+       newbase = rdgsbase();
+
+       if (newindex == index && newbase == base) {
+               printf("[OK]\tIndex and base were preserved\n");
+       } else {
+               printf("[FAIL]\tAfter switch, GS = 0x%hx and GSBASE = 0x%lx\n",
+                      newindex, newbase);
+               nerrs++;
+       }
+}
+
 static void *threadproc(void *ctx)
 {
        while (1) {
@@ -439,6 +509,17 @@ int main()
 {
        pthread_t thread;
 
+       /* Probe FSGSBASE */
+       sethandler(SIGILL, sigill, 0);
+       if (sigsetjmp(jmpbuf, 1) == 0) {
+               rdfsbase();
+               have_fsgsbase = true;
+               printf("\tFSGSBASE instructions are enabled\n");
+       } else {
+               printf("\tFSGSBASE instructions are disabled\n");
+       }
+       clearhandler(SIGILL);
+
        sethandler(SIGSEGV, sigsegv, 0);
 
        check_gs_value(0);
@@ -485,6 +566,21 @@ int main()
 
        test_unexpected_base();
 
+       if (have_fsgsbase) {
+               unsigned short ss;
+
+               asm volatile ("mov %%ss, %0" : "=rm" (ss));
+
+               test_wrbase(0, 0);
+               test_wrbase(0, 1);
+               test_wrbase(0, 0x200000000);
+               test_wrbase(0, 0xffffffffffffffff);
+               test_wrbase(ss, 0);
+               test_wrbase(ss, 1);
+               test_wrbase(ss, 0x200000000);
+               test_wrbase(ss, 0xffffffffffffffff);
+       }
+
        ftx = 3;  /* Kill the thread. */
        syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);