|
@@ -54,6 +54,37 @@
|
|
|
#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;
|
|
|
}
|