@@ -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);
@@ -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>
@@ -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))
@@ -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;
}
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@list.ru> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Cyrill Gorcunov <gorcunov@gmail.com> Cc: Pavel Emelyanov <xemul@parallels.com> Signed-off-by: Andy Lutomirski <luto@kernel.org> --- 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(-)