* [PATCH v2 6.1.y 1/3] KVM: nVMX: Add a helper to get highest pending from Posted Interrupt vector
2026-06-19 20:31 [PATCH v2 6.1.y 0/3] KVM: nVMX: backport virtual-APIC host NULL-deref fix Nicholas Dudar
@ 2026-06-19 20:31 ` Nicholas Dudar
2026-06-19 20:46 ` sashiko-bot
2026-06-19 20:31 ` [PATCH v2 6.1.y 2/3] KVM: nVMX: Check for pending posted interrupts when looking for nested events Nicholas Dudar
` (2 subsequent siblings)
3 siblings, 1 reply; 6+ messages in thread
From: Nicholas Dudar @ 2026-06-19 20:31 UTC (permalink / raw)
To: stable
Cc: seanjc, pbonzini, gregkh, kvm, linux-kernel, 0wn, mlevitsk,
jmattson, Nicholas Dudar
From: Sean Christopherson <seanjc@google.com>
commit d83c36d822be44db4bad0c43bea99c8908f54117 upstream.
Add a helper to retrieve the highest pending vector given a Posted
Interrupt descriptor. While the actual operation is straightforward, it's
surprisingly easy to mess up, e.g. if one tries to reuse lapic.c's
find_highest_vector(), which doesn't work with PID.PIR due to the APIC's
IRR and ISR component registers being physically discontiguous (they're
4-byte registers aligned at 16-byte intervals).
To make PIR handling more consistent with respect to IRR and ISR handling,
return -1 to indicate "no interrupt pending".
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20240607172609.3205077-2-seanjc@google.com
Signed-off-by: Sean Christopherson <seanjc@google.com>
[ Nicholas Dudar: backport to 6.1.y. 6.1.y defines struct pi_desc in
posted_intr.h and predates the move to <asm/posted_intr.h>, so the helper
and the <linux/find.h> include go in posted_intr.h. ]
Signed-off-by: Nicholas Dudar <main.kalliope@gmail.com>
---
arch/x86/kvm/vmx/nested.c | 5 +++--
arch/x86/kvm/vmx/posted_intr.h | 10 ++++++++++
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index bdc462944..7d8e18dbe 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -12,6 +12,7 @@
#include "mmu.h"
#include "nested.h"
#include "pmu.h"
+#include "posted_intr.h"
#include "sgx.h"
#include "trace.h"
#include "vmx.h"
@@ -3818,8 +3819,8 @@ static int vmx_complete_nested_posted_interrupt(struct kvm_vcpu *vcpu)
if (!pi_test_and_clear_on(vmx->nested.pi_desc))
return 0;
- max_irr = find_last_bit((unsigned long *)vmx->nested.pi_desc->pir, 256);
- if (max_irr != 256) {
+ max_irr = pi_find_highest_vector(vmx->nested.pi_desc);
+ if (max_irr > 0) {
vapic_page = vmx->nested.virtual_apic_map.hva;
if (!vapic_page)
goto mmio_needed;
diff --git a/arch/x86/kvm/vmx/posted_intr.h b/arch/x86/kvm/vmx/posted_intr.h
index 269920765..88cea0dac 100644
--- a/arch/x86/kvm/vmx/posted_intr.h
+++ b/arch/x86/kvm/vmx/posted_intr.h
@@ -2,6 +2,8 @@
#ifndef __KVM_X86_VMX_POSTED_INTR_H
#define __KVM_X86_VMX_POSTED_INTR_H
+#include <linux/find.h>
+
#define POSTED_INTR_ON 0
#define POSTED_INTR_SN 1
@@ -103,4 +105,12 @@ int vmx_pi_update_irte(struct kvm *kvm, unsigned int host_irq,
uint32_t guest_irq, bool set);
void vmx_pi_start_assignment(struct kvm *kvm);
+static inline int pi_find_highest_vector(struct pi_desc *pi_desc)
+{
+ int vec;
+
+ vec = find_last_bit((unsigned long *)pi_desc->pir, 256);
+ return vec < 256 ? vec : -1;
+}
+
#endif /* __KVM_X86_VMX_POSTED_INTR_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH v2 6.1.y 1/3] KVM: nVMX: Add a helper to get highest pending from Posted Interrupt vector
2026-06-19 20:31 ` [PATCH v2 6.1.y 1/3] KVM: nVMX: Add a helper to get highest pending from Posted Interrupt vector Nicholas Dudar
@ 2026-06-19 20:46 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-06-19 20:46 UTC (permalink / raw)
To: Nicholas Dudar; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The condition `if (max_irr > 0)` incorrectly ignores vector 0, preventing it from being synced to the guest's virtual APIC and leaving the PIR state corrupted.
--
commit 5c23c313090006d2eae5e26f892baf83bf9030f1
Author: Sean Christopherson <seanjc@google.com>
KVM: nVMX: Add a helper to get highest pending from Posted Interrupt vector
This commit adds a helper to retrieve the highest pending vector given a
Posted Interrupt descriptor. The helper returns -1 to indicate no interrupt
is pending, improving consistency with IRR and ISR handling.
Link: https://lore.kernel.org/r/20240607172609.3205077-2-seanjc@google.com
> diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
> index bdc462944cb08..7d8e18dbe8531 100644
> --- a/arch/x86/kvm/vmx/nested.c
> +++ b/arch/x86/kvm/vmx/nested.c
[ ... ]
> @@ -3818,8 +3819,8 @@ static int vmx_complete_nested_posted_interrupt(struct kvm_vcpu *vcpu)
> if (!pi_test_and_clear_on(vmx->nested.pi_desc))
> return 0;
>
> - max_irr = find_last_bit((unsigned long *)vmx->nested.pi_desc->pir, 256);
> - if (max_irr != 256) {
> + max_irr = pi_find_highest_vector(vmx->nested.pi_desc);
> + if (max_irr > 0) {
[Severity: Medium]
Does this condition inadvertently skip vector 0?
The commit message states that pi_find_highest_vector() returns -1 to indicate
no interrupt pending, so max_irr can be -1 or 0-255. By checking max_irr > 0,
the code skips processing if vector 0 is the highest pending interrupt.
While vector 0 is an invalid APIC interrupt that hardware typically ignores,
skipping it here means KVM does not call __kvm_apic_update_irr(). As a result,
vector 0 could remain stuck in the PIR array instead of being cleared, which
diverges from the lockless PIR/ON protocol.
Should this condition check for max_irr >= 0 or max_irr != -1 instead?
> vapic_page = vmx->nested.virtual_apic_map.hva;
> if (!vapic_page)
> goto mmio_needed;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260619203107.2752678-1-main.kalliope@gmail.com?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 6.1.y 2/3] KVM: nVMX: Check for pending posted interrupts when looking for nested events
2026-06-19 20:31 [PATCH v2 6.1.y 0/3] KVM: nVMX: backport virtual-APIC host NULL-deref fix Nicholas Dudar
2026-06-19 20:31 ` [PATCH v2 6.1.y 1/3] KVM: nVMX: Add a helper to get highest pending from Posted Interrupt vector Nicholas Dudar
@ 2026-06-19 20:31 ` Nicholas Dudar
2026-06-19 20:31 ` [PATCH v2 6.1.y 3/3] KVM: nVMX: Fold requested virtual interrupt check into has_nested_events() Nicholas Dudar
2026-06-21 13:47 ` [PATCH v2 6.1.y 0/3] KVM: nVMX: backport virtual-APIC host NULL-deref fix Sasha Levin
3 siblings, 0 replies; 6+ messages in thread
From: Nicholas Dudar @ 2026-06-19 20:31 UTC (permalink / raw)
To: stable
Cc: seanjc, pbonzini, gregkh, kvm, linux-kernel, 0wn, mlevitsk,
jmattson, Nicholas Dudar
From: Sean Christopherson <seanjc@google.com>
commit 27c4fa42b11af780d49ce704f7fa67b3c2544df4 upstream.
Check for pending (and notified!) posted interrupts when checking if L2
has a pending wake event, as fully posted/notified virtual interrupt is a
valid wake event for HLT.
Note that KVM must check vmx->nested.pi_pending to avoid prematurely
waking L2, e.g. even if KVM sees a non-zero PID.PIR and PID.0N=1, the
virtual interrupt won't actually be recognized until a notification IRQ is
received by the vCPU or the vCPU does (nested) VM-Enter.
Fixes: 26844fee6ade ("KVM: x86: never write to memory from kvm_vcpu_check_block()")
Cc: stable@vger.kernel.org
Cc: Maxim Levitsky <mlevitsk@redhat.com>
Reported-by: Jim Mattson <jmattson@google.com>
Closes: https://lore.kernel.org/all/20231207010302.2240506-1-jmattson@google.com
Link: https://lore.kernel.org/r/20240607172609.3205077-5-seanjc@google.com
Signed-off-by: Sean Christopherson <seanjc@google.com>
[ Nicholas Dudar: backport to 6.1.y. Prerequisite for the next patch, which
folds its check into the vmx_has_nested_events() body this patch builds.
Applies cleanly. The for_injection path still returns preemption_timer ||
mtf, as the previous 6.1.y body did. ]
Signed-off-by: Nicholas Dudar <main.kalliope@gmail.com>
---
arch/x86/kvm/vmx/nested.c | 36 ++++++++++++++++++++++++++++++++++--
1 file changed, 34 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 7d8e18dbe..ad07e83d2 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -3953,8 +3953,40 @@ static bool nested_vmx_preemption_timer_pending(struct kvm_vcpu *vcpu)
static bool vmx_has_nested_events(struct kvm_vcpu *vcpu, bool for_injection)
{
- return nested_vmx_preemption_timer_pending(vcpu) ||
- to_vmx(vcpu)->nested.mtf_pending;
+ struct vcpu_vmx *vmx = to_vmx(vcpu);
+ void *vapic = vmx->nested.virtual_apic_map.hva;
+ int max_irr, vppr;
+
+ if (nested_vmx_preemption_timer_pending(vcpu) ||
+ vmx->nested.mtf_pending)
+ return true;
+
+ /*
+ * Virtual Interrupt Delivery doesn't require manual injection. Either
+ * the interrupt is already in GUEST_RVI and will be recognized by CPU
+ * at VM-Entry, or there is a KVM_REQ_EVENT pending and KVM will move
+ * the interrupt from the PIR to RVI prior to entering the guest.
+ */
+ if (for_injection)
+ return false;
+
+ if (!nested_cpu_has_vid(get_vmcs12(vcpu)) ||
+ __vmx_interrupt_blocked(vcpu))
+ return false;
+
+ if (!vapic)
+ return false;
+
+ vppr = *((u32 *)(vapic + APIC_PROCPRI));
+
+ if (vmx->nested.pi_pending && vmx->nested.pi_desc &&
+ pi_test_on(vmx->nested.pi_desc)) {
+ max_irr = pi_find_highest_vector(vmx->nested.pi_desc);
+ if (max_irr > 0 && (max_irr & 0xf0) > (vppr & 0xf0))
+ return true;
+ }
+
+ return false;
}
/*
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 6.1.y 3/3] KVM: nVMX: Fold requested virtual interrupt check into has_nested_events()
2026-06-19 20:31 [PATCH v2 6.1.y 0/3] KVM: nVMX: backport virtual-APIC host NULL-deref fix Nicholas Dudar
2026-06-19 20:31 ` [PATCH v2 6.1.y 1/3] KVM: nVMX: Add a helper to get highest pending from Posted Interrupt vector Nicholas Dudar
2026-06-19 20:31 ` [PATCH v2 6.1.y 2/3] KVM: nVMX: Check for pending posted interrupts when looking for nested events Nicholas Dudar
@ 2026-06-19 20:31 ` Nicholas Dudar
2026-06-21 13:47 ` [PATCH v2 6.1.y 0/3] KVM: nVMX: backport virtual-APIC host NULL-deref fix Sasha Levin
3 siblings, 0 replies; 6+ messages in thread
From: Nicholas Dudar @ 2026-06-19 20:31 UTC (permalink / raw)
To: stable
Cc: seanjc, pbonzini, gregkh, kvm, linux-kernel, 0wn, mlevitsk,
jmattson, Nicholas Dudar
From: Sean Christopherson <seanjc@google.com>
commit 321ef62b0c5f6f57bb8500a2ca5986052675abbf upstream.
Check for a Requested Virtual Interrupt, i.e. a virtual interrupt that is
pending delivery, in vmx_has_nested_events() and drop the one-off
kvm_x86_ops.guest_apic_has_interrupt() hook.
In addition to dropping a superfluous hook, this fixes a bug where KVM
would incorrectly treat virtual interrupts _for L2_ as always enabled due
to kvm_arch_interrupt_allowed(), by way of vmx_interrupt_blocked(),
treating IRQs as enabled if L2 is active and vmcs12 is configured to exit
on IRQs, i.e. KVM would treat a virtual interrupt for L2 as a valid wake
event based on L1's IRQ blocking status.
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20240607172609.3205077-6-seanjc@google.com
Signed-off-by: Sean Christopherson <seanjc@google.com>
[ Nicholas Dudar: backport to 6.1.y. 6.1.y predates the vmx main.c /
x86_ops.h split, so drop .guest_apic_has_interrupt from vmx_x86_ops in
vmx.c rather than vt_x86_ops in main.c. The function is static in vmx.c, so
upstream's x86_ops.h prototype removal does not apply. 6.1.y keeps the
current hwapic_isr_update signature. ]
Signed-off-by: Nicholas Dudar <main.kalliope@gmail.com>
---
arch/x86/include/asm/kvm-x86-ops.h | 1 -
arch/x86/include/asm/kvm_host.h | 1 -
arch/x86/kvm/vmx/nested.c | 4 ++++
arch/x86/kvm/vmx/vmx.c | 21 ---------------------
arch/x86/kvm/x86.c | 10 +---------
5 files changed, 5 insertions(+), 32 deletions(-)
diff --git a/arch/x86/include/asm/kvm-x86-ops.h b/arch/x86/include/asm/kvm-x86-ops.h
index c068565fe..1cfe83263 100644
--- a/arch/x86/include/asm/kvm-x86-ops.h
+++ b/arch/x86/include/asm/kvm-x86-ops.h
@@ -81,7 +81,6 @@ KVM_X86_OP(check_apicv_inhibit_reasons)
KVM_X86_OP(refresh_apicv_exec_ctrl)
KVM_X86_OP_OPTIONAL(hwapic_irr_update)
KVM_X86_OP_OPTIONAL(hwapic_isr_update)
-KVM_X86_OP_OPTIONAL_RET0(guest_apic_has_interrupt)
KVM_X86_OP_OPTIONAL(load_eoi_exitmap)
KVM_X86_OP_OPTIONAL(set_virtual_apic_mode)
KVM_X86_OP_OPTIONAL(set_apic_access_page_addr)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index fe5c0f86a..31395c434 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1549,7 +1549,6 @@ struct kvm_x86_ops {
void (*refresh_apicv_exec_ctrl)(struct kvm_vcpu *vcpu);
void (*hwapic_irr_update)(struct kvm_vcpu *vcpu, int max_irr);
void (*hwapic_isr_update)(struct kvm_vcpu *vcpu, int isr);
- bool (*guest_apic_has_interrupt)(struct kvm_vcpu *vcpu);
void (*load_eoi_exitmap)(struct kvm_vcpu *vcpu, u64 *eoi_exit_bitmap);
void (*set_virtual_apic_mode)(struct kvm_vcpu *vcpu);
void (*set_apic_access_page_addr)(struct kvm_vcpu *vcpu);
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index ad07e83d2..f7a790a28 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -3979,6 +3979,10 @@ static bool vmx_has_nested_events(struct kvm_vcpu *vcpu, bool for_injection)
vppr = *((u32 *)(vapic + APIC_PROCPRI));
+ max_irr = vmx_get_rvi();
+ if ((max_irr & 0xf0) > (vppr & 0xf0))
+ return true;
+
if (vmx->nested.pi_pending && vmx->nested.pi_desc &&
pi_test_on(vmx->nested.pi_desc)) {
max_irr = pi_find_highest_vector(vmx->nested.pi_desc);
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index e5d162e97..2e6454e4c 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -4063,26 +4063,6 @@ void pt_update_intercept_for_msr(struct kvm_vcpu *vcpu)
}
}
-static bool vmx_guest_apic_has_interrupt(struct kvm_vcpu *vcpu)
-{
- struct vcpu_vmx *vmx = to_vmx(vcpu);
- void *vapic_page;
- u32 vppr;
- int rvi;
-
- if (WARN_ON_ONCE(!is_guest_mode(vcpu)) ||
- !nested_cpu_has_vid(get_vmcs12(vcpu)) ||
- WARN_ON_ONCE(!vmx->nested.virtual_apic_map.gfn))
- return false;
-
- rvi = vmx_get_rvi();
-
- vapic_page = vmx->nested.virtual_apic_map.hva;
- vppr = *((u32 *)(vapic_page + APIC_PROCPRI));
-
- return ((rvi & 0xf0) > (vppr & 0xf0));
-}
-
static void vmx_msr_filter_changed(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
@@ -8266,7 +8246,6 @@ static struct kvm_x86_ops vmx_x86_ops __initdata = {
.check_apicv_inhibit_reasons = vmx_check_apicv_inhibit_reasons,
.hwapic_irr_update = vmx_hwapic_irr_update,
.hwapic_isr_update = vmx_hwapic_isr_update,
- .guest_apic_has_interrupt = vmx_guest_apic_has_interrupt,
.sync_pir_to_irr = vmx_sync_pir_to_irr,
.deliver_interrupt = vmx_deliver_interrupt,
.dy_apicv_has_pending_interrupt = pi_has_pending_interrupt,
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 10ef8a435..208a713d7 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -13046,12 +13046,6 @@ void kvm_arch_flush_shadow_memslot(struct kvm *kvm,
kvm_page_track_flush_slot(kvm, slot);
}
-static inline bool kvm_guest_apic_has_interrupt(struct kvm_vcpu *vcpu)
-{
- return (is_guest_mode(vcpu) &&
- static_call(kvm_x86_guest_apic_has_interrupt)(vcpu));
-}
-
static inline bool kvm_vcpu_has_events(struct kvm_vcpu *vcpu)
{
if (!list_empty_careful(&vcpu->async_pf.done))
@@ -13077,9 +13071,7 @@ static inline bool kvm_vcpu_has_events(struct kvm_vcpu *vcpu)
static_call(kvm_x86_smi_allowed)(vcpu, false)))
return true;
- if (kvm_arch_interrupt_allowed(vcpu) &&
- (kvm_cpu_has_interrupt(vcpu) ||
- kvm_guest_apic_has_interrupt(vcpu)))
+ if (kvm_arch_interrupt_allowed(vcpu) && kvm_cpu_has_interrupt(vcpu))
return true;
if (kvm_hv_has_stimer_pending(vcpu))
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH v2 6.1.y 0/3] KVM: nVMX: backport virtual-APIC host NULL-deref fix
2026-06-19 20:31 [PATCH v2 6.1.y 0/3] KVM: nVMX: backport virtual-APIC host NULL-deref fix Nicholas Dudar
` (2 preceding siblings ...)
2026-06-19 20:31 ` [PATCH v2 6.1.y 3/3] KVM: nVMX: Fold requested virtual interrupt check into has_nested_events() Nicholas Dudar
@ 2026-06-21 13:47 ` Sasha Levin
3 siblings, 0 replies; 6+ messages in thread
From: Sasha Levin @ 2026-06-21 13:47 UTC (permalink / raw)
To: stable
Cc: Sasha Levin, seanjc, pbonzini, gregkh, kvm, linux-kernel, 0wn,
mlevitsk, jmattson, Nicholas Dudar
> This series backports the fix for a guest-triggerable host NULL pointer
> dereference in nested-VMX virtual-APIC handling. The bug is present in
> 6.1.y and fixed in 6.6.y and later.
Queued the 3-patch series for 6.1, thanks.
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 6+ messages in thread