Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86/signal/64: Re-add support for SS in the 64-bit signal context

This is a second attempt to make the improvements from c6f2062935c8
("x86/signal/64: Fix SS handling for signals delivered to 64-bit
programs"), which was reverted by 51adbfbba5c6 ("x86/signal/64: Add
support for SS in the 64-bit signal context").

This adds two new uc_flags flags. UC_SIGCONTEXT_SS will be set for
all 64-bit signals (including x32). It indicates that the saved SS
field is valid and that the kernel supports the new behavior.

The goal is to fix a problems with signal handling in 64-bit tasks:
SS wasn't saved in the 64-bit signal context, making it awkward to
determine what SS was at the time of signal delivery and making it
impossible to return to a non-flat SS (as calling sigreturn clobbers
SS).

This also made it extremely difficult for 64-bit tasks to return to
fully-defined 16-bit contexts, because only the kernel can easily do
espfix64, but sigreturn was unable to set a non-flag SS:ESP.
(DOSEMU has a monstrous hack to partially work around this
limitation.)

If we could go back in time, the correct fix would be to make 64-bit
signals work just like 32-bit signals with respect to SS: save it
in signal context, reset it when delivering a signal, and restore
it in sigreturn.

Unfortunately, doing that (as I tried originally) breaks DOSEMU:
DOSEMU wouldn't reset the signal context's SS when clearing the LDT
and changing the saved CS to 64-bit mode, since it predates the SS
context field existing in the first place.

This patch is a bit more complicated, and it tries to balance a
bunch of goals. It makes most cases of changing ucontext->ss during
signal handling work as expected.

I do this by special-casing the interesting case. On sigreturn,
ucontext->ss will be honored by default, unless the ucontext was
created from scratch by an old program and had a 64-bit CS
(unfortunately, CRIU can do this) or was the result of changing a
32-bit signal context to 64-bit without resetting SS (as DOSEMU
does).

For the benefit of new 64-bit software that uses segmentation (new
versions of DOSEMU might), the new behavior can be detected with a
new ucontext flag UC_SIGCONTEXT_SS.

To avoid compilation issues, __pad0 is left as an alias for ss in
ucontext.

The nitty-gritty details are documented in the header file.

This patch also re-enables the sigreturn_64 and ldt_gdt_64 selftests,
as the kernel change allows both of them to pass.

Tested-by: Stas Sergeev <stsp@list.ru>
Signed-off-by: Andy Lutomirski <luto@kernel.org>
Acked-by: Borislav 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: Thomas Gleixner <tglx@linutronix.de>
Link: http://lkml.kernel.org/r/749149cbfc3e75cd7fcdad69a854b399d792cc6f.1455664054.git.luto@kernel.org
[ Small readability edit. ]
Signed-off-by: Ingo Molnar <mingo@kernel.org>

authored by

Andy Lutomirski and committed by
Ingo Molnar
6c25da5a 8ff5bd2e

+99 -30
-1
arch/x86/include/asm/sighandling.h
··· 13 13 X86_EFLAGS_CF | X86_EFLAGS_RF) 14 14 15 15 void signal_fault(struct pt_regs *regs, void __user *frame, char *where); 16 - int restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc); 17 16 int setup_sigcontext(struct sigcontext __user *sc, void __user *fpstate, 18 17 struct pt_regs *regs, unsigned long mask); 19 18
+5 -2
arch/x86/include/uapi/asm/sigcontext.h
··· 256 256 __u16 cs; 257 257 __u16 gs; 258 258 __u16 fs; 259 - __u16 __pad0; 259 + __u16 ss; 260 260 __u64 err; 261 261 __u64 trapno; 262 262 __u64 oldmask; ··· 368 368 */ 369 369 __u16 gs; 370 370 __u16 fs; 371 - __u16 __pad0; 371 + union { 372 + __u16 ss; /* If UC_SIGCONTEXT_SS */ 373 + __u16 __pad0; /* Alias name for old (!UC_SIGCONTEXT_SS) user-space */ 374 + }; 372 375 __u64 err; 373 376 __u64 trapno; 374 377 __u64 oldmask;
+48 -5
arch/x86/include/uapi/asm/ucontext.h
··· 1 1 #ifndef _ASM_X86_UCONTEXT_H 2 2 #define _ASM_X86_UCONTEXT_H 3 3 4 - #define UC_FP_XSTATE 0x1 /* indicates the presence of extended state 5 - * information in the memory layout pointed 6 - * by the fpstate pointer in the ucontext's 7 - * sigcontext struct (uc_mcontext). 8 - */ 4 + /* 5 + * Indicates the presence of extended state information in the memory 6 + * layout pointed by the fpstate pointer in the ucontext's sigcontext 7 + * struct (uc_mcontext). 8 + */ 9 + #define UC_FP_XSTATE 0x1 10 + 11 + #ifdef __x86_64__ 12 + /* 13 + * UC_SIGCONTEXT_SS will be set when delivering 64-bit or x32 signals on 14 + * kernels that save SS in the sigcontext. All kernels that set 15 + * UC_SIGCONTEXT_SS will correctly restore at least the low 32 bits of esp 16 + * regardless of SS (i.e. they implement espfix). 17 + * 18 + * Kernels that set UC_SIGCONTEXT_SS will also set UC_STRICT_RESTORE_SS 19 + * when delivering a signal that came from 64-bit code. 20 + * 21 + * Sigreturn restores SS as follows: 22 + * 23 + * if (saved SS is valid || UC_STRICT_RESTORE_SS is set || 24 + * saved CS is not 64-bit) 25 + * new SS = saved SS (will fail IRET and signal if invalid) 26 + * else 27 + * new SS = a flat 32-bit data segment 28 + * 29 + * This behavior serves three purposes: 30 + * 31 + * - Legacy programs that construct a 64-bit sigcontext from scratch 32 + * with zero or garbage in the SS slot (e.g. old CRIU) and call 33 + * sigreturn will still work. 34 + * 35 + * - Old DOSEMU versions sometimes catch a signal from a segmented 36 + * context, delete the old SS segment (with modify_ldt), and change 37 + * the saved CS to a 64-bit segment. These DOSEMU versions expect 38 + * sigreturn to send them back to 64-bit mode without killing them, 39 + * despite the fact that the SS selector when the signal was raised is 40 + * no longer valid. UC_STRICT_RESTORE_SS will be clear, so the kernel 41 + * will fix up SS for these DOSEMU versions. 42 + * 43 + * - Old and new programs that catch a signal and return without 44 + * modifying the saved context will end up in exactly the state they 45 + * started in, even if they were running in a segmented context when 46 + * the signal was raised.. Old kernels would lose track of the 47 + * previous SS value. 48 + */ 49 + #define UC_SIGCONTEXT_SS 0x2 50 + #define UC_STRICT_RESTORE_SS 0x4 51 + #endif 9 52 10 53 #include <asm-generic/ucontext.h> 11 54
+44 -19
arch/x86/kernel/signal.c
··· 90 90 } 91 91 #endif 92 92 93 - int restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc) 93 + static int restore_sigcontext(struct pt_regs *regs, 94 + struct sigcontext __user *sc, 95 + unsigned long uc_flags) 94 96 { 95 97 unsigned long buf_val; 96 98 void __user *buf; ··· 125 123 COPY(r15); 126 124 #endif /* CONFIG_X86_64 */ 127 125 128 - #ifdef CONFIG_X86_32 129 126 COPY_SEG_CPL3(cs); 130 127 COPY_SEG_CPL3(ss); 131 - #else /* !CONFIG_X86_32 */ 132 - /* Kernel saves and restores only the CS segment register on signals, 133 - * which is the bare minimum needed to allow mixed 32/64-bit code. 134 - * App's signal handler can save/restore other segments if needed. */ 135 - COPY_SEG_CPL3(cs); 136 - #endif /* CONFIG_X86_32 */ 128 + 129 + #ifdef CONFIG_X86_64 130 + /* 131 + * Fix up SS if needed for the benefit of old DOSEMU and 132 + * CRIU. 133 + */ 134 + if (unlikely(!(uc_flags & UC_STRICT_RESTORE_SS) && 135 + user_64bit_mode(regs))) 136 + force_valid_ss(regs); 137 + #endif 137 138 138 139 get_user_ex(tmpflags, &sc->flags); 139 140 regs->flags = (regs->flags & ~FIX_EFLAGS) | (tmpflags & FIX_EFLAGS); ··· 199 194 put_user_ex(regs->cs, &sc->cs); 200 195 put_user_ex(0, &sc->gs); 201 196 put_user_ex(0, &sc->fs); 197 + put_user_ex(regs->ss, &sc->ss); 202 198 #endif /* CONFIG_X86_32 */ 203 199 204 200 put_user_ex(fpstate, &sc->fpstate); ··· 438 432 return 0; 439 433 } 440 434 #else /* !CONFIG_X86_32 */ 435 + static unsigned long frame_uc_flags(struct pt_regs *regs) 436 + { 437 + unsigned long flags; 438 + 439 + if (cpu_has_xsave) 440 + flags = UC_FP_XSTATE | UC_SIGCONTEXT_SS; 441 + else 442 + flags = UC_SIGCONTEXT_SS; 443 + 444 + if (likely(user_64bit_mode(regs))) 445 + flags |= UC_STRICT_RESTORE_SS; 446 + 447 + return flags; 448 + } 449 + 441 450 static int __setup_rt_frame(int sig, struct ksignal *ksig, 442 451 sigset_t *set, struct pt_regs *regs) 443 452 { ··· 472 451 473 452 put_user_try { 474 453 /* Create the ucontext. */ 475 - if (cpu_has_xsave) 476 - put_user_ex(UC_FP_XSTATE, &frame->uc.uc_flags); 477 - else 478 - put_user_ex(0, &frame->uc.uc_flags); 454 + put_user_ex(frame_uc_flags(regs), &frame->uc.uc_flags); 479 455 put_user_ex(0, &frame->uc.uc_link); 480 456 save_altstack_ex(&frame->uc.uc_stack, regs->sp); 481 457 ··· 554 536 555 537 put_user_try { 556 538 /* Create the ucontext. */ 557 - if (cpu_has_xsave) 558 - put_user_ex(UC_FP_XSTATE, &frame->uc.uc_flags); 559 - else 560 - put_user_ex(0, &frame->uc.uc_flags); 539 + put_user_ex(frame_uc_flags(regs), &frame->uc.uc_flags); 561 540 put_user_ex(0, &frame->uc.uc_link); 562 541 compat_save_altstack_ex(&frame->uc.uc_stack, regs->sp); 563 542 put_user_ex(0, &frame->uc.uc__pad0); ··· 616 601 617 602 set_current_blocked(&set); 618 603 619 - if (restore_sigcontext(regs, &frame->sc)) 604 + /* 605 + * x86_32 has no uc_flags bits relevant to restore_sigcontext. 606 + * Save a few cycles by skipping the __get_user. 607 + */ 608 + if (restore_sigcontext(regs, &frame->sc, 0)) 620 609 goto badframe; 621 610 return regs->ax; 622 611 ··· 636 617 struct pt_regs *regs = current_pt_regs(); 637 618 struct rt_sigframe __user *frame; 638 619 sigset_t set; 620 + unsigned long uc_flags; 639 621 640 622 frame = (struct rt_sigframe __user *)(regs->sp - sizeof(long)); 641 623 if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) 642 624 goto badframe; 643 625 if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) 644 626 goto badframe; 627 + if (__get_user(uc_flags, &frame->uc.uc_flags)) 628 + goto badframe; 645 629 646 630 set_current_blocked(&set); 647 631 648 - if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) 632 + if (restore_sigcontext(regs, &frame->uc.uc_mcontext, uc_flags)) 649 633 goto badframe; 650 634 651 635 if (restore_altstack(&frame->uc.uc_stack)) ··· 835 813 struct pt_regs *regs = current_pt_regs(); 836 814 struct rt_sigframe_x32 __user *frame; 837 815 sigset_t set; 816 + unsigned long uc_flags; 838 817 839 818 frame = (struct rt_sigframe_x32 __user *)(regs->sp - 8); 840 819 ··· 843 820 goto badframe; 844 821 if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) 845 822 goto badframe; 823 + if (__get_user(uc_flags, &frame->uc.uc_flags)) 824 + goto badframe; 846 825 847 826 set_current_blocked(&set); 848 827 849 - if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) 828 + if (restore_sigcontext(regs, &frame->uc.uc_mcontext, uc_flags)) 850 829 goto badframe; 851 830 852 831 if (compat_restore_altstack(&frame->uc.uc_stack))
+2 -3
tools/testing/selftests/x86/Makefile
··· 5 5 .PHONY: all all_32 all_64 warn_32bit_failure clean 6 6 7 7 TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_syscall \ 8 - check_initial_reg_state 9 - TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault sigreturn test_syscall_vdso unwind_vdso \ 8 + check_initial_reg_state sigreturn ldt_gdt 9 + TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \ 10 10 test_FCMOV test_FCOMI test_FISTTP \ 11 - ldt_gdt \ 12 11 vdso_restorer 13 12 14 13 TARGETS_C_32BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_32BIT_ONLY)