Linux used to have nearly useless SS handling for 64-bit signals. Signal delivery to a 64-bit process would preserve the SS selector, but the selector wasn't saved in sigcontext. Sigreturn would then clobber SS. If the signal being delivered was due to a bad SS, signal delivery would fail and the task would be killed. As of Linux 4.1, it's fixed: signal delivery sets up a valid SS in the hardware SS selector, saves the old SS in the sigcontext, and restores it properly in sigreturn. DOSEMU had a curious workaround for the old behavior: it saved the hardware SS selector it was given during signal delivery and fudged RIP and CS so that sigreturn would return to a trampoline that restored the old RIP, CS, and, importantly, SS. The upshot is that the change in sigcontext had no effect on DOSEMU (DOSEMU doesn't care what was in sigcontext, and the fact that the old SS is presented to the trampoline in new kernels is irrelevant because the trampoline uses long mode), but the change in signal delivery caused DOSEMU's workaround to restore __USER_DS instead of the correct pre-signal SS value. Do our best to work around it: explicitly check whether the old SS is usable and leave it alone during signal delivery if it is. Sigreturn is unchanged. Reported-by: Stas Sergeev <stsp@xxxxxxx> Fixes: c6f2062935c8 ("x86/signal/64: Fix SS handling for signals delivered to 64-bit programs") Signed-off-by: Andy Lutomirski <luto@xxxxxxxxxx> --- arch/x86/include/asm/desc_defs.h | 23 +++++++++++++++++++++++ arch/x86/kernel/signal.c | 29 +++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/arch/x86/include/asm/desc_defs.h b/arch/x86/include/asm/desc_defs.h index 278441f39856..00971705a16d 100644 --- a/arch/x86/include/asm/desc_defs.h +++ b/arch/x86/include/asm/desc_defs.h @@ -98,4 +98,27 @@ struct desc_ptr { #endif /* !__ASSEMBLY__ */ +/* Access rights as returned by LAR */ +#define AR_TYPE_RODATA (0 * (1 << 9)) +#define AR_TYPE_RWDATA (1 * (1 << 9)) +#define AR_TYPE_RODATA_EXPDOWN (2 * (1 << 9)) +#define AR_TYPE_RWDATA_EXPDOWN (3 * (1 << 9)) +#define AR_TYPE_XOCODE (4 * (1 << 9)) +#define AR_TYPE_XRCODE (5 * (1 << 9)) +#define AR_TYPE_XOCODE_CONF (6 * (1 << 9)) +#define AR_TYPE_XRCODE_CONF (7 * (1 << 9)) +#define AR_TYPE_MASK (7 * (1 << 9)) + +#define AR_DPL0 (0 * (1 << 13)) +#define AR_DPL3 (3 * (1 << 13)) +#define AR_DPL_MASK (3 * (1 << 13)) + +#define AR_A (1 << 8) /* A means "accessed" */ +#define AR_S (1 << 12) /* S means "not system" */ +#define AR_P (1 << 15) /* P means "present" */ +#define AR_AVL (1 << 20) /* AVL does nothing */ +#define AR_L (1 << 21) /* L means "long mode" */ +#define AR_DB (1 << 22) /* D or B, depending on type */ +#define AR_G (1 << 23) /* G means "limit in pages" */ + #endif /* _ASM_X86_DESC_DEFS_H */ diff --git a/arch/x86/kernel/signal.c b/arch/x86/kernel/signal.c index 206996c1669d..784af1e49fc1 100644 --- a/arch/x86/kernel/signal.c +++ b/arch/x86/kernel/signal.c @@ -460,10 +460,35 @@ static int __setup_rt_frame(int sig, struct ksignal *ksig, * SS descriptor, but we do need SS to be valid. It's possible * that the old SS is entirely bogus -- this can happen if the * signal we're trying to deliver is #GP or #SS caused by a bad - * SS value. + * SS value. We also have a compatbility issue here: DOSEMU + * relies on the contents of the SS register indicating the + * SS value at the time of the signal, even though that code in + * DOSEMU predates sigreturn's ability to restore SS. (DOSEMU + * avoids relying on sigreturn to restore SS; instead it uses + * a trampoline.) So we do our best: if the old SS was valid, + * we keep it. Otherwise we replace it. */ regs->cs = __USER_CS; - regs->ss = __USER_DS; + + if (unlikely(regs->ss != __USER_DS)) { + u32 ar; + asm ("lar %[old_ss], %[ar]\n\t" + "jz 1f\n\t" + "xorl %[ar], %[ar]\n\t" /* If invalid, set ar = 0 */ + "1:" + : [ar] "=r" (ar) + : [old_ss] "rm" ((u16)regs->ss)); + + /* + * For a valid 64-bit user context, we need DPL 3, type + * read-write data or read-write exp-down data, and S and P + * set. + */ + ar &= AR_DPL_MASK | AR_S | AR_P | AR_TYPE_MASK; + if (ar != (AR_DPL3 | AR_S | AR_P | AR_TYPE_RWDATA) && + ar != (AR_DPL3 | AR_S | AR_P | AR_TYPE_RWDATA_EXPDOWN)) + regs->ss = __USER_DS; + } return 0; } -- 2.4.3 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html