This adds two new uc_flags flags. UC_SAVED_SS will be set for all 64-bit signals (including x32). It indicates that the saved SS field is valid and that the kernel understands UC_RESTORE_SS. The kernel will *not* set UC_RESTORE_SS. User signal handlers can set UC_RESTORE_SS themselves to indicate that sigreturn should restore SS from the sigcontext. 64-bit programs that use segmentation are encouraged to check UC_SAVED_SS and set UC_RESTORE_SS in their signal handlers. This is the only straightforward way to cause sigreturn to restore SS. (The only non-test program that I know of that uses segmentation in a 64-bit binary is DOSEMU, and DOSEMU currently uses a nasty trampoline to work around the lack of this mechanism in old kernels. It could detect UC_RESTORE_SS and use it to avoid needing a trampoline. Cc: Stas Sergeev <stsp@xxxxxxx> Cc: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx> Cc: Cyrill Gorcunov <gorcunov@xxxxxxxxx> Cc: Pavel Emelyanov <xemul@xxxxxxxxxxxxx> Signed-off-by: Andy Lutomirski <luto@xxxxxxxxxx> --- arch/x86/include/asm/sighandling.h | 1 - arch/x86/include/uapi/asm/ucontext.h | 26 +++++++++++++++++---- arch/x86/kernel/signal.c | 41 ++++++++++++++++++++++++++------- tools/testing/selftests/x86/sigreturn.c | 26 +++++++++++++++++++++ 4 files changed, 80 insertions(+), 14 deletions(-) diff --git a/arch/x86/include/asm/sighandling.h b/arch/x86/include/asm/sighandling.h index 89db46752a8f..452c88b8ad06 100644 --- a/arch/x86/include/asm/sighandling.h +++ b/arch/x86/include/asm/sighandling.h @@ -13,7 +13,6 @@ X86_EFLAGS_CF | X86_EFLAGS_RF) void signal_fault(struct pt_regs *regs, void __user *frame, char *where); -int restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc); int setup_sigcontext(struct sigcontext __user *sc, void __user *fpstate, struct pt_regs *regs, unsigned long mask); diff --git a/arch/x86/include/uapi/asm/ucontext.h b/arch/x86/include/uapi/asm/ucontext.h index b7c29c8017f2..964bc3b46ff3 100644 --- a/arch/x86/include/uapi/asm/ucontext.h +++ b/arch/x86/include/uapi/asm/ucontext.h @@ -1,11 +1,27 @@ #ifndef _ASM_X86_UCONTEXT_H #define _ASM_X86_UCONTEXT_H -#define UC_FP_XSTATE 0x1 /* indicates the presence of extended state - * information in the memory layout pointed - * by the fpstate pointer in the ucontext's - * sigcontext struct (uc_mcontext). - */ +/* + * indicates the presence of extended state + * information in the memory layout pointed + * by the fpstate pointer in the ucontext's + * sigcontext struct (uc_mcontext). + */ +#define UC_FP_XSTATE 0x1 + +#ifdef __x86_64__ +/* + * UC_SAVED_SS will be set when delivering 64-bit or x32 signals on + * kernels that save SS in the sigcontext. Kernels that set UC_SAVED_SS + * allow signal handlers to set UC_RESTORE_SS; if UC_RESTORE_SS is set, + * then sigreturn will restore SS. + * + * For compatibility with old programs, the kernel will *not* set + * UC_RESTORE_SS when delivering signals. + */ +#define UC_SAVED_SS 0x2 +#define UC_RESTORE_SS 0x4 +#endif #include <asm-generic/ucontext.h> diff --git a/arch/x86/kernel/signal.c b/arch/x86/kernel/signal.c index 784af1e49fc1..746250e9bce1 100644 --- a/arch/x86/kernel/signal.c +++ b/arch/x86/kernel/signal.c @@ -61,7 +61,9 @@ regs->seg = GET_SEG(seg) | 3; \ } while (0) -int restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc) +static int restore_sigcontext(struct pt_regs *regs, + struct sigcontext __user *sc, + unsigned long uc_flags) { void __user *buf; unsigned int tmpflags; @@ -94,7 +96,19 @@ int restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc) #endif /* CONFIG_X86_64 */ COPY_SEG_CPL3(cs); + +#ifdef CONFIG_X86_64 + /* + * For the 64-bit ABI, we only restore SS if UC_RESTORE_SS + * is set. Otherwise we rely on the fact that regs->ss + * is already set to __USER_DS by the SYSCALL entry code. + */ + if (uc_flags & UC_RESTORE_SS) + COPY_SEG_CPL3(ss); +#else + /* For the 32-bit ABI, we always restore SS. */ COPY_SEG_CPL3(ss); +#endif get_user_ex(tmpflags, &sc->flags); regs->flags = (regs->flags & ~FIX_EFLAGS) | (tmpflags & FIX_EFLAGS); @@ -336,6 +350,7 @@ static int __setup_rt_frame(int sig, struct ksignal *ksig, void __user *restorer; int err = 0; void __user *fpstate = NULL; + unsigned long flags = 0; frame = get_sigframe(&ksig->ka, regs, sizeof(*frame), &fpstate); @@ -349,9 +364,12 @@ static int __setup_rt_frame(int sig, struct ksignal *ksig, /* Create the ucontext. */ if (cpu_has_xsave) - put_user_ex(UC_FP_XSTATE, &frame->uc.uc_flags); - else - put_user_ex(0, &frame->uc.uc_flags); + flags = UC_FP_XSTATE; +#ifdef CONFIG_X86_64 + flags |= UC_SAVED_SS; +#endif + put_user_ex(flags, &frame->uc.uc_flags); + put_user_ex(0, &frame->uc.uc_link); save_altstack_ex(&frame->uc.uc_stack, regs->sp); @@ -517,9 +535,10 @@ static int x32_setup_rt_frame(struct ksignal *ksig, put_user_try { /* Create the ucontext. */ if (cpu_has_xsave) - put_user_ex(UC_FP_XSTATE, &frame->uc.uc_flags); + put_user_ex(UC_FP_XSTATE | UC_SAVED_SS, + &frame->uc.uc_flags); else - put_user_ex(0, &frame->uc.uc_flags); + put_user_ex(UC_SAVED_SS, &frame->uc.uc_flags); put_user_ex(0, &frame->uc.uc_link); compat_save_altstack_ex(&frame->uc.uc_stack, regs->sp); put_user_ex(0, &frame->uc.uc__pad0); @@ -597,16 +616,19 @@ asmlinkage long sys_rt_sigreturn(void) struct pt_regs *regs = current_pt_regs(); struct rt_sigframe __user *frame; sigset_t set; + unsigned long uc_flags; frame = (struct rt_sigframe __user *)(regs->sp - sizeof(long)); if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) goto badframe; if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) goto badframe; + if (__get_user(uc_flags, &frame->uc.uc_flags)) + goto badframe; set_current_blocked(&set); - if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) + if (restore_sigcontext(regs, &frame->uc.uc_mcontext, uc_flags)) goto badframe; if (restore_altstack(&frame->uc.uc_stack)) @@ -810,6 +832,7 @@ asmlinkage long sys32_x32_rt_sigreturn(void) struct pt_regs *regs = current_pt_regs(); struct rt_sigframe_x32 __user *frame; sigset_t set; + unsigned long uc_flags; frame = (struct rt_sigframe_x32 __user *)(regs->sp - 8); @@ -817,10 +840,12 @@ asmlinkage long sys32_x32_rt_sigreturn(void) goto badframe; if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) goto badframe; + if (__get_user(uc_flags, &frame->uc.uc_flags)) + goto badframe; set_current_blocked(&set); - if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) + if (restore_sigcontext(regs, &frame->uc.uc_mcontext, uc_flags)) goto badframe; if (compat_restore_altstack(&frame->uc.uc_stack)) diff --git a/tools/testing/selftests/x86/sigreturn.c b/tools/testing/selftests/x86/sigreturn.c index b5aa1bab7416..8f0176b91854 100644 --- a/tools/testing/selftests/x86/sigreturn.c +++ b/tools/testing/selftests/x86/sigreturn.c @@ -55,6 +55,24 @@ #include <sys/user.h> /* + * Copies from asm/ucontext.h, as asm/ucontext.h conflicts badly with the glibc + * headers. + */ +#ifdef __x86_64__ +/* + * UC_SAVED_SS will be set when delivering 64-bit or x32 signals on + * kernels that save SS in the sigcontext. Kernels that set UC_SAVED_SS + * allow signal handlers to set UC_RESTORE_SS; if UC_RESTORE_SS is set, + * then sigreturn will restore SS. + * + * For compatibility with old programs, the kernel will *not* set + * UC_RESTORE_SS when delivering signals. + */ +#define UC_SAVED_SS 0x2 +#define UC_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 * entries 0-5 for their own internal purposes, so start our LDT @@ -330,6 +348,10 @@ static void sigusr1(int sig, siginfo_t *info, void *ctx_void) memcpy(&requested_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); requested_regs[REG_AX] = *ssptr(ctx); /* The asm code does this. */ +#ifdef __x86_64__ + ctx->uc_flags |= UC_RESTORE_SS; +#endif + return; } @@ -358,6 +380,10 @@ 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__ + ctx->uc_flags |= UC_RESTORE_SS; +#endif + sig_trapped = sig; } -- 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