* [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®