selftests/x86: Add tests for UC_SIGCONTEXT_SS and UC_STRICT_RESTORE_SS
authorAndy Lutomirski <luto@kernel.org>
Tue, 16 Feb 2016 23:09:04 +0000 (15:09 -0800)
committerIngo Molnar <mingo@kernel.org>
Wed, 17 Feb 2016 07:32:12 +0000 (08:32 +0100)
This tests the two ABI-preserving cases that DOSEMU cares about, and
it also explicitly tests the new UC_SIGCONTEXT_SS and
UC_STRICT_RESTORE_SS flags.

Signed-off-by: Andy Lutomirski <luto@kernel.org>
Acked-by: Borislav Petkov <bp@alien8.de>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Cyrill Gorcunov <gorcunov@gmail.com>
Cc: Denys Vlasenko <dvlasenk@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Pavel Emelyanov <xemul@parallels.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Shuah Khan <shuahkh@osg.samsung.com>
Cc: Stas Sergeev <stsp@list.ru>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: http://lkml.kernel.org/r/f3d08f98541d0bd3030ceb35e05e21f59e30232c.1455664054.git.luto@kernel.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>
tools/testing/selftests/x86/sigreturn.c

index b5aa1bab7416394918fd59a441b0bda3853cfd28..8a577e7070c64104d74aa1e4361cfe8709894e25 100644 (file)
 #include <sys/ptrace.h>
 #include <sys/user.h>
 
+/* Pull in AR_xyz defines. */
+typedef unsigned int u32;
+typedef unsigned short u16;
+#include "../../../../arch/x86/include/asm/desc_defs.h"
+
+/*
+ * Copied from asm/ucontext.h, as asm/ucontext.h conflicts badly with the glibc
+ * headers.
+ */
+#ifdef __x86_64__
+/*
+ * UC_SIGCONTEXT_SS will be set when delivering 64-bit or x32 signals on
+ * kernels that save SS in the sigcontext.  All kernels that set
+ * UC_SIGCONTEXT_SS will correctly restore at least the low 32 bits of esp
+ * regardless of SS (i.e. they implement espfix).
+ *
+ * Kernels that set UC_SIGCONTEXT_SS will also set UC_STRICT_RESTORE_SS
+ * when delivering a signal that came from 64-bit code.
+ *
+ * Sigreturn restores SS as follows:
+ *
+ * if (saved SS is valid || UC_STRICT_RESTORE_SS is set ||
+ *     saved CS is not 64-bit)
+ *         new SS = saved SS  (will fail IRET and signal if invalid)
+ * else
+ *         new SS = a flat 32-bit data segment
+ */
+#define UC_SIGCONTEXT_SS       0x2
+#define UC_STRICT_RESTORE_SS   0x4
+#endif
+
 /*
  * In principle, this test can run on Linux emulation layers (e.g.
  * Illumos "LX branded zones").  Solaris-based kernels reserve LDT
@@ -267,6 +298,9 @@ static gregset_t initial_regs, requested_regs, resulting_regs;
 /* Instructions for the SIGUSR1 handler. */
 static volatile unsigned short sig_cs, sig_ss;
 static volatile sig_atomic_t sig_trapped, sig_err, sig_trapno;
+#ifdef __x86_64__
+static volatile sig_atomic_t sig_corrupt_final_ss;
+#endif
 
 /* Abstractions for some 32-bit vs 64-bit differences. */
 #ifdef __x86_64__
@@ -305,9 +339,105 @@ static greg_t *csptr(ucontext_t *ctx)
 }
 #endif
 
+/*
+ * Checks a given selector for its code bitness or returns -1 if it's not
+ * a usable code segment selector.
+ */
+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. */
+}
+
+/*
+ * Checks a given selector for its code bitness or returns -1 if it's not
+ * a usable code segment selector.
+ */
+bool is_valid_ss(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 false;
+
+       if ((ar & AR_TYPE_MASK) != AR_TYPE_RWDATA &&
+           (ar & AR_TYPE_MASK) != AR_TYPE_RWDATA_EXPDOWN)
+               return false;
+
+       return (ar & AR_P);
+}
+
 /* Number of errors in the current test case. */
 static volatile sig_atomic_t nerrs;
 
