* [PATCH v4 0/2] KVM: arm64: vgic: Fix racy LPI release and re-registration handling @ 2026-07-03 12:25 Carlos López 2026-07-03 12:26 ` [PATCH v4 1/2] KVM: arm64: vgic: Fix race between LPI release and re-registration Carlos López 2026-07-03 12:26 ` [PATCH v4 2/2] KVM: arm64: vgic: Mitigate potential LPI registration failure Carlos López 0 siblings, 2 replies; 5+ messages in thread From: Carlos López @ 2026-07-03 12:25 UTC (permalink / raw) To: kvmarm, linux-kernel Cc: maz, oupton, joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas, will, linux-arm-kernel, Carlos López Fix a couple of potential issues that could arise from racy LPI release and re-registration for the same INTID. The issue fixed in patch 1 can manifest itself through either a leaked LPI structure, or a prematurely deleted LPI. The issue fixed in patch 2 could materialize as a spurious -ENOMEM failure when registering an LPI. v4: * Add __GFP_ACCOUNT to patch 2 (Sashiko). v3: * Use refcount_dec_and_lock_irqsave() instead of unconditionally grabbing the xarray lock in patch 1. * Add patch 2. v2: * Address Sashiko's review. Fix the direct release path by decrementing the refcount under the xarray spinlock, preventing a UAF that would have been introduced in v1. Carlos López (2): KVM: arm64: vgic: Fix race between LPI release and re-registration KVM: arm64: vgic: Mitigate potential LPI registration failure arch/arm64/kvm/vgic/vgic-its.c | 11 ++++++++++- arch/arm64/kvm/vgic/vgic.c | 10 ++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) base-commit: 1ee27dacbe5dc4def481794d899d67b0d4570094 -- 2.51.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v4 1/2] KVM: arm64: vgic: Fix race between LPI release and re-registration 2026-07-03 12:25 [PATCH v4 0/2] KVM: arm64: vgic: Fix racy LPI release and re-registration handling Carlos López @ 2026-07-03 12:26 ` Carlos López 2026-07-03 22:13 ` Oliver Upton 2026-07-03 12:26 ` [PATCH v4 2/2] KVM: arm64: vgic: Mitigate potential LPI registration failure Carlos López 1 sibling, 1 reply; 5+ messages in thread From: Carlos López @ 2026-07-03 12:26 UTC (permalink / raw) To: kvmarm, linux-kernel Cc: maz, oupton, joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas, will, linux-arm-kernel, Carlos López Fix a potential race between decrementing an LPI's reference count and evicting that structure from the LPI xarray. LPI structures are maintained in the VGIC LPI xarray (dist->lpi_xa). When the reference count of an LPI structure drops to zero, vgic_release_lpi_locked() removes the structure from the xarray and frees it under the xarray lock. However, the release of an LPI can race with a concurrent LPI re-registration with the same INTID via vgic_add_lpi() on another CPU, since the reference count drop and the xarray eviction are not performed in a single atomic step. This can happen e.g. if the guest issues a DISCARD while the LPI is still referenced from a vCPU's active-pending list (ap_list), and the same INTID is re-mapped via MAPTI. Particularly, vgic_release_lpi_locked() is called from two distinct paths: direct release via vgic_put_irq(), and deferred release via vgic_release_deleted_lpis(). During direct release, the issue can result in deleting a newly registered LPI from the xarray: CPU0 (Releasing LPI) CPU1 (Adding new LPI) ==================== ===================== vgic_put_irq() __vgic_put_irq() refcount_dec_and_test() vgic_add_lpi() xa_lock_irqsave() old_irq = xa_load(.., intid) vgic_try_get_irq_ref(old_irq) == false new IRQ inserted --> __xa_store(.., intid, ..) xa_unlock_irqrestore() xa_lock_irqsave(); vgic_release_lpi_locked() __xa_erase(.., irq->intid) <-- BUG: new IRQ is erased kfree_rcu(old_irq) During the deferred release path, the old IRQ can be leaked: CPU0 (Releasing LPI) CPU1 (Adding new LPI) ==================== ===================== vgic_put_irq_norelease() __vgic_put_irq() refcount_dec_and_test() irq->pending_release = true vgic_add_lpi() xa_lock_irqsave() old_irq = xa_load(.., intid) vgic_try_get_irq_ref(oldirq) == false BUG: old IRQ overwritten --> __xa_store(.., intid, ..) xa_unlock_irqrestore() vgic_release_deleted_lpis() xa_lock_irqsave() xa_for_each() { .. } <-- old IRQ with pending_release = true is gone, so it cannot be released To fix the direct release path, move the reference count drop inside the xarray lock, making sure that vgic_add_lpi() never encounters the to-be-released LPI. To fix the deferred release path, since the refcount drop must happen under a raw spinlock, the same solution does not work. Instead, update vgic_add_lpi(), so that if it evicts a non-NULL refcount=0 LPI from the xarray, it takes on the responsibility of releasing it. If this happens, vgic_release_deleted_lpis() will iterate the xarray normally and will simply not find the already released structure. Reported-by: Claude:claude-opus-4-6 Fixes: 3a08a6ca7c37 ("KVM: arm64: vgic-v3: Use bare refcount for VGIC LPIs") Fixes: d54594accf73 ("KVM: arm64: vgic-v3: Erase LPIs from xarray outside of raw spinlocks") Signed-off-by: Carlos López <clopez@suse.de> --- arch/arm64/kvm/vgic/vgic-its.c | 10 +++++++++- arch/arm64/kvm/vgic/vgic.c | 10 ++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 67d107e9a77d..577286069368 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -116,7 +116,15 @@ static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid, kfree(irq); irq = oldirq; } else { - ret = xa_err(__xa_store(&dist->lpi_xa, intid, irq, 0)); + /* + * The entry is either empty or contains a dead LPI (refcount=0) + * from the deferred release path, pending cleanup by + * vgic_release_deleted_lpis(). Evict and free it if present. + */ + oldirq = __xa_store(&dist->lpi_xa, intid, irq, 0); + ret = xa_err(oldirq); + if (!ret && oldirq) + kfree_rcu(oldirq, rcu); } xa_unlock_irqrestore(&dist->lpi_xa, flags); diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c index 5a4768d8cd4f..cc09e0c45b46 100644 --- a/arch/arm64/kvm/vgic/vgic.c +++ b/arch/arm64/kvm/vgic/vgic.c @@ -167,12 +167,14 @@ void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq) guard(spinlock_irqsave)(&dist->lpi_xa.xa_lock); } - if (!__vgic_put_irq(kvm, irq)) + if (!irq_is_lpi(kvm, irq->intid)) return; - xa_lock_irqsave(&dist->lpi_xa, flags); - vgic_release_lpi_locked(dist, irq); - xa_unlock_irqrestore(&dist->lpi_xa, flags); + if (refcount_dec_and_lock_irqsave(&irq->refcount, + &dist->lpi_xa.xa_lock, &flags)) { + vgic_release_lpi_locked(dist, irq); + xa_unlock_irqrestore(&dist->lpi_xa, flags); + } } static void vgic_release_deleted_lpis(struct kvm *kvm) -- 2.51.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v4 1/2] KVM: arm64: vgic: Fix race between LPI release and re-registration 2026-07-03 12:26 ` [PATCH v4 1/2] KVM: arm64: vgic: Fix race between LPI release and re-registration Carlos López @ 2026-07-03 22:13 ` Oliver Upton 2026-07-03 22:19 ` Oliver Upton 0 siblings, 1 reply; 5+ messages in thread From: Oliver Upton @ 2026-07-03 22:13 UTC (permalink / raw) To: Carlos López Cc: kvmarm, linux-kernel, maz, joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas, will, linux-arm-kernel Hi Carlos, Please wait a few days between spins, we were in the middle of a conversation and I didn't have a chance to reply to you since yesterday. On Fri, Jul 03, 2026 at 02:26:00PM +0200, Carlos López wrote: > diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c > index 67d107e9a77d..577286069368 100644 > --- a/arch/arm64/kvm/vgic/vgic-its.c > +++ b/arch/arm64/kvm/vgic/vgic-its.c > @@ -116,7 +116,15 @@ static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid, > kfree(irq); > irq = oldirq; > } else { > - ret = xa_err(__xa_store(&dist->lpi_xa, intid, irq, 0)); > + /* > + * The entry is either empty or contains a dead LPI (refcount=0) > + * from the deferred release path, pending cleanup by > + * vgic_release_deleted_lpis(). Evict and free it if present. > + */ > + oldirq = __xa_store(&dist->lpi_xa, intid, irq, 0); > + ret = xa_err(oldirq); > + if (!ret && oldirq) > + kfree_rcu(oldirq, rcu); We need to assert the assumption here that the evicted IRQ has a nonzero refcount and is pending release. Otherwise I feel like possibly leaking memory is better than potentially introducing a UAF. Untested diff below. Thanks, Oliver diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 4477f870c7b3..621a83399184 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -116,18 +116,28 @@ static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid, kfree(irq); irq = oldirq; } else { - ret = xa_err(__xa_store(&dist->lpi_xa, intid, irq, 0)); - } - - xa_unlock_irqrestore(&dist->lpi_xa, flags); + oldirq = __xa_store(&dist->lpi_xa, intid, irq, 0); + ret = xa_err(oldirq); + if (ret) { + xa_unlock_irqrestore(&dist->lpi_xa, flags); + kfree(irq); - if (ret) { - xa_release(&dist->lpi_xa, intid); - kfree(irq); + return ERR_PTR(ret); + } - return ERR_PTR(ret); + /* + * The expectation is that only LPIs which are pending release + * could possibly be evicted by storing the new entry. Free the + * old IRQ only if this is the case, as otherwise someone else + * could still hold a pointer on the outgoing IRQ. + */ + if (oldirq && !WARN_ON_ONCE(refcount_read(&oldirq->refcount) && + oldirq->pending_release)) + kfree_rcu(oldirq, rcu); } + xa_unlock_irqrestore(&dist->lpi_xa, flags); + /* * We "cache" the configuration table entries in our struct vgic_irq's. * However we only have those structs for mapped IRQs, so we read in ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v4 1/2] KVM: arm64: vgic: Fix race between LPI release and re-registration 2026-07-03 22:13 ` Oliver Upton @ 2026-07-03 22:19 ` Oliver Upton 0 siblings, 0 replies; 5+ messages in thread From: Oliver Upton @ 2026-07-03 22:19 UTC (permalink / raw) To: Carlos López Cc: kvmarm, linux-kernel, maz, joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas, will, linux-arm-kernel On Fri, Jul 03, 2026 at 03:13:34PM -0700, Oliver Upton wrote: > Hi Carlos, > > Please wait a few days between spins, we were in the middle of a > conversation and I didn't have a chance to reply to you since yesterday. > > On Fri, Jul 03, 2026 at 02:26:00PM +0200, Carlos López wrote: > > diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c > > index 67d107e9a77d..577286069368 100644 > > --- a/arch/arm64/kvm/vgic/vgic-its.c > > +++ b/arch/arm64/kvm/vgic/vgic-its.c > > @@ -116,7 +116,15 @@ static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid, > > kfree(irq); > > irq = oldirq; > > } else { > > - ret = xa_err(__xa_store(&dist->lpi_xa, intid, irq, 0)); > > + /* > > + * The entry is either empty or contains a dead LPI (refcount=0) > > + * from the deferred release path, pending cleanup by > > + * vgic_release_deleted_lpis(). Evict and free it if present. > > + */ > > + oldirq = __xa_store(&dist->lpi_xa, intid, irq, 0); > > + ret = xa_err(oldirq); > > + if (!ret && oldirq) > > + kfree_rcu(oldirq, rcu); > > We need to assert the assumption here that the evicted IRQ has a nonzero s/nonzero/zero Obviously not reading what the hell I'm writing :) > refcount and is pending release. Otherwise I feel like possibly leaking > memory is better than potentially introducing a UAF. > > Untested diff below. > > Thanks, > Oliver > > diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c > index 4477f870c7b3..621a83399184 100644 > --- a/arch/arm64/kvm/vgic/vgic-its.c > +++ b/arch/arm64/kvm/vgic/vgic-its.c > @@ -116,18 +116,28 @@ static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid, > kfree(irq); > irq = oldirq; > } else { > - ret = xa_err(__xa_store(&dist->lpi_xa, intid, irq, 0)); > - } > - > - xa_unlock_irqrestore(&dist->lpi_xa, flags); > + oldirq = __xa_store(&dist->lpi_xa, intid, irq, 0); > + ret = xa_err(oldirq); > + if (ret) { > + xa_unlock_irqrestore(&dist->lpi_xa, flags); > + kfree(irq); > > - if (ret) { > - xa_release(&dist->lpi_xa, intid); > - kfree(irq); > + return ERR_PTR(ret); > + } > > - return ERR_PTR(ret); > + /* > + * The expectation is that only LPIs which are pending release > + * could possibly be evicted by storing the new entry. Free the > + * old IRQ only if this is the case, as otherwise someone else > + * could still hold a pointer on the outgoing IRQ. > + */ > + if (oldirq && !WARN_ON_ONCE(refcount_read(&oldirq->refcount) && > + oldirq->pending_release)) if (oldirq && !WARN_ON_ONCE(refcount_read(&oldirq->refcount) || !oldirq->pending_release)) Thanks, Oliver ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v4 2/2] KVM: arm64: vgic: Mitigate potential LPI registration failure 2026-07-03 12:25 [PATCH v4 0/2] KVM: arm64: vgic: Fix racy LPI release and re-registration handling Carlos López 2026-07-03 12:26 ` [PATCH v4 1/2] KVM: arm64: vgic: Fix race between LPI release and re-registration Carlos López @ 2026-07-03 12:26 ` Carlos López 1 sibling, 0 replies; 5+ messages in thread From: Carlos López @ 2026-07-03 12:26 UTC (permalink / raw) To: kvmarm, linux-kernel Cc: maz, oupton, joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas, will, linux-arm-kernel, Carlos López, Sashiko Mitigate a potential failure when inserting a new LPI into the VGIC LPI xarray. When vgic_add_lpi() is preparing to register a new LPI, it pre-allocates an xarray entry using xa_reserve_irq(), so that it can later perform the insertion under the xarray lock without allocating. However, since xa_reserve_irq() is called before acquiring such lock, there is a potential race where xa_reserve_irq() observes a populated entry, thus not performing the allocation, and another CPU removes that entry before the xarray lock is grabbed to perform the insertion. CPU0 (Adding new LPI) CPU1 (Releasing LPI) ===================== =================== vgic_add_lpi() /* Entry populated, does not allocate */ xa_reserve_irq(.., intid, ..) vgic_release_deleted_lpis() xa_lock_irqsave() vgic_release_lpi_locked() xarray node freed --> __xa_erase(.., intid) xa_unlock_irqrestore() xa_lock_irqsave() xa_load(.., intid) == NULL vgic_try_get_irq_ref(NULL) == false __xa_store(.., intid, irq, 0) <-- xarray node was freed, gfp=0 cannot allocate, returns -ENOMEM This can happen e.g. if the guest issues a DISCARD while the LPI is still referenced from a vCPU's active-pending list (ap_list), and the same INTID is re-mapped via MAPTI. Mitigate this by passing GFP_NOWAIT to __xa_store(), so that the allocation can happen under the lock in the rare case that this condition is hit. Add __GFP_ACCOUNT as well to match xa_reserve_irq()'s flags. Reported-by: Sashiko <sashiko-bot@kernel.org> Fixes: 1d6f83f60f79 ("KVM: arm64: vgic: Store LPIs in an xarray") Signed-off-by: Carlos López <clopez@suse.de> --- arch/arm64/kvm/vgic/vgic-its.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 577286069368..327418477c51 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -121,7 +121,8 @@ static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid, * from the deferred release path, pending cleanup by * vgic_release_deleted_lpis(). Evict and free it if present. */ - oldirq = __xa_store(&dist->lpi_xa, intid, irq, 0); + oldirq = __xa_store(&dist->lpi_xa, intid, irq, + GFP_NOWAIT | __GFP_ACCOUNT); ret = xa_err(oldirq); if (!ret && oldirq) kfree_rcu(oldirq, rcu); -- 2.51.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-03 22:19 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-03 12:25 [PATCH v4 0/2] KVM: arm64: vgic: Fix racy LPI release and re-registration handling Carlos López 2026-07-03 12:26 ` [PATCH v4 1/2] KVM: arm64: vgic: Fix race between LPI release and re-registration Carlos López 2026-07-03 22:13 ` Oliver Upton 2026-07-03 22:19 ` Oliver Upton 2026-07-03 12:26 ` [PATCH v4 2/2] KVM: arm64: vgic: Mitigate potential LPI registration failure Carlos López
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox