Skip to contentSkip to conversation

FSGSBASE fix, test, and a semi-related cleanup

PATCH In review Andy Lutomirski 2 participants 8 messages Jul 2, 2019

Subsystems (4)
lore
Share & tools
lore

Andy Lutomirski

author maintainer
#0 lore raw

cover FSGSBASE fix, test, and a semi-related cleanup

In -tip, if FSGSBASE and PTI are on, the kernel crashes if SYSENTER happens with TF set. It also crashes under if a non-NMI paranoid entry happens for any other reason from kernel mode with user GSBASE and user CR3, e.g. due to MOV SS shenanigans.

This series fixes the bug. It also adds another test to make sure we exercise SYSENTER with TF set regardless of what vendor's CPU we're on, although the test isn't needed to detect the bug: the single_step_syscall_32 and mov_ss_trap_* tests also trigger it. And it compiles ignore_sysret out on IA32_EMULATION kernels -- I wasted a couple minutes while debugging this wondering whether I was accidentally triggering ignore_sysret.

Andy Lutomirski (3):
selftests/x86: Test SYSCALL and SYSENTER manually with TF set x86/entry/64: Don't compile ignore_sysret if 32-bit emulation is
enabled
x86/entry/64: Fix and clean up paranoid_exit

 arch/x86/entry/entry_64.S                     |  39 +++---
 tools/testing/selftests/x86/Makefile          |   5 +-
 .../testing/selftests/x86/syscall_arg_fault.c | 112 +++++++++++++++++-
 3 files changed, 133 insertions(+), 23 deletions(-)
Signature · 1 line
2.21.0
author maintainer
#5 lore raw

PATCH 3/3 x86/entry/64: Fix and clean up paranoid_exit

paranoid_exit needs to restore CR3 before GSBASE. Doing it in the opposite order crashes if the exception came from a context with user GSBASE and user CR3 -- RESTORE_CR3 cannot resture user CR3 if run with user GSBASE. This results in infinitely recursing exceptions if user code does SYSENTER with TF set if both FSGSBASE and PTI are enabled.

The old code worked if user code just set TF without SYSENTER because #DB from user mode is special cased in idtentry and paranoid_exit doesn't run.

Fix it by cleaning up the spaghetti code. All that paranoid_exit needs to do is to disable IRQs, handle IRQ tracing, then restore CR3, and restore GSBASE. Simply do those actions in that order.

Fixes 708078f65721 ("x86/entry/64: Handle FSGSBASE enabled paranoid entry/exit")
Cc Thomas Gleixner <[email protected]>
Cc H. Peter Anvin <[email protected]>
Cc Andi Kleen <[email protected]>
Cc Ravi Shankar <[email protected]>
Cc "Bae, Chang Seok" <[email protected]>
---
 arch/x86/entry/entry_64.S | 33 +++++++++++++++++----------------
 1 file changed, 17 insertions(+), 16 deletions(-)
arch/x86/entry/entry_64.S +17 -16
index 54b1b0468b2b..670306f588bf 100644
@@ -1256,31 +1256,32 @@ END(paranoid_entry)
 ENTRY(paranoid_exit)
 	UNWIND_HINT_REGS
 	DISABLE_INTERRUPTS(CLBR_ANY)
-	TRACE_IRQS_OFF_DEBUG
 
-	/* Handle GS depending on FSGSBASE availability */
-	ALTERNATIVE "jmp .Lparanoid_exit_checkgs", "nop",X86_FEATURE_FSGSBASE
+	/*
+	 * The order of operations is important.  IRQ tracing requires
+	 * kernel GSBASE and CR3.  RESTORE_CR3 requires kernel GS base.
+	 *
+	 * NB to anyone to tries to optimize this code: this code does
+	 * not execute at all for exceptions coming from user mode.  Those
+	 * exceptions go through error_exit instead.
+	 */
+	TRACE_IRQS_IRETQ_DEBUG
+	RESTORE_CR3	scratch_reg=%rax save_reg=%r14
+
+	/* Handle the three GSBASE cases. */
+	ALTERNATIVE "jmp .Lparanoid_exit_checkgs", "", X86_FEATURE_FSGSBASE
 
 	/* With FSGSBASE enabled, unconditionally restore GSBASE */
 	wrgsbase	%rbx
-	jmp	.Lparanoid_exit_no_swapgs;
+	jmp	restore_regs_and_return_to_kernel
 
 .Lparanoid_exit_checkgs:
 	/* On non-FSGSBASE systems, conditionally do SWAPGS */
 	testl	%ebx, %ebx
-	jnz	.Lparanoid_exit_no_swapgs
-	TRACE_IRQS_IRETQ
-	/* Always restore stashed CR3 value (see paranoid_entry) */
-	RESTORE_CR3	scratch_reg=%rbx save_reg=%r14
-	SWAPGS_UNSAFE_STACK
-	jmp	.Lparanoid_exit_restore
-
-.Lparanoid_exit_no_swapgs:
-	TRACE_IRQS_IRETQ_DEBUG
-	/* Always restore stashed CR3 value (see paranoid_entry) */
-	RESTORE_CR3	scratch_reg=%rbx save_reg=%r14
+	jnz	restore_regs_and_return_to_kernel
 
-.Lparanoid_exit_restore:
+	/* We are returning to a context with user GSBASE. */
+	SWAPGS_UNSAFE_STACK
 	jmp	restore_regs_and_return_to_kernel
 END(paranoid_exit)
 
Signature · 1 line
2.21.0
author maintainer
#3 lore raw

PATCH 2/3 x86/entry/64: Don't compile ignore_sysret if 32-bit emulation is enabled

In reply to Andy Lutomirski

It's only used if !CONFIG_IA32_EMULATION, so disable it in normal configs. This will save a few bytes of text and reduce confusion.

Cc "Bae, Chang Seok" <[email protected]>
---
 arch/x86/entry/entry_64.S | 6 ++++++
 1 file changed, 6 insertions(+)
arch/x86/entry/entry_64.S +6 -0
index 7f9f5119d6b1..54b1b0468b2b 100644
@@ -1743,11 +1743,17 @@ nmi_restore:
 	iretq
 END(nmi)
 
+#ifndef CONFIG_IA32_EMULATION
+/*
+ * This handles SYSCALL from 32-bit code.  There is no way to program
+ * MSRs to fully disable 32-bit SYSCALL.
+ */
 ENTRY(ignore_sysret)
 	UNWIND_HINT_EMPTY
 	mov	$-ENOSYS, %eax
 	sysret
 END(ignore_sysret)
+#endif
 
 ENTRY(rewind_stack_do_exit)
 	UNWIND_HINT_FUNC
Signature · 1 line
2.21.0
author maintainer
#1 lore raw

PATCH 1/3 selftests/x86: Test SYSCALL and SYSENTER manually with TF set

In reply to Andy Lutomirski

Make sure that we exercise both variants of the nasty TF-in-compat-syscall regardless of what vendor's CPU is running the tests.

Also change the intentional signal after SYSCALL to use ud2, which is a lot more comprehensible.

This crashes the kernel due to an FSGSBASE bug right now.

This test *also* detects a bug in KVM when run on an Intel host. KVM people, feel free to use it to help debug. There's a bunch of code in this test to warn instead of infinite looping when the bug gets triggered.

---
 tools/testing/selftests/x86/Makefile          |   5 +-
 .../testing/selftests/x86/syscall_arg_fault.c | 112 +++++++++++++++++-
 2 files changed, 110 insertions(+), 7 deletions(-)
tools/testing/selftests/x86/Makefile +3 -2
index 186520198de7..fa07d526fe39 100644
@@ -12,8 +12,9 @@ CAN_BUILD_WITH_NOPIE := $(shell ./check_cc.sh $(CC) trivial_program.c -no-pie)
 
 TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt test_mremap_vdso \
 			check_initial_reg_state sigreturn iopl mpx-mini-test ioperm \
-			protection_keys test_vdso test_vsyscall mov_ss_trap
-TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \
+			protection_keys test_vdso test_vsyscall mov_ss_trap \
+			syscall_arg_fault
+TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
 			test_FCMOV test_FCOMI test_FISTTP \
 			vdso_restorer
 TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip
tools/testing/selftests/x86/syscall_arg_fault.c +107 -5

147 lines not on this page · raw message

Signature · 1 line
2.21.0
author maintainer
#7 lore raw
In reply to Andy Lutomirski
Quoted 6 lines · Andy Lutomirski

In -tip, if FSGSBASE and PTI are on, the kernel crashes if SYSENTER happens with TF set. It also crashes under if a non-NMI paranoid entry happens for any other reason from kernel mode with user GSBASE and user CR3, e.g. due to MOV SS shenanigans.

Quoted 7 lines

This series fixes the bug. It also adds another test to make sure we exercise SYSENTER with TF set regardless of what vendor's CPU we're on, although the test isn't needed to detect the bug: the single_step_syscall_32 and mov_ss_trap_* tests also trigger it. And it compiles ignore_sysret out on IA32_EMULATION kernels -- I wasted a couple minutes while debugging this wondering whether I was accidentally triggering ignore_sysret.

I forgot to mention: even with this applied, the x86/cpu tree is not ready for prime time. The fsgsbase test case fails on released kernels and crashes on x86/cpu. I haven't gotten to the bottom of it yet. The test code looks a bit dubious, but that doesn't necessarily mean the kernel is okay.

#2 lore raw

[tip:x86/cpu] selftests/x86: Test SYSCALL and SYSENTER manually with TF set

In reply to Andy Lutomirski
selftests/x86: Test SYSCALL and SYSENTER manually with TF set
Commit-ID:  9402eaf4c11f0b892eda7b2bcb4654ab34ce34f9
Gitweb:     https://git.kernel.org/tip/9402eaf4c11f0b892eda7b2bcb4654ab34ce34f9
Author:     Andy Lutomirski <[email protected]>
AuthorDate: Mon, 1 Jul 2019 20:43:19 -0700
Committer:  Thomas Gleixner <[email protected]>
CommitDate: Tue, 2 Jul 2019 08:45:20 +0200

selftests/x86: Test SYSCALL and SYSENTER manually with TF set

Make sure that both variants of the nasty TF-in-compat-syscall are exercised regardless of what vendor's CPU is running the tests.

Also change the intentional signal after SYSCALL to use ud2, which is a lot more comprehensible.

This crashes the kernel due to an FSGSBASE bug right now.

This test *also* detects a bug in KVM when run on an Intel host. KVM people, feel free to use it to help debug. There's a bunch of code in this test to warn instead of going into an infinite looping when the bug gets triggered.

---
 tools/testing/selftests/x86/Makefile            |   5 +-
 tools/testing/selftests/x86/syscall_arg_fault.c | 112 ++++++++++++++++++++++--
 2 files changed, 110 insertions(+), 7 deletions(-)
tools/testing/selftests/x86/Makefile +3 -2
index 186520198de7..fa07d526fe39 100644
@@ -12,8 +12,9 @@ CAN_BUILD_WITH_NOPIE := $(shell ./check_cc.sh $(CC) trivial_program.c -no-pie)
 
 TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt test_mremap_vdso \
 			check_initial_reg_state sigreturn iopl mpx-mini-test ioperm \
-			protection_keys test_vdso test_vsyscall mov_ss_trap
-TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \
+			protection_keys test_vdso test_vsyscall mov_ss_trap \
+			syscall_arg_fault
+TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
 			test_FCMOV test_FCOMI test_FISTTP \
 			vdso_restorer
 TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip
tools/testing/selftests/x86/syscall_arg_fault.c +107 -5

147 lines not on this page · raw message

#4 lore raw

[tip:x86/cpu] x86/entry/64: Don't compile ignore_sysret if 32-bit emulation is enabled

In reply to Andy Lutomirski
x86/entry/64: Don't compile ignore_sysret if 32-bit emulation is enabled
Commit-ID:  dffb3f9db6b593f3ed6ab4c8d8f10e0aa6aa7a88
Gitweb:     https://git.kernel.org/tip/dffb3f9db6b593f3ed6ab4c8d8f10e0aa6aa7a88
Author:     Andy Lutomirski <[email protected]>
AuthorDate: Mon, 1 Jul 2019 20:43:20 -0700
Committer:  Thomas Gleixner <[email protected]>
CommitDate: Tue, 2 Jul 2019 08:45:20 +0200

x86/entry/64: Don't compile ignore_sysret if 32-bit emulation is enabled

It's only used if !CONFIG_IA32_EMULATION, so disable it in normal configs. This will save a few bytes of text and reduce confusion.

---
 arch/x86/entry/entry_64.S | 6 ++++++
 1 file changed, 6 insertions(+)
arch/x86/entry/entry_64.S +6 -0
index 7f9f5119d6b1..54b1b0468b2b 100644
@@ -1743,11 +1743,17 @@ nmi_restore:
 	iretq
 END(nmi)
 
+#ifndef CONFIG_IA32_EMULATION
+/*
+ * This handles SYSCALL from 32-bit code.  There is no way to program
+ * MSRs to fully disable 32-bit SYSCALL.
+ */
 ENTRY(ignore_sysret)
 	UNWIND_HINT_EMPTY
 	mov	$-ENOSYS, %eax
 	sysret
 END(ignore_sysret)
+#endif
 
 ENTRY(rewind_stack_do_exit)
 	UNWIND_HINT_FUNC
#6 lore raw

[tip:x86/cpu] x86/entry/64: Fix and clean up paranoid_exit

In reply to Andy Lutomirski
x86/entry/64: Fix and clean up paranoid_exit
Commit-ID:  539bca535decb11a0861b6205c6684b8e908589b
Gitweb:     https://git.kernel.org/tip/539bca535decb11a0861b6205c6684b8e908589b
Author:     Andy Lutomirski <[email protected]>
AuthorDate: Mon, 1 Jul 2019 20:43:21 -0700
Committer:  Thomas Gleixner <[email protected]>
CommitDate: Tue, 2 Jul 2019 08:45:20 +0200

x86/entry/64: Fix and clean up paranoid_exit

paranoid_exit needs to restore CR3 before GSBASE. Doing it in the opposite order crashes if the exception came from a context with user GSBASE and user CR3 -- RESTORE_CR3 cannot resture user CR3 if run with user GSBASE. This results in infinitely recursing exceptions if user code does SYSENTER with TF set if both FSGSBASE and PTI are enabled.

The old code worked if user code just set TF without SYSENTER because #DB from user mode is special cased in idtentry and paranoid_exit doesn't run.

Fix it by cleaning up the spaghetti code. All that paranoid_exit needs to do is to disable IRQs, handle IRQ tracing, then restore CR3, and restore GSBASE. Simply do those actions in that order.

Fixes 708078f65721 ("x86/entry/64: Handle FSGSBASE enabled paranoid entry/exit")
Cc Borislav Petkov <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc "H . Peter Anvin" <[email protected]>
Cc Andi Kleen <[email protected]>
Cc Ravi Shankar <[email protected]>
Cc H. Peter Anvin <[email protected]>
---
 arch/x86/entry/entry_64.S | 33 +++++++++++++++++----------------
 1 file changed, 17 insertions(+), 16 deletions(-)
arch/x86/entry/entry_64.S +17 -16
index 54b1b0468b2b..670306f588bf 100644
@@ -1256,31 +1256,32 @@ END(paranoid_entry)
 ENTRY(paranoid_exit)
 	UNWIND_HINT_REGS
 	DISABLE_INTERRUPTS(CLBR_ANY)
-	TRACE_IRQS_OFF_DEBUG
 
-	/* Handle GS depending on FSGSBASE availability */
-	ALTERNATIVE "jmp .Lparanoid_exit_checkgs", "nop",X86_FEATURE_FSGSBASE
+	/*
+	 * The order of operations is important.  IRQ tracing requires
+	 * kernel GSBASE and CR3.  RESTORE_CR3 requires kernel GS base.
+	 *
+	 * NB to anyone to tries to optimize this code: this code does
+	 * not execute at all for exceptions coming from user mode.  Those
+	 * exceptions go through error_exit instead.
+	 */
+	TRACE_IRQS_IRETQ_DEBUG
+	RESTORE_CR3	scratch_reg=%rax save_reg=%r14
+
+	/* Handle the three GSBASE cases. */
+	ALTERNATIVE "jmp .Lparanoid_exit_checkgs", "", X86_FEATURE_FSGSBASE
 
 	/* With FSGSBASE enabled, unconditionally restore GSBASE */
 	wrgsbase	%rbx
-	jmp	.Lparanoid_exit_no_swapgs;
+	jmp	restore_regs_and_return_to_kernel
 
 .Lparanoid_exit_checkgs:
 	/* On non-FSGSBASE systems, conditionally do SWAPGS */
 	testl	%ebx, %ebx
-	jnz	.Lparanoid_exit_no_swapgs
-	TRACE_IRQS_IRETQ
-	/* Always restore stashed CR3 value (see paranoid_entry) */
-	RESTORE_CR3	scratch_reg=%rbx save_reg=%r14
-	SWAPGS_UNSAFE_STACK
-	jmp	.Lparanoid_exit_restore
-
-.Lparanoid_exit_no_swapgs:
-	TRACE_IRQS_IRETQ_DEBUG
-	/* Always restore stashed CR3 value (see paranoid_entry) */
-	RESTORE_CR3	scratch_reg=%rbx save_reg=%r14
+	jnz	restore_regs_and_return_to_kernel
 
-.Lparanoid_exit_restore:
+	/* We are returning to a context with user GSBASE. */
+	SWAPGS_UNSAFE_STACK
 	jmp	restore_regs_and_return_to_kernel
 END(paranoid_exit)