+static void validate_signal_ss(int sig, ucontext_t *ctx)
+{
+#ifdef __x86_64__
+       bool was_64bit = (cs_bitness(*csptr(ctx)) == 64);
+
+       if (!(ctx->uc_flags & UC_SIGCONTEXT_SS)) {
+               printf("[FAIL]\tUC_SIGCONTEXT_SS was not set\n");
+               nerrs++;
+
+               /*
+                * This happens on Linux 4.1.  The rest will fail, too, so
+                * return now to reduce the noise.
+                */
+               return;
+       }
+
+       /* UC_STRICT_RESTORE_SS is set iff we came from 64-bit mode. */
+       if (!!(ctx->uc_flags & UC_STRICT_RESTORE_SS) != was_64bit) {
+               printf("[FAIL]\tUC_STRICT_RESTORE_SS was wrong in signal %d\n",
+                      sig);
+               nerrs++;
+       }
+
+       if (is_valid_ss(*ssptr(ctx))) {
+               /*
+                * DOSEMU was written before 64-bit sigcontext had SS, and
+                * it tries to figure out the signal source SS by looking at
+                * the physical register.  Make sure that keeps working.
+                */
+               unsigned short hw_ss;
+               asm ("mov %%ss, %0" : "=rm" (hw_ss));
+               if (hw_ss != *ssptr(ctx)) {
+                       printf("[FAIL]\tHW SS didn't match saved SS\n");
+                       nerrs++;
+               }
+       }
+#endif
+}
+
 /*
  * SIGUSR1 handler.  Sets CS and SS as requested and points IP to the
  * int3 trampoline.  Sets SP to a large known value so that we can see
@@ -317,6 +447,8 @@ static void sigusr1(int sig, siginfo_t *info, void *ctx_void)
 {
        ucontext_t *ctx = (ucontext_t*)ctx_void;
 
+       validate_signal_ss(sig, ctx);
+
        memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
 
        *csptr(ctx) = sig_cs;
@@ -334,13 +466,16 @@ static void sigusr1(int sig, siginfo_t *info, void *ctx_void)
 }
 
 /*
- * Called after a successful sigreturn.  Restores our state so that
- * the original raise(SIGUSR1) returns.
+ * Called after a successful sigreturn (via int3) or from a failed
+ * sigreturn (directly by kernel).  Restores our state so that the
+ * original raise(SIGUSR1) returns.
  */
 static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
 {
        ucontext_t *ctx = (ucontext_t*)ctx_void;
 
+       validate_signal_ss(sig, ctx);
+
        sig_err = ctx->uc_mcontext.gregs[REG_ERR];
        sig_trapno = ctx->uc_mcontext.gregs[REG_TRAPNO];
 
@@ -358,41 +493,62 @@ static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
        memcpy(&resulting_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
        memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t));
 
+#ifdef __x86_64__
+       if (sig_corrupt_final_ss) {
+               if (ctx->uc_flags & UC_STRICT_RESTORE_SS) {
+                       printf("[FAIL]\tUC_STRICT_RESTORE_SS was set inappropriately\n");
+                       nerrs++;
+               } else {
+                       /*
+                        * DOSEMU transitions from 32-bit to 64-bit mode by
+                        * adjusting sigcontext, and it requires that this work
+                        * even if the saved SS is bogus.
+                        */
+                       printf("\tCorrupting SS on return to 64-bit mode\n");
+                       *ssptr(ctx) = 0;
+               }
+       }
+#endif
+
        sig_trapped = sig;
 }
 
-/*
- * Checks a given selector for its code bitness or returns -1 if it's not
- * a usable code segment selector.
- */
-int cs_bitness(unsigned short cs)
+#ifdef __x86_64__
+/* Tests recovery if !UC_STRICT_RESTORE_SS */
+static void sigusr2(int sig, siginfo_t *info, void *ctx_void)
 {
-       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));
+       ucontext_t *ctx = (ucontext_t*)ctx_void;
 
-       if (!valid)
-               return -1;
+       if (!(ctx->uc_flags & UC_STRICT_RESTORE_SS)) {
+               printf("[FAIL]\traise(2) didn't set UC_STRICT_RESTORE_SS\n");
+               nerrs++;
+               return;  /* We can't do the rest. */
+       }
 
-       bool db = (ar & (1 << 22));
-       bool l = (ar & (1 << 21));
+       ctx->uc_flags &= ~UC_STRICT_RESTORE_SS;
+       *ssptr(ctx) = 0;
 
-       if (!(ar & (1<<11)))
-           return -1;  /* Not code. */
+       /* Return.  The kernel should recover without sending another signal. */
+}
 
-       if (l && !db)
-               return 64;
-       else if (!l && db)
-               return 32;
-       else if (!l && !db)
-               return 16;
-       else
-               return -1;      /* Unknown bitness. */
+static int test_nonstrict_ss(void)
+{
+       clearhandler(SIGUSR1);
+       clearhandler(SIGTRAP);
+       clearhandler(SIGSEGV);
+       clearhandler(SIGILL);
+       sethandler(SIGUSR2, sigusr2, 0);
+
+       nerrs = 0;
+
+       printf("[RUN]\tClear UC_STRICT_RESTORE_SS and corrupt SS\n");
+       raise(SIGUSR2);
+       if (!nerrs)
+               printf("[OK]\tIt worked\n");
+
+       return nerrs;
 }
+#endif
 
 /* Finds a usable code segment of the requested bitness. */
 int find_cs(int bitness)
@@ -576,6 +732,12 @@ static int test_bad_iret(int cs_bits, unsigned short ss, int force_cs)
                       errdesc, strsignal(sig_trapped));
                return 0;
        } else {
+               /*
+                * This also implicitly tests UC_STRICT_RESTORE_SS:
+                * We check that these signals set UC_STRICT_RESTORE_SS and,
+                * if UC_STRICT_RESTORE_SS doesn't cause strict behavior,
+                * then we won't get SIGSEGV.
+                */
                printf("[FAIL]\tDid not get SIGSEGV\n");
                return 1;
        }
@@ -632,6 +794,14 @@ int main()
                                                    GDT3(gdt_data16_idx));
        }
 
+#ifdef __x86_64__
+       /* Nasty ABI case: check SS corruption handling. */
+       sig_corrupt_final_ss = 1;
+       total_nerrs += test_valid_sigreturn(32, false, -1);
+       total_nerrs += test_valid_sigreturn(32, true, -1);
+       sig_corrupt_final_ss = 0;
+#endif
+
        /*
         * We're done testing valid sigreturn cases.  Now we test states
         * for which sigreturn itself will succeed but the subsequent
@@ -680,5 +850,9 @@ int main()
        if (gdt_npdata32_idx)
                test_bad_iret(32, GDT3(gdt_npdata32_idx), -1);
 
+#ifdef __x86_64__
+       total_nerrs += test_nonstrict_ss();
+#endif
+
        return total_nerrs ? 1 : 0;
 }