x86/kvm: RCU/context tracking and instrumentation protections
v2 cover x86/kvm: RCU/context tracking and instrumentation protections
Folks,
this is a rebased and adopted version of the original series which is available here:
https://lore.kernel.org/r/[email protected]
It deals with the RCU and context tracking state and the protection against instrumentation in sensitive places:
- Placing the guest_enter/exit() calls at the correct place
- Moving the sensitive VMENTER/EXIT code into the non-instrumentable code section.
- Fixup the tracing code to comply with the non-instrumentation rules
- Use native functions to access CR2 and the GS base MSR in the critical code pathes to prevent them from being instrumented.
Thanks,
tglx
PATCH v2 1/7 x86/kvm: Move context tracking where it belongs
From: Thomas Gleixner <[email protected]>
Context tracking for KVM happens way too early in the vcpu_run() code. Anything after guest_enter_irqoff() and before guest_exit_irqoff() cannot use RCU and should also be not instrumented.
The current way of doing this covers way too much code. Move it closer to the actual vmenter/exit code.
--- arch/x86/kvm/svm/svm.c | 16 ++++++++++++++++ arch/x86/kvm/vmx/vmx.c | 10 ++++++++++ arch/x86/kvm/x86.c | 2 -- 3 files changed, 26 insertions(+), 2 deletions(-)
arch/x86/kvm/svm/svm.c +16 -0
@@ -3399,6 +3399,14 @@ static __no_kcsan fastpath_t svm_vcpu_ru*/ x86_spec_ctrl_set_guest (svm->spec_ctrl, svm->virt_spec_ctrl); +/* +* Tell context tracking that this CPU is about to enter guest +* mode. This has to be after x86_spec_ctrl_set_guest() because +* that can take locks (lockdep needs RCU) and calls into world and +* some more. +*/ +guest_enter_irqoff (); +__svm_vcpu_run (svm->vmcb_pa, (unsigned long *)&svm->vcpu.arch.regs);#ifdef CONFIG_X86_64
@@ -3409,6 +3417,14 @@ static __no_kcsan fastpath_t svm_vcpu_ruloadsegment (gs, svm->host.gs);#endif #endif +/* +* Tell context tracking that this CPU is back. +* +* This needs to be done before the below as native_read_msr() +* contains a tracepoint and x86_spec_ctrl_restore_host() calls +* into world and some more. +*/ +guest_exit_irqoff ();/* * We do not use IBRS in the kernel. If this vCPU has used the
arch/x86/kvm/vmx/vmx.c +10 -0
@@ -6728,6 +6728,11 @@ static fastpath_t vmx_vcpu_run(struct kv*/ x86_spec_ctrl_set_guest (vmx->spec_ctrl,0 ); +/* +* Tell context tracking that this CPU is about to enter guest mode. +*/ +guest_enter_irqoff (); +/* L1D Flush includes CPU buffer clear to mitigate MDS */ if (static_branch_unlikely (&vmx_l1d_should_flush))vmx_l1d_flush (vcpu);
@@ -6743,6 +6748,11 @@ static fastpath_t vmx_vcpu_run(struct kv vcpu->arch.cr2 =read_cr2 ();/* +* Tell context tracking that this CPU is back. +*/ +guest_exit_irqoff (); + +/* * We do not use IBRS in the kernel. If this vCPU has used the * SPEC_CTRL MSR it may have left it on; save the value and * turn it off. This is much more efficient than blindly adding
arch/x86/kvm/x86.c +0 -2
@@ -8501,7 +8501,6 @@ static int vcpu_enter_guest(struct kvm_v }trace_kvm_entry (vcpu->vcpu_id); -guest_enter_irqoff ();fpregs_assert_state_consistent ();if (test_thread_flag (TIF_NEED_FPU_LOAD ))
@@ -8563,7 +8562,6 @@ static int vcpu_enter_guest(struct kvm_vlocal_irq_disable ();kvm_after_interrupt (vcpu); -guest_exit_irqoff ();if (lapic_in_kernel (vcpu)) {s64 delta = vcpu->arch.apic->lapic_timer.advance_expire_delta;if (delta !=S64_MIN ) {
PATCH v2 2/7 x86/kvm/vmx: Add hardirq tracing to guest enter/exit
In reply to Thomas GleixnerFrom: Thomas Gleixner <[email protected]>
Entering guest mode is more or less the same as returning to user space. From an instrumentation point of view both leave kernel mode and the transition to guest or user mode reenables interrupts on the host. In user mode an interrupt is served directly and in guest mode it causes a VM exit which then handles or reinjects the interrupt.
The transition from guest mode or user mode to kernel mode disables interrupts, which needs to be recorded in instrumentation to set the correct state again.
This is important for e.g. latency analysis because otherwise the execution time in guest or user mode would be wrongly accounted as interrupt disabled and could trigger false positives.
Add hardirq tracing to guest enter/exit functions in the same way as it is done in the user mode enter/exit code, respecting the RCU requirements.
--- arch/x86/kvm/vmx/vmx.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-)
arch/x86/kvm/vmx/vmx.c +25 -2
@@ -6729,9 +6729,21 @@ static fastpath_t vmx_vcpu_run(struct kvx86_spec_ctrl_set_guest (vmx->spec_ctrl,0 );/* -* Tell context tracking that this CPU is about to enter guest mode. +* VMENTER enables interrupts (host state), but the kernel state is +* interrupts disabled when this is invoked. Also tell RCU about +* it. This is the same logic as for exit_to_user_mode(). +* +* This ensures that e.g. latency analysis on the host observes +* guest mode as interrupt enabled. +* +* guest_enter_irqoff() informs context tracking about the +* transition to guest mode and if enabled adjusts RCU state +* accordingly. */ +trace_hardirqs_on_prepare (); +lockdep_hardirqs_on_prepare (CALLER_ADDR0 );guest_enter_irqoff (); +lockdep_hardirqs_on (CALLER_ADDR0 );/* L1D Flush includes CPU buffer clear to mitigate MDS */ if (static_branch_unlikely (&vmx_l1d_should_flush))
@@ -6748,9 +6760,20 @@ static fastpath_t vmx_vcpu_run(struct kv vcpu->arch.cr2 =read_cr2 ();/* -* Tell context tracking that this CPU is back. +* VMEXIT disables interrupts (host state), but tracing and lockdep +* have them in state 'on' as recorded before entering guest mode. +* Same as enter_from_user_mode(). +* +* guest_exit_irqoff() restores host context and reinstates RCU if +* enabled and required. +* +* This needs to be done before the below as native_read_msr() +* contains a tracepoint and x86_spec_ctrl_restore_host() calls +* into world and some more. */ +lockdep_hardirqs_off (CALLER_ADDR0 );guest_exit_irqoff (); +trace_hardirqs_off_finish ();/* * We do not use IBRS in the kernel. If this vCPU has used the
PATCH v2 3/7 x86/kvm/svm: Add hardirq tracing on guest enter/exit
In reply to Thomas GleixnerFrom: Thomas Gleixner <[email protected]>
Entering guest mode is more or less the same as returning to user space. From an instrumentation point of view both leave kernel mode and the transition to guest or user mode reenables interrupts on the host. In user mode an interrupt is served directly and in guest mode it causes a VM exit which then handles or reinjects the interrupt.
The transition from guest mode or user mode to kernel mode disables interrupts, which needs to be recorded in instrumentation to set the correct state again.
This is important for e.g. latency analysis because otherwise the execution time in guest or user mode would be wrongly accounted as interrupt disabled and could trigger false positives.
Add hardirq tracing to guest enter/exit functions in the same way as it is done in the user mode enter/exit code, respecting the RCU requirements.
--- arch/x86/kvm/svm/svm.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-)
arch/x86/kvm/svm/svm.c +22 -5
@@ -3400,12 +3400,21 @@ static __no_kcsan fastpath_t svm_vcpu_rux86_spec_ctrl_set_guest (svm->spec_ctrl, svm->virt_spec_ctrl);/* -* Tell context tracking that this CPU is about to enter guest -* mode. This has to be after x86_spec_ctrl_set_guest() because -* that can take locks (lockdep needs RCU) and calls into world and -* some more. +* VMENTER enables interrupts (host state), but the kernel state is +* interrupts disabled when this is invoked. Also tell RCU about +* it. This is the same logic as for exit_to_user_mode(). +* +* This ensures that e.g. latency analysis on the host observes +* guest mode as interrupt enabled. +* +* guest_enter_irqoff() informs context tracking about the +* transition to guest mode and if enabled adjusts RCU state +* accordingly. */ +trace_hardirqs_on_prepare (); +lockdep_hardirqs_on_prepare (CALLER_ADDR0 );guest_enter_irqoff (); +lockdep_hardirqs_on (CALLER_ADDR0 );__svm_vcpu_run (svm->vmcb_pa, (unsigned long *)&svm->vcpu.arch.regs);
@@ -3417,14 +3426,22 @@ static __no_kcsan fastpath_t svm_vcpu_ruloadsegment (gs, svm->host.gs);#endif #endif +/* -* Tell context tracking that this CPU is back. +* VMEXIT disables interrupts (host state), but tracing and lockdep +* have them in state 'on' as recorded before entering guest mode. +* Same as enter_from_user_mode(). +* +* guest_exit_irqoff() restores host context and reinstates RCU if +* enabled and required. * * This needs to be done before the below as native_read_msr() * contains a tracepoint and x86_spec_ctrl_restore_host() calls * into world and some more. */ +lockdep_hardirqs_off (CALLER_ADDR0 );guest_exit_irqoff (); +trace_hardirqs_off_finish ();/* * We do not use IBRS in the kernel. If this vCPU has used the
PATCH v2 4/7 x86/kvm/vmx: Move guest enter/exit into .noinstr.text
In reply to Thomas GleixnerFrom: Thomas Gleixner <[email protected]>
Move the functions which are inside the RCU off region into the non-instrumentable text section.
--- arch/x86/include/asm/hardirq.h | 4 - arch/x86/include/asm/kvm_host.h | 8 ++ arch/x86/kvm/vmx/ops.h | 4 + arch/x86/kvm/vmx/vmenter.S | 5 + arch/x86/kvm/vmx/vmx.c | 111 ++++++++++++++++++++++------------------ arch/x86/kvm/x86.c | 2 6 files changed, 81 insertions(+), 53 deletions(-)
arch/x86/include/asm/hardirq.h +2 -2
@@ -67,12 +67,12 @@ static inline void kvm_set_cpu_l1tf_flus__this_cpu_write (irq_stat.kvm_cpu_l1tf_flush_l1d,1 ); } -static inline void kvm_clear_cpu_l1tf_flush_l1d (void ) +static __always_inline void kvm_clear_cpu_l1tf_flush_l1d(void) {__this_cpu_write (irq_stat.kvm_cpu_l1tf_flush_l1d,0 ); } -static inline bool kvm_get_cpu_l1tf_flush_l1d (void ) +static __always_inline bool kvm_get_cpu_l1tf_flush_l1d(void) {return __this_cpu_read (irq_stat.kvm_cpu_l1tf_flush_l1d); }
arch/x86/include/asm/kvm_host.h +8 -0
@@ -1636,7 +1636,15 @@ asmlinkage void kvm_spurious_fault(void) insn"\n\t" \"jmp 668f \n\t" \"667: \n\t" \ +"1: \n\t" \ +".pushsection .discard.instr_begin \n\t" \ +".long 1b - . \n\t" \ +".popsection \n\t" \"call kvm_spurious_fault \n\t" \ +"1: \n\t" \ +".pushsection .discard.instr_end \n\t" \ +".long 1b - . \n\t" \ +".popsection \n\t" \"668: \n\t" \ _ASM_EXTABLE(666b ,667b )
arch/x86/kvm/vmx/ops.h +4 -0
@@ -146,7 +146,9 @@ do { \ : : op1 : "cc" : error, fault); \return ; \ error: \ +instrumentation_begin (); \ insn##_error(error_args); \ +instrumentation_end (); \return ; \ fault: \kvm_spurious_fault (); \
@@ -161,7 +163,9 @@ do { \ : : op1, op2 : "cc" : error, fault); \return ; \ error: \ +instrumentation_begin (); \ insn##_error(error_args); \ +instrumentation_end (); \return ; \ fault: \kvm_spurious_fault (); \
arch/x86/kvm/vmx/vmenter.S +4 -1
@@ -27,7 +27,7 @@ #define VCPU_R15 __VCPU_REGS_R15 * WORD_SIZE #endif - .text +.section .noinstr.text, "ax" /** * vmx_vmenter - VM-Enter the current loaded VMCS
@@ -234,6 +234,9 @@ SYM_FUNC_START(__vmx_vcpu_run) jmp 1b SYM_FUNC_END(__vmx_vcpu_run) + +.section .text, "ax" + /** * vmread_error_trampoline - Trampoline from inline asm to vmread_error() * @field: VMCS field encoding that failed
arch/x86/kvm/vmx/vmx.c +62 -49
141 lines not on this page · raw message
arch/x86/kvm/x86.c +1 -1
@@ -379,7 +379,7 @@ int kvm_set_apic_base(struct kvm_vcpu *v }EXPORT_SYMBOL_GPL (kvm_set_apic_base); -asmlinkage __visible void kvm_spurious_fault(void) +asmlinkage __visible noinstr void kvm_spurious_fault(void) {/* Fault while not rebooting. We want the trace. */ BUG_ON (!kvm_rebooting);
PATCH v2 5/7 x86/kvm/svm: Move guest enter/exit into .noinstr.text
In reply to Thomas GleixnerFrom: Thomas Gleixner <[email protected]>
Move the functions which are inside the RCU off region into the non-instrumentable text section.
--- arch/x86/kvm/svm/svm.c | 98 +++++++++++++++++++++++++-------------------- arch/x86/kvm/svm/vmenter.S | 2 2 files changed, 56 insertions(+), 44 deletions(-)
arch/x86/kvm/svm/svm.c +55 -43
@@ -3344,6 +3344,60 @@ static fastpath_t svm_exit_handlers_fastvoid __svm_vcpu_run (unsigned long vmcb_pa,unsigned long *regs); +static noinstr void svm_vcpu_enter_exit(struct kvm_vcpu *vcpu, +struct vcpu_svm *svm) +{ +/* +* VMENTER enables interrupts (host state), but the kernel state is +* interrupts disabled when this is invoked. Also tell RCU about +* it. This is the same logic as for exit_to_user_mode(). +* +* This ensures that e.g. latency analysis on the host observes +* guest mode as interrupt enabled. +* +* guest_enter_irqoff() informs context tracking about the +* transition to guest mode and if enabled adjusts RCU state +* accordingly. +*/ +instrumentation_begin (); +trace_hardirqs_on_prepare (); +lockdep_hardirqs_on_prepare (CALLER_ADDR0 ); +instrumentation_end (); + +guest_enter_irqoff (); +lockdep_hardirqs_on (CALLER_ADDR0 ); + +__svm_vcpu_run (svm->vmcb_pa, (unsigned long *)&svm->vcpu.arch.regs); + +#ifdef CONFIG_X86_64 +wrmsrl (MSR_GS_BASE , svm->host.gs_base); +#else +loadsegment (fs, svm->host.fs); +#ifndef CONFIG_X86_32_LAZY_GS +loadsegment (gs, svm->host.gs); +#endif +#endif + +/* +* VMEXIT disables interrupts (host state), but tracing and lockdep +* have them in state 'on' as recorded before entering guest mode. +* Same as enter_from_user_mode(). +* +* guest_exit_irqoff() restores host context and reinstates RCU if +* enabled and required. +* +* This needs to be done before the below as native_read_msr() +* contains a tracepoint and x86_spec_ctrl_restore_host() calls +* into world and some more. +*/ +lockdep_hardirqs_off (CALLER_ADDR0 ); +guest_exit_irqoff (); + +instrumentation_begin (); +trace_hardirqs_off_finish (); +instrumentation_end (); +} + static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu) {fastpath_t exit_fastpath;
@@ -3399,49 +3453,7 @@ static __no_kcsan fastpath_t svm_vcpu_ru*/ x86_spec_ctrl_set_guest (svm->spec_ctrl, svm->virt_spec_ctrl); -/* -* VMENTER enables interrupts (host state), but the kernel state is -* interrupts disabled when this is invoked. Also tell RCU about -* it. This is the same logic as for exit_to_user_mode(). -* -* This ensures that e.g. latency analysis on the host observes -* guest mode as interrupt enabled. -* -* guest_enter_irqoff() informs context tracking about the -* transition to guest mode and if enabled adjusts RCU state -* accordingly. -*/ -trace_hardirqs_on_prepare (); -lockdep_hardirqs_on_prepare (CALLER_ADDR0 ); -guest_enter_irqoff (); -lockdep_hardirqs_on (CALLER_ADDR0 ); - -__svm_vcpu_run (svm->vmcb_pa, (unsigned long *)&svm->vcpu.arch.regs); - -#ifdef CONFIG_X86_64 -wrmsrl (MSR_GS_BASE , svm->host.gs_base); -#else -loadsegment (fs, svm->host.fs); -#ifndef CONFIG_X86_32_LAZY_GS -loadsegment (gs, svm->host.gs); -#endif -#endif - -/* -* VMEXIT disables interrupts (host state), but tracing and lockdep -* have them in state 'on' as recorded before entering guest mode. -* Same as enter_from_user_mode(). -* -* guest_exit_irqoff() restores host context and reinstates RCU if -* enabled and required. -* -* This needs to be done before the below as native_read_msr() -* contains a tracepoint and x86_spec_ctrl_restore_host() calls -* into world and some more. -*/ -lockdep_hardirqs_off (CALLER_ADDR0 ); -guest_exit_irqoff (); -trace_hardirqs_off_finish (); +svm_vcpu_enter_exit (vcpu, svm);/* * We do not use IBRS in the kernel. If this vCPU has used the
arch/x86/kvm/svm/vmenter.S +1 -1
@@ -27,7 +27,7 @@ #define VCPU_R15 __VCPU_REGS_R15 * WORD_SIZE #endif - .text +.section .noinstr.text, "ax" /** * __svm_vcpu_run - Run a vCPU via a transition to SVM guest mode
PATCH v2 6/7 x86/kvm/svm: Use uninstrumented wrmsrl() to restore GS
In reply to Thomas GleixnerFrom: Thomas Gleixner <[email protected]>
On guest exit MSR_GS_BASE contains whatever the guest wrote to it and the first action after returning from the ASM code is to set it to the host kernel value. This uses wrmsrl() which is interesting at least.
wrmsrl() is either using native_write_msr() or the paravirt variant. The XEN_PV code is uninteresting as nested SVM in a XEN_PV guest does not work.
But native_write_msr() can be placed out of line by the compiler especially when paravirtualization is enabled in the kernel configuration. The function is marked notrace, but still can be probed if CONFIG_KPROBE_EVENTS_ON_NOTRACE is enabled.
That would be a fatal problem as kprobe events use per-CPU variables which are GS based and would be accessed with the guest GS. Depending on the GS value this would either explode in colorful ways or lead to completely undebugable data corruption.
Aside of that native_write_msr() contains a tracepoint which objtool complains about as it is invoked from the noinstr section.
As this cannot run inside a XEN_PV guest there is no point in using wrmsrl(). Use native_wrmsrl() instead which is just a plain native WRMSR without tracing or anything else attached.
--- arch/x86/kvm/svm/svm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/x86/kvm/svm/svm.c +1 -1
@@ -3370,7 +3370,7 @@ static noinstr void svm_vcpu_enter_exit(__svm_vcpu_run (svm->vmcb_pa, (unsigned long *)&svm->vcpu.arch.regs);#ifdef CONFIG_X86_64 -wrmsrl (MSR_GS_BASE , svm->host.gs_base); +native_wrmsrl (MSR_GS_BASE , svm->host.gs_base);#else loadsegment (fs, svm->host.fs);#ifndef CONFIG_X86_32_LAZY_GS
PATCH v2 7/7 x86/kvm/vmx: Use native read/write_cr2()
In reply to Thomas GleixnerFrom: Thomas Gleixner <[email protected]>
read/write_cr2() go throuh the paravirt XXL indirection, but nested VMX in a XEN_PV guest is not supported.
Use the native variants.
--- arch/x86/kvm/vmx/vmx.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
arch/x86/kvm/vmx/vmx.c +3 -3
@@ -6683,13 +6683,13 @@ static noinstr void vmx_vcpu_enter_exit(else if (static_branch_unlikely (&mds_user_clear))mds_clear_cpu_buffers (); -if (vcpu->arch.cr2 !=read_cr2 ()) -write_cr2 (vcpu->arch.cr2); +if (vcpu->arch.cr2 !=native_read_cr2 ()) +native_write_cr2 (vcpu->arch.cr2); vmx->fail =__vmx_vcpu_run (vmx, (unsigned long *)&vcpu->arch.regs, vmx->loaded_vmcs->launched); - vcpu->arch.cr2 =read_cr2 (); + vcpu->arch.cr2 =native_read_cr2 ();/* * VMEXIT disables interrupts (host state), but tracing and lockdep
Re: [patch V2 7/7] x86/kvm/vmx: Use native read/write_cr2()
Quoted 8 lines · Thomas Gleixner
From: Thomas Gleixner <[email protected]>
read/write_cr2() go throuh the paravirt XXL indirection, but nested VMX in a
XEN_PVguest is not supported.Use the native variants.
Signed-off-by Thomas Gleixner <[email protected]>
Juergen
Quoted 15 lines · Thomas Gleixner
Folks,
this is a rebased and adopted version of the original series which is available here:
https://lore.kernel.org/r/[email protected]It deals with the RCU and context tracking state and the protection against instrumentation in sensitive places:
- Placing the guest_enter/exit() calls at the correct place
- Moving the sensitive VMENTER/EXIT code into the non-instrumentable code section.
Quoted 9 lines
- Fixup the tracing code to comply with the non-instrumentation rules
- Use native functions to access CR2 and the GS base MSR in the critical code pathes to prevent them from being instrumented.
Thanks,
tglx
Queued, thanks.
Paolo