Skip to content

Commit a217a65

Browse files
Lai JiangshanKAGA-KOKO
authored andcommitted
KVM/VMX: Invoke NMI non-IST entry instead of IST entry
In VMX, the host NMI handler needs to be invoked after NMI VM-Exit. Before commit 1a5488e ("KVM: VMX: Invoke NMI handler via indirect call instead of INTn"), this was done by INTn ("int $2"). But INTn microcode is relatively expensive, so the commit reworked NMI VM-Exit handling to invoke the kernel handler by function call. But this missed a detail. The NMI entry point for direct invocation is fetched from the IDT table and called on the kernel stack. But on 64-bit the NMI entry installed in the IDT expects to be invoked on the IST stack. It relies on the "NMI executing" variable on the IST stack to work correctly, which is at a fixed position in the IST stack. When the entry point is unexpectedly called on the kernel stack, the RSP-addressed "NMI executing" variable is obviously also on the kernel stack and is "uninitialized" and can cause the NMI entry code to run in the wrong way. Provide a non-ist entry point for VMX which shares the C-function with the regular NMI entry and invoke the new asm entry point instead. On 32-bit this just maps to the regular NMI entry point as 32-bit has no ISTs and is not affected. [ tglx: Made it independent for backporting, massaged changelog ] Fixes: 1a5488e ("KVM: VMX: Invoke NMI handler via indirect call instead of INTn") Signed-off-by: Lai Jiangshan <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Tested-by: Lai Jiangshan <[email protected]> Cc: [email protected] Link: https://lore.kernel.org/r/[email protected]
1 parent fc48a6d commit a217a65

File tree

3 files changed

+34
-7
lines changed

3 files changed

+34
-7
lines changed

arch/x86/include/asm/idtentry.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,21 @@ DECLARE_IDTENTRY_RAW(X86_TRAP_MC, xenpv_exc_machine_check);
588588
#endif
589589

590590
/* NMI */
591+
592+
#if defined(CONFIG_X86_64) && IS_ENABLED(CONFIG_KVM_INTEL)
593+
/*
594+
* Special NOIST entry point for VMX which invokes this on the kernel
595+
* stack. asm_exc_nmi() requires an IST to work correctly vs. the NMI
596+
* 'executing' marker.
597+
*
598+
* On 32bit this just uses the regular NMI entry point because 32-bit does
599+
* not have ISTs.
600+
*/
601+
DECLARE_IDTENTRY(X86_TRAP_NMI, exc_nmi_noist);
602+
#else
603+
#define asm_exc_nmi_noist asm_exc_nmi
604+
#endif
605+
591606
DECLARE_IDTENTRY_NMI(X86_TRAP_NMI, exc_nmi);
592607
#ifdef CONFIG_XEN_PV
593608
DECLARE_IDTENTRY_RAW(X86_TRAP_NMI, xenpv_exc_nmi);

arch/x86/kernel/nmi.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,16 @@ DEFINE_IDTENTRY_RAW(exc_nmi)
524524
mds_user_clear_cpu_buffers();
525525
}
526526

527+
#if defined(CONFIG_X86_64) && IS_ENABLED(CONFIG_KVM_INTEL)
528+
DEFINE_IDTENTRY_RAW(exc_nmi_noist)
529+
{
530+
exc_nmi(regs);
531+
}
532+
#endif
533+
#if IS_MODULE(CONFIG_KVM_INTEL)
534+
EXPORT_SYMBOL_GPL(asm_exc_nmi_noist);
535+
#endif
536+
527537
void stop_nmi(void)
528538
{
529539
ignore_nmis++;

arch/x86/kvm/vmx/vmx.c

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <asm/debugreg.h>
3737
#include <asm/desc.h>
3838
#include <asm/fpu/internal.h>
39+
#include <asm/idtentry.h>
3940
#include <asm/io.h>
4041
#include <asm/irq_remapping.h>
4142
#include <asm/kexec.h>
@@ -6415,18 +6416,17 @@ static void vmx_apicv_post_state_restore(struct kvm_vcpu *vcpu)
64156416

64166417
void vmx_do_interrupt_nmi_irqoff(unsigned long entry);
64176418

6418-
static void handle_interrupt_nmi_irqoff(struct kvm_vcpu *vcpu, u32 intr_info)
6419+
static void handle_interrupt_nmi_irqoff(struct kvm_vcpu *vcpu,
6420+
unsigned long entry)
64196421
{
6420-
unsigned int vector = intr_info & INTR_INFO_VECTOR_MASK;
6421-
gate_desc *desc = (gate_desc *)host_idt_base + vector;
6422-
64236422
kvm_before_interrupt(vcpu);
6424-
vmx_do_interrupt_nmi_irqoff(gate_offset(desc));
6423+
vmx_do_interrupt_nmi_irqoff(entry);
64256424
kvm_after_interrupt(vcpu);
64266425
}
64276426

64286427
static void handle_exception_nmi_irqoff(struct vcpu_vmx *vmx)
64296428
{
6429+
const unsigned long nmi_entry = (unsigned long)asm_exc_nmi_noist;
64306430
u32 intr_info = vmx_get_intr_info(&vmx->vcpu);
64316431

64326432
/* if exit due to PF check for async PF */
@@ -6437,18 +6437,20 @@ static void handle_exception_nmi_irqoff(struct vcpu_vmx *vmx)
64376437
kvm_machine_check();
64386438
/* We need to handle NMIs before interrupts are enabled */
64396439
else if (is_nmi(intr_info))
6440-
handle_interrupt_nmi_irqoff(&vmx->vcpu, intr_info);
6440+
handle_interrupt_nmi_irqoff(&vmx->vcpu, nmi_entry);
64416441
}
64426442

64436443
static void handle_external_interrupt_irqoff(struct kvm_vcpu *vcpu)
64446444
{
64456445
u32 intr_info = vmx_get_intr_info(vcpu);
6446+
unsigned int vector = intr_info & INTR_INFO_VECTOR_MASK;
6447+
gate_desc *desc = (gate_desc *)host_idt_base + vector;
64466448

64476449
if (WARN_ONCE(!is_external_intr(intr_info),
64486450
"KVM: unexpected VM-Exit interrupt info: 0x%x", intr_info))
64496451
return;
64506452

6451-
handle_interrupt_nmi_irqoff(vcpu, intr_info);
6453+
handle_interrupt_nmi_irqoff(vcpu, gate_offset(desc));
64526454
}
64536455

64546456
static void vmx_handle_exit_irqoff(struct kvm_vcpu *vcpu)

0 commit comments

Comments
 (0)