]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
KVM: arm64: vgic: Handle race between interrupt affinity change and LPI disabling
authorMarc Zyngier <maz@kernel.org>
Mon, 15 Jun 2026 18:16:25 +0000 (19:16 +0100)
committerMarc Zyngier <maz@kernel.org>
Wed, 17 Jun 2026 11:49:07 +0000 (12:49 +0100)
Hyunwoo Kim reports some really bad races should the following
situation occur:

- LPI-I is pending in vcpu-B's AP list
- vcpu-A writes to vcpu-B's RD to disable its LPIs
- vcpu-C moves I from B to C

If the last two race nicely enough, vgic_prune_ap_list() can drop
the irq and AP list locks, reacquire them, and in the interval
the irq has been freed. UAF follows.

The fix is two-fold:

- Before dropping the irq and ap_list locks, take a reference on
  the irq

- Do not try to handle migration of the pending bit: there is no
  expectation that this state is retained, as per the architecture

With that, we're sure that the interrupt is still around, and we
safely remove it from the AP list as it has no target at this
stage (unless another interrupt fires, but that's another story).

Reported-by: Hyunwoo Kim <imv4bel@gmail.com>
Tested-by: Hyunwoo Kim <imv4bel@gmail.com>
Link: https://lore.kernel.org/r/ailsCnyoS82r_QRz@v4bel
Link: https://patch.msgid.link/20260615181625.3029352-1-maz@kernel.org
Fixes: 5dd4b924e390a ("KVM: arm/arm64: vgic: Add refcounting for IRQs")
Signed-off-by: Marc Zyngier <maz@kernel.org>
Cc: stable@vger.kernel.org
arch/arm64/kvm/vgic/vgic.c

index 5a4768d8cd4f313e304c7c2fb95916e4b2977930..70a161383e5a6078ea1012a84e1e081e3cec088d 100644 (file)
@@ -203,6 +203,7 @@ void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu)
        list_for_each_entry_safe(irq, tmp, &vgic_cpu->ap_list_head, ap_list) {
                if (irq_is_lpi(vcpu->kvm, irq->intid)) {
                        raw_spin_lock(&irq->irq_lock);
+                       irq->pending_latch = false;
                        list_del(&irq->ap_list);
                        irq->vcpu = NULL;
                        raw_spin_unlock(&irq->irq_lock);
@@ -792,7 +793,11 @@ retry:
                        continue;
                }
 
-               /* This interrupt looks like it has to be migrated. */
+               /*
+                * This interrupt looks like it has to be migrated,
+                * make sure it is kept alive while locks are dropped.
+                */
+               vgic_get_irq_ref(irq);
 
                raw_spin_unlock(&irq->irq_lock);
                raw_spin_unlock(&vgic_cpu->ap_list_lock);
@@ -836,6 +841,8 @@ retry:
                raw_spin_unlock(&vcpuB->arch.vgic_cpu.ap_list_lock);
                raw_spin_unlock(&vcpuA->arch.vgic_cpu.ap_list_lock);
 
+               deleted_lpis |= vgic_put_irq_norelease(vcpu->kvm, irq);
+
                if (target_vcpu_needs_kick) {
                        kvm_make_request(KVM_REQ_IRQ_PENDING, target_vcpu);
                        kvm_vcpu_kick(target_vcpu);