diff options
author | Andy Lutomirski <luto@kernel.org> | 2016-02-16 18:09:03 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2016-02-17 02:32:11 -0500 |
commit | 6c25da5ad55d48c41b8909bc1f4e3cd5d85bb499 (patch) | |
tree | 3dc78e9e4f4b59fa3523536e5a00f3c5e0579126 /arch/x86/kernel | |
parent | 8ff5bd2e1e2767fbf737f84d5f92668dafe7e7b0 (diff) |
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>
Diffstat (limited to 'arch/x86/kernel')
-rw-r--r-- | arch/x86/kernel/signal.c | 63 |
1 files changed, 44 insertions, 19 deletions
diff --git a/arch/x86/kernel/signal.c b/arch/x86/kernel/signal.c index 52f82c7ef57d..548ddf7d6fd2 100644 --- a/arch/x86/kernel/signal.c +++ b/arch/x86/kernel/signal.c | |||
@@ -90,7 +90,9 @@ static void force_valid_ss(struct pt_regs *regs) | |||
90 | } | 90 | } |
91 | #endif | 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 | unsigned long buf_val; | 97 | unsigned long buf_val; |
96 | void __user *buf; | 98 | void __user *buf; |
@@ -123,15 +125,18 @@ int restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc) | |||
123 | COPY(r15); | 125 | COPY(r15); |
124 | #endif /* CONFIG_X86_64 */ | 126 | #endif /* CONFIG_X86_64 */ |
125 | 127 | ||
126 | #ifdef CONFIG_X86_32 | ||
127 | COPY_SEG_CPL3(cs); | 128 | COPY_SEG_CPL3(cs); |
128 | COPY_SEG_CPL3(ss); | 129 | COPY_SEG_CPL3(ss); |
129 | #else /* !CONFIG_X86_32 */ | 130 | |
130 | /* Kernel saves and restores only the CS segment register on signals, | 131 | #ifdef CONFIG_X86_64 |
131 | * which is the bare minimum needed to allow mixed 32/64-bit code. | 132 | /* |
132 | * App's signal handler can save/restore other segments if needed. */ | 133 | * Fix up SS if needed for the benefit of old DOSEMU and |
133 | COPY_SEG_CPL3(cs); | 134 | * CRIU. |
134 | #endif /* CONFIG_X86_32 */ | 135 | */ |
136 | if (unlikely(!(uc_flags & UC_STRICT_RESTORE_SS) && | ||
137 | user_64bit_mode(regs))) | ||
138 | force_valid_ss(regs); | ||
139 | #endif | ||
135 | 140 | ||
136 | get_user_ex(tmpflags, &sc->flags); | 141 | get_user_ex(tmpflags, &sc->flags); |
137 | regs->flags = (regs->flags & ~FIX_EFLAGS) | (tmpflags & FIX_EFLAGS); | 142 | regs->flags = (regs->flags & ~FIX_EFLAGS) | (tmpflags & FIX_EFLAGS); |
@@ -194,6 +199,7 @@ int setup_sigcontext(struct sigcontext __user *sc, void __user *fpstate, | |||
194 | put_user_ex(regs->cs, &sc->cs); | 199 | put_user_ex(regs->cs, &sc->cs); |
195 | put_user_ex(0, &sc->gs); | 200 | put_user_ex(0, &sc->gs); |
196 | put_user_ex(0, &sc->fs); | 201 | put_user_ex(0, &sc->fs); |
202 | put_user_ex(regs->ss, &sc->ss); | ||
197 | #endif /* CONFIG_X86_32 */ | 203 | #endif /* CONFIG_X86_32 */ |
198 | 204 | ||
199 | put_user_ex(fpstate, &sc->fpstate); | 205 | put_user_ex(fpstate, &sc->fpstate); |
@@ -432,6 +438,21 @@ static int __setup_rt_frame(int sig, struct ksignal *ksig, | |||
432 | return 0; | 438 | return 0; |
433 | } | 439 | } |
434 | #else /* !CONFIG_X86_32 */ | 440 | #else /* !CONFIG_X86_32 */ |
441 | static unsigned long frame_uc_flags(struct pt_regs *regs) | ||
442 | { | ||
443 | unsigned long flags; | ||
444 | |||
445 | if (cpu_has_xsave) | ||
446 | flags = UC_FP_XSTATE | UC_SIGCONTEXT_SS; | ||
447 | else | ||
448 | flags = UC_SIGCONTEXT_SS; | ||
449 | |||
450 | if (likely(user_64bit_mode(regs))) | ||
451 | flags |= UC_STRICT_RESTORE_SS; | ||
452 | |||
453 | return flags; | ||
454 | } | ||
455 | |||
435 | static int __setup_rt_frame(int sig, struct ksignal *ksig, | 456 | static int __setup_rt_frame(int sig, struct ksignal *ksig, |
436 | sigset_t *set, struct pt_regs *regs) | 457 | sigset_t *set, struct pt_regs *regs) |
437 | { | 458 | { |
@@ -451,10 +472,7 @@ static int __setup_rt_frame(int sig, struct ksignal *ksig, | |||
451 | 472 | ||
452 | put_user_try { | 473 | put_user_try { |
453 | /* Create the ucontext. */ | 474 | /* Create the ucontext. */ |
454 | if (cpu_has_xsave) | 475 | put_user_ex(frame_uc_flags(regs), &frame->uc.uc_flags); |
455 | put_user_ex(UC_FP_XSTATE, &frame->uc.uc_flags); | ||
456 | else | ||
457 | put_user_ex(0, &frame->uc.uc_flags); | ||
458 | put_user_ex(0, &frame->uc.uc_link); | 476 | put_user_ex(0, &frame->uc.uc_link); |
459 | save_altstack_ex(&frame->uc.uc_stack, regs->sp); | 477 | save_altstack_ex(&frame->uc.uc_stack, regs->sp); |
460 | 478 | ||
@@ -536,10 +554,7 @@ static int x32_setup_rt_frame(struct ksignal *ksig, | |||
536 | 554 | ||
537 | put_user_try { | 555 | put_user_try { |
538 | /* Create the ucontext. */ | 556 | /* Create the ucontext. */ |
539 | if (cpu_has_xsave) | 557 | put_user_ex(frame_uc_flags(regs), &frame->uc.uc_flags); |
540 | put_user_ex(UC_FP_XSTATE, &frame->uc.uc_flags); | ||
541 | else | ||
542 | put_user_ex(0, &frame->uc.uc_flags); | ||
543 | put_user_ex(0, &frame->uc.uc_link); | 558 | put_user_ex(0, &frame->uc.uc_link); |
544 | compat_save_altstack_ex(&frame->uc.uc_stack, regs->sp); | 559 | compat_save_altstack_ex(&frame->uc.uc_stack, regs->sp); |
545 | put_user_ex(0, &frame->uc.uc__pad0); | 560 | put_user_ex(0, &frame->uc.uc__pad0); |
@@ -601,7 +616,11 @@ asmlinkage unsigned long sys_sigreturn(void) | |||
601 | 616 | ||
602 | set_current_blocked(&set); | 617 | set_current_blocked(&set); |
603 | 618 | ||
604 | if (restore_sigcontext(regs, &frame->sc)) | 619 | /* |
620 | * x86_32 has no uc_flags bits relevant to restore_sigcontext. | ||
621 | * Save a few cycles by skipping the __get_user. | ||
622 | */ | ||
623 | if (restore_sigcontext(regs, &frame->sc, 0)) | ||
605 | goto badframe; | 624 | goto badframe; |
606 | return regs->ax; | 625 | return regs->ax; |
607 | 626 | ||
@@ -617,16 +636,19 @@ asmlinkage long sys_rt_sigreturn(void) | |||
617 | struct pt_regs *regs = current_pt_regs(); | 636 | struct pt_regs *regs = current_pt_regs(); |
618 | struct rt_sigframe __user *frame; | 637 | struct rt_sigframe __user *frame; |
619 | sigset_t set; | 638 | sigset_t set; |
639 | unsigned long uc_flags; | ||
620 | 640 | ||
621 | frame = (struct rt_sigframe __user *)(regs->sp - sizeof(long)); | 641 | frame = (struct rt_sigframe __user *)(regs->sp - sizeof(long)); |
622 | if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) | 642 | if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) |
623 | goto badframe; | 643 | goto badframe; |
624 | if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) | 644 | if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) |
625 | goto badframe; | 645 | goto badframe; |
646 | if (__get_user(uc_flags, &frame->uc.uc_flags)) | ||
647 | goto badframe; | ||
626 | 648 | ||
627 | set_current_blocked(&set); | 649 | set_current_blocked(&set); |
628 | 650 | ||
629 | if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) | 651 | if (restore_sigcontext(regs, &frame->uc.uc_mcontext, uc_flags)) |
630 | goto badframe; | 652 | goto badframe; |
631 | 653 | ||
632 | if (restore_altstack(&frame->uc.uc_stack)) | 654 | if (restore_altstack(&frame->uc.uc_stack)) |
@@ -813,6 +835,7 @@ asmlinkage long sys32_x32_rt_sigreturn(void) | |||
813 | struct pt_regs *regs = current_pt_regs(); | 835 | struct pt_regs *regs = current_pt_regs(); |
814 | struct rt_sigframe_x32 __user *frame; | 836 | struct rt_sigframe_x32 __user *frame; |
815 | sigset_t set; | 837 | sigset_t set; |
838 | unsigned long uc_flags; | ||
816 | 839 | ||
817 | frame = (struct rt_sigframe_x32 __user *)(regs->sp - 8); | 840 | frame = (struct rt_sigframe_x32 __user *)(regs->sp - 8); |
818 | 841 | ||
@@ -820,10 +843,12 @@ asmlinkage long sys32_x32_rt_sigreturn(void) | |||
820 | goto badframe; | 843 | goto badframe; |
821 | if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) | 844 | if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) |
822 | goto badframe; | 845 | goto badframe; |
846 | if (__get_user(uc_flags, &frame->uc.uc_flags)) | ||
847 | goto badframe; | ||
823 | 848 | ||
824 | set_current_blocked(&set); | 849 | set_current_blocked(&set); |
825 | 850 | ||
826 | if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) | 851 | if (restore_sigcontext(regs, &frame->uc.uc_mcontext, uc_flags)) |
827 | goto badframe; | 852 | goto badframe; |
828 | 853 | ||
829 | if (compat_restore_altstack(&frame->uc.uc_stack)) | 854 | if (compat_restore_altstack(&frame->uc.uc_stack)) |