summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Christopherson <seanjc@google.com>2026-06-12 17:03:25 -0700
committerPaolo Bonzini <pbonzini@redhat.com>2026-06-24 08:24:05 -0400
commit4f1f1ffbddebebdb592c3a43b95e05f017c67946 (patch)
treebf439946ab7db59717f6e4e0ff56b21aead681e2
parentee67344af1865f7d299df413939927073fb59176 (diff)
KVM: x86: Don't treat interrupts as allowed just because a nested run is pending
When querying whether or not interrupts (IRQs) are allowed, check for a pending nested run _after_ checking whether or not interrupts are blocked. If L1 is running L2 _without_ nested_exit_on_intr(), i.e. if L1 IRQs can be blocked while running L2, and interrupts will indeed be blocked once the nested VM-Enter to L2 is completed, then KVM should treat interrupts as not being allowed. For injection, this avoids an unnecessary (forced) VM-Exit, as KVM can immediately request an IRQ window, instead of forcing an exit and _then_ requesting an IRQ window (because after the forced exit, KVM will see that interrupts are blocked). For non-injection usage, only kvm_vcpu_ready_for_interrupt_injection() is affected in practice. Barring KVM bugs or misbehaving userspace (at which point all architectural guarantees are off), kvm_vcpu_has_events() is unreachable when a nested run is pending. To reach kvm_vcpu_has_events(), kvm_vcpu_running() needs to return false, i.e. vcpu->arch.mp_state needs to be something other than RUNNABLE. If nested_run_pending is true, then mp_state *must* be RUNNABLE (again barring bugs or stupid userspace), because KVM shouldn't emulate VMRUN/VMLAUNCH/VMRESUME while the vCPU is !RUNNABLE. The one "near miss" is VMX's GUEST_ACTIVITY_STATE field, which allows L1 to put the vCPU into HLT or WFS as part of nested VMLAUNCH/VMRESUME. However, KVM clears nested_run_pending prior to calling kvm_emulate_halt_noskip() when putting L2 into HLT via GUEST_ACTIVITY_HLT, and also clears the flag before setting mp_state to INIT_RECEIVED. SVM has no equivalent to GUEST_ACTIVITY_STATE. I.e. the vCPU will always be runnable if a nested run is pending, and thus kvm_arch_vcpu_runnable() => kvm_vcpu_has_events() is effectively dead code, as is __kvm_emulate_halt() => kvm_vcpu_has_events(). Oh, and TDX doesn't support nested VMX. Similarly, kvm_can_do_async_pf() is unreachable as KVM shouldn't be faulting in memory with a pending nested VM-Enter. As for kvm_vcpu_ready_for_interrupt_injection(), KVM's current behavior of incorrectly treating interrupts as being allowed could result in KVM prematurely exiting to userspace to accept an ExtINT. But, KVM will still hold/block the ExtINT and request its own IRQ window. I.e. the net effect is more or less the same as the for-injection case, the unnecessary exit just happens at a different boundary. Signed-off-by: Sean Christopherson <seanjc@google.com> Message-ID: <20260613000329.732085-27-seanjc@google.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
-rw-r--r--arch/x86/kvm/svm/svm.c6
-rw-r--r--arch/x86/kvm/vmx/vmx.c5
2 files changed, 7 insertions, 4 deletions
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index e6408c3e8419..ef69a51ab27f 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -4055,12 +4055,12 @@ static int svm_interrupt_allowed(struct kvm_vcpu *vcpu, bool for_injection)
{
struct vcpu_svm *svm = to_svm(vcpu);
- if (vcpu->arch.nested_run_pending)
- return -EBUSY;
-
if (svm_interrupt_blocked(vcpu))
return 0;
+ if (vcpu->arch.nested_run_pending)
+ return -EBUSY;
+
/*
* An IRQ must not be injected into L2 if it's supposed to VM-Exit,
* e.g. if the IRQ arrived asynchronously after checking nested events.
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index b2562d158151..7e7dd3d7c45b 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -5250,6 +5250,9 @@ bool vmx_interrupt_blocked(struct kvm_vcpu *vcpu)
int vmx_interrupt_allowed(struct kvm_vcpu *vcpu, bool for_injection)
{
+ if (vmx_interrupt_blocked(vcpu))
+ return 0;
+
if (vcpu->arch.nested_run_pending)
return -EBUSY;
@@ -5260,7 +5263,7 @@ int vmx_interrupt_allowed(struct kvm_vcpu *vcpu, bool for_injection)
if (for_injection && is_guest_mode(vcpu) && nested_exit_on_intr(vcpu))
return -EBUSY;
- return !vmx_interrupt_blocked(vcpu);
+ return 1;
}
int vmx_set_tss_addr(struct kvm *kvm, unsigned int addr)