Commit 4f6c8938 authored by Andy Lutomirski's avatar Andy Lutomirski Committed by Ingo Molnar

selftests/x86: Add tests for UC_SIGCONTEXT_SS and UC_STRICT_RESTORE_SS

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: default avatarAndy Lutomirski <luto@kernel.org>
Acked-by: default avatarBorislav 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.orgSigned-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent 6c25da5a
...@@ -54,6 +54,37 @@ ...@@ -54,6 +54,37 @@
#include <sys/ptrace.h> #include <sys/ptrace.h>
#include <sys/user.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. * In principle, this test can run on Linux emulation layers (e.g.
* Illumos "LX branded zones"). Solaris-based kernels reserve LDT * Illumos "LX branded zones"). Solaris-based kernels reserve LDT
...@@ -267,6 +298,9 @@ static gregset_t initial_regs, requested_regs, resulting_regs; ...@@ -267,6 +298,9 @@ static gregset_t initial_regs, requested_regs, resulting_regs;
/* Instructions for the SIGUSR1 handler. */ /* Instructions for the SIGUSR1 handler. */
static volatile unsigned short sig_cs, sig_ss; static volatile unsigned short sig_cs, sig_ss;
static volatile sig_atomic_t sig_trapped, sig_err, sig_trapno; 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. */ /* Abstractions for some 32-bit vs 64-bit differences. */
#ifdef __x86_64__ #ifdef __x86_64__
...@@ -305,9 +339,105 @@ static greg_t *csptr(ucontext_t *ctx) ...@@ -305,9 +339,105 @@ static greg_t *csptr(ucontext_t *ctx)
} }
#endif #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. */ /* Number of errors in the current test case. */
static volatile sig_atomic_t nerrs; 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 * 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 * 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) ...@@ -317,6 +447,8 @@ static void sigusr1(int sig, siginfo_t *info, void *ctx_void)
{ {
ucontext_t *ctx = (ucontext_t*)ctx_void; ucontext_t *ctx = (ucontext_t*)ctx_void;
validate_signal_ss(sig, ctx);
memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
*csptr(ctx) = sig_cs; *csptr(ctx) = sig_cs;
...@@ -334,13 +466,16 @@ static void sigusr1(int sig, siginfo_t *info, void *ctx_void) ...@@ -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 * Called after a successful sigreturn (via int3) or from a failed
* the original raise(SIGUSR1) returns. * 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) static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
{ {
ucontext_t *ctx = (ucontext_t*)ctx_void; ucontext_t *ctx = (ucontext_t*)ctx_void;
validate_signal_ss(sig, ctx);
sig_err = ctx->uc_mcontext.gregs[REG_ERR]; sig_err = ctx->uc_mcontext.gregs[REG_ERR];
sig_trapno = ctx->uc_mcontext.gregs[REG_TRAPNO]; sig_trapno = ctx->uc_mcontext.gregs[REG_TRAPNO];
...@@ -358,41 +493,62 @@ static void sigtrap(int sig, siginfo_t *info, void *ctx_void) ...@@ -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(&resulting_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
memcpy(&ctx->uc_mcontext.gregs, &initial_regs, 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; sig_trapped = sig;
} }
/* #ifdef __x86_64__
* Checks a given selector for its code bitness or returns -1 if it's not /* Tests recovery if !UC_STRICT_RESTORE_SS */
* a usable code segment selector. static void sigusr2(int sig, siginfo_t *info, void *ctx_void)
*/
int cs_bitness(unsigned short cs)
{ {
uint32_t valid = 0, ar; ucontext_t *ctx = (ucontext_t*)ctx_void;
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) if (!(ctx->uc_flags & UC_STRICT_RESTORE_SS)) {
return -1; 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)); ctx->uc_flags &= ~UC_STRICT_RESTORE_SS;
bool l = (ar & (1 << 21)); *ssptr(ctx) = 0;
if (!(ar & (1<<11))) /* Return. The kernel should recover without sending another signal. */
return -1; /* Not code. */ }
if (l && !db) static int test_nonstrict_ss(void)
return 64; {
else if (!l && db) clearhandler(SIGUSR1);
return 32; clearhandler(SIGTRAP);
else if (!l && !db) clearhandler(SIGSEGV);
return 16; clearhandler(SIGILL);
else sethandler(SIGUSR2, sigusr2, 0);
return -1; /* Unknown bitness. */
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. */ /* Finds a usable code segment of the requested bitness. */
int find_cs(int bitness) int find_cs(int bitness)
...@@ -576,6 +732,12 @@ static int test_bad_iret(int cs_bits, unsigned short ss, int force_cs) ...@@ -576,6 +732,12 @@ static int test_bad_iret(int cs_bits, unsigned short ss, int force_cs)
errdesc, strsignal(sig_trapped)); errdesc, strsignal(sig_trapped));
return 0; return 0;
} else { } 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"); printf("[FAIL]\tDid not get SIGSEGV\n");
return 1; return 1;
} }
...@@ -632,6 +794,14 @@ int main() ...@@ -632,6 +794,14 @@ int main()
GDT3(gdt_data16_idx)); 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 * We're done testing valid sigreturn cases. Now we test states
* for which sigreturn itself will succeed but the subsequent * for which sigreturn itself will succeed but the subsequent
...@@ -680,5 +850,9 @@ int main() ...@@ -680,5 +850,9 @@ int main()
if (gdt_npdata32_idx) if (gdt_npdata32_idx)
test_bad_iret(32, GDT3(gdt_npdata32_idx), -1); test_bad_iret(32, GDT3(gdt_npdata32_idx), -1);
#ifdef __x86_64__
total_nerrs += test_nonstrict_ss();
#endif
return total_nerrs ? 1 : 0; return total_nerrs ? 1 : 0;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment