mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] KVM: x86: Re-pend NMI in vNMI hardware when injection is blocked
@ 2026-07-28  1:54 Vasiliy Kovalev
  2026-07-28 22:40 ` Vasiliy Kovalev
  0 siblings, 1 reply; 2+ messages in thread
From: Vasiliy Kovalev @ 2026-07-28  1:54 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini, kvm
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Maxim Levitsky, Santosh Shukla, linux-kernel, lvc-project,
	kovalev

With two NMIs pending and vNMI enabled, process_nmi() hands one NMI to
hardware via kvm_x86_ops.set_vnmi_pending() and leaves the other in
vcpu->arch.nmi_pending for injection via .inject_nmi(). If that injection
is blocked - here by an interrupt shadow set through KVM_SET_VCPU_EVENTS -
kvm_check_and_inject_events() calls .enable_nmi_window() to single-step
the shadow. On the next VMRUN hardware delivers the NMI it was holding and
sets NMI-blocking itself, so svm_get_nmi_mask() becomes true. KVM_REQ_NMI
is not pending on the re-entry, so process_nmi() does not re-run and the
leftover NMI is still queued; .enable_nmi_window() is called again with
NMIs now masked, and svm_enable_nmi_window() hits WARN_ON_ONCE() - which
asserts KVM never opens an NMI window while NMIs are masked under vNMI.
With panic_on_warn this is a host DoS.

WARNING: CPU: 3 PID: 1450 at arch/x86/kvm/svm/svm.c:4004 svm_enable_nmi_window+0x716/0x8f0 [kvm_amd]
CPU: 3 UID: 0 PID: 1450 Comm: syz.1.86 Not tainted 6.12.85+ #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996),
BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014
RIP: 0010:svm_enable_nmi_window+0x716/0x8f0 arch/x86/kvm/svm/svm.c:4004 [kvm_amd]
Call Trace:
 <TASK>
 kvm_check_and_inject_events+0x117a/0x1520 arch/x86/kvm/x86.c:10486 [kvm]
 vcpu_enter_guest.constprop.0+0x56f/0x40c0 arch/x86/kvm/x86.c:10946 [kvm]
 vcpu_run arch/x86/kvm/x86.c:11344 [inline] [kvm]
 kvm_arch_vcpu_ioctl_run+0x5dc/0x2560 arch/x86/kvm/x86.c:11671 [kvm]
 kvm_vcpu_ioctl+0x459/0x1510 virt/kvm/kvm_main.c:4492 [kvm]
 __x64_sys_ioctl+0x190/0x220 fs/ioctl.c:893
 do_syscall_64+0xf5/0x220 arch/x86/entry/common.c:78
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
 </TASK>

Before falling back to a window, retry .set_vnmi_pending() as process_nmi()
does. Hardware has already delivered the NMI it was holding, so the retry
succeeds, nmi_pending drops to zero, and no window is requested; hardware
delivers the re-pended NMI once the guest IRETs. On the first pass hardware
still holds the earlier NMI, so the retry fails and single-stepping is
unchanged. Without vNMI .set_vnmi_pending is NULL and the branch is a nop.

Found by Linux Verification Center (linuxtesting.org) with Syzkaller.

Fixes: fa4c027a7956 ("KVM: x86: Add support for SVM's Virtual NMI")
Cc: stable@vger.kernel.org
Signed-off-by: Vasiliy Kovalev <kovalev@altlinux.org>
---
The splat above is from syzkaller fuzzing on 6.12; the bug also reproduces
on kvm-x86/next with the syzkaller reproducer in a KVM guest on an
AMD EPYC 9754 (Zen 4c) with vNMI enabled; the WARN no longer fires with
this patch applied.

 arch/x86/kvm/x86.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 960ff7fcd8db..d35f5fd3cb16 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -7724,6 +7724,9 @@ static int kvm_check_and_inject_events(struct kvm_vcpu *vcpu,
 			can_inject = false;
 			WARN_ON(kvm_x86_call(nmi_allowed)(vcpu, true) < 0);
 		}
+		if (vcpu->arch.nmi_pending &&
+		    (kvm_x86_call(set_vnmi_pending)(vcpu)))
+			vcpu->arch.nmi_pending--;
 		if (vcpu->arch.nmi_pending)
 			kvm_x86_call(enable_nmi_window)(vcpu);
 	}
-- 
2.50.1


^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [PATCH] KVM: x86: Re-pend NMI in vNMI hardware when injection is blocked
  2026-07-28  1:54 [PATCH] KVM: x86: Re-pend NMI in vNMI hardware when injection is blocked Vasiliy Kovalev
@ 2026-07-28 22:40 ` Vasiliy Kovalev
  0 siblings, 0 replies; 2+ messages in thread
From: Vasiliy Kovalev @ 2026-07-28 22:40 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini, kvm
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Maxim Levitsky, Santosh Shukla, linux-kernel, lvc-project

Sean,

On 7/28/26 04:54, Vasiliy Kovalev wrote:
>   [...]
> 
> Before falling back to a window, retry .set_vnmi_pending() as process_nmi()
> does. Hardware has already delivered the NMI it was holding, so the retry
> succeeds, nmi_pending drops to zero, and no window is requested; hardware
> delivers the re-pended NMI once the guest IRETs. On the first pass hardware
> still holds the earlier NMI, so the retry fails and single-stepping is
> unchanged. Without vNMI .set_vnmi_pending is NULL and the branch is a nop.
> 
> [...]
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -7724,6 +7724,9 @@ static int kvm_check_and_inject_events(struct kvm_vcpu *vcpu,
>   			can_inject = false;
>   			WARN_ON(kvm_x86_call(nmi_allowed)(vcpu, true) < 0);
>   		}
> +		if (vcpu->arch.nmi_pending &&
> +		    (kvm_x86_call(set_vnmi_pending)(vcpu)))
> +			vcpu->arch.nmi_pending--;
>   		if (vcpu->arch.nmi_pending)
>   			kvm_x86_call(enable_nmi_window)(vcpu);
>   	}


Fuzzing on 6.12 (AMD EPYC 9754, vNMI) still hits this WARN with this patch
applied, no stable reproducer. I dumped the vNMI state on entry to the NMI
block in kvm_check_and_inject_events(), plus a line in the re-pend branch
the patch adds:

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 35ba3551f..f7615674f 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -34,6 +34,8 @@
  #include "xen.h"
  #include "smm.h"

+#include "svm/svm.h"
+
  #include <linux/clocksource.h>
  #include <linux/interrupt.h>
  #include <linux/kvm.h>
@@ -10485,6 +10487,16 @@ static int kvm_check_and_inject_events(struct 
kvm_vcpu *vcpu,
  #endif

         if (vcpu->arch.nmi_pending) {
+               {
+                       struct vcpu_svm *svm = to_svm(vcpu);
+                       pr_warn("NMIdbg: entry pending=%u int_ctl=%#x 
VB=%d VP=%d shadow=%d allowed=%d\n",
+                               vcpu->arch.nmi_pending,
+                               svm->vmcb->control.int_ctl,
+                               !!(svm->vmcb->control.int_ctl & 
V_NMI_BLOCKING_MASK),
+                               !!(svm->vmcb->control.int_ctl & 
V_NMI_PENDING_MASK),
+                               !!(svm->vmcb->control.int_state & 
SVM_INTERRUPT_SHADOW_MASK),
+                               kvm_x86_call(nmi_allowed)(vcpu, false));
+               }
                 r = can_inject ? kvm_x86_call(nmi_allowed)(vcpu, true) :
                                  -EBUSY;
                 if (r < 0)
@@ -10498,7 +10510,11 @@ static int kvm_check_and_inject_events(struct 
kvm_vcpu *vcpu,
                 }
                 if (vcpu->arch.nmi_pending &&
                     (kvm_x86_call(set_vnmi_pending)(vcpu)))
+               {
                         vcpu->arch.nmi_pending--;
+                       pr_warn("NMIdbg: re-pended, pending now %u\n",
+                                vcpu->arch.nmi_pending);
+               }
                 if (vcpu->arch.nmi_pending)
                         kvm_x86_call(enable_nmi_window)(vcpu);
         }

The WARN fires with V_NMI_BLOCKING and V_NMI_PENDING both set and an NMI
still queued - and no "re-pended" line precedes it, i.e. set_vnmi_pending()
returned false because the slot is already occupied:

	NMIdbg: entry pending=1 int_ctl=0x7000a00 VB=0 VP=1 shadow=1 allowed=0 
  (repeated)
	NMIdbg: entry pending=1 int_ctl=0x7001a00 VB=1 VP=1 shadow=0 allowed=0
	WARNING: CPU: 3 at arch/x86/kvm/svm/svm.c:4027 
svm_enable_nmi_window+0x703/0x8e0
	 kvm_check_and_inject_events+0x10ed/0x16a0
	 vcpu_run+0x1922/0x4ed0
	 kvm_vcpu_ioctl+0x469/0x1590

So V_NMI_PENDING is already set: there's nowhere to re-pend, nmi_pending
can't be drained, and the window is still requested. The patch fixes the
interrupt-shadow path (original reproducer no longer triggers) but not this
one. I couldn't minimize the exact sequence, but it looks like
KVM_SET_VCPU_EVENTS setting nmi.masked while a vNMI is pending
(svm_set_nmi_mask() sets V_NMI_BLOCKING without touching V_NMI_PENDING).

In this state svm_enable_nmi_window() just takes the early return and
hardware delivers the pending vNMI on IRET, so nothing malfunctions -
b4bd55646747 makes the same "still function correctly" note for the
STI-shadow/GIF=0 cases. Drop the WARN_ON_ONCE(), or is reaching this state
(V_NMI_BLOCKING + V_NMI_PENDING set, nmi_pending != 0) itself a bug to fix
elsewhere?

-- 
Thanks,
Vasiliy

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-07-28 22:40 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-28  1:54 [PATCH] KVM: x86: Re-pend NMI in vNMI hardware when injection is blocked Vasiliy Kovalev
2026-07-28 22:40 ` Vasiliy Kovalev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®