From: zjamg <ndaugoing@gmail.com>
To: Marc Zyngier <maz@kernel.org>, Oliver Upton <oliver.upton@linux.dev>
Cc: James Morse <james.morse@arm.com>,
Suzuki K Poulose <suzuki.poulose@arm.com>,
Zenghui Yu <yuzenghui@huawei.com>,
Catalin Marinas <catalin.marinas@arm.com>,
Will Deacon <will@kernel.org>,
kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, Yuchao Zhang <ndaugoing@gmail.com>,
stable@vger.kernel.org
Subject: [PATCH v2] KVM: arm64: vgic: Do not remove in-flight LPIs from AP list on disable
Date: Fri, 18 Sep 2026 12:02:14 +0800 [thread overview]
Message-ID: <20260918040214.85580-2-ndaugoing@gmail.com> (raw)
In-Reply-To: <20260918040214.85580-1-ndaugoing@gmail.com>
From: Yuchao Zhang <ndaugoing@gmail.com>
By code inspection, a race condition and potential Use-After-Free/crash
exists between remote LPI disabling and local LR folding when EOImode==0.
Commit 6da5e537f5af ("KVM: arm64: vgic: Pick EOIcount deactivations from
AP-list tail") introduced tracking the last interrupt placed in a List
Register in per-CPU host data (*host_data_ptr(last_lr_irq)) and traverses
the remaining ap_list via list_for_each_entry_continue() in
vgic_v3_fold_lr_state().
However, an interrupt loaded into an LR can be an LPI. While vCPU-B is
running the guest, another vCPU-A can write to vCPU-B's redistributor
GICR_CTLR to clear EnableLPIs, which dispatches vgic_flush_pending_lpis().
vgic_flush_pending_lpis() unconditionally removes all LPIs from ap_list
without checking whether the interrupt is currently in an LR
(irq->on_lr):
- It calls list_del(&irq->ap_list), setting ap_list.next to LIST_POISON1,
and drops the AP-list reference.
- If the LPI is unmapped or its translation cache was invalidated, the
vgic_irq refcount drops to zero and the object is freed via RCU.
- Meanwhile, vCPU-B's per-CPU *host_data_ptr(last_lr_irq) cannot be
cleared by remote vCPUs and is left dangling.
When vCPU-B subsequently exits the guest:
1. vgic_v3_fold_lr_state() resumes using the unlinked last_lr_irq.
2. list_for_each_entry_continue() unconditionally evaluates
list_next_entry(irq, ap_list) during loop initialization, accessing
LIST_POISON1 (or freed memory).
3. If eoicount > 0, it attempts guard(raw_spinlock)(&irq->irq_lock) on
the poisoned address, leading to an immediate host kernel panic.
Fix this by:
1. In vgic_flush_pending_lpis(), do not remove LPIs that are currently
in-flight in an LR (irq->on_lr == true). They will be naturally pruned
by the owning vCPU's vgic_prune_ap_list() after LR folding.
2. In vgic_fold_state(), clear *host_data_ptr(last_lr_irq) after folding
so that no stale pointer survives past guest execution.
3. In vgic_v3_fold_lr_state() and vgic_v2_fold_lr_state(), bail out
immediately if eoicount is zero, avoiding unnecessary list_next_entry()
evaluation.
4. In vgic_v3_fold_lr(), do not resurrect the pending state of an edge
LPI folded while the redistributor has LPIs disabled.
vgic_flush_pending_lpis() cannot remotely clear the LRs of a running
vCPU, so such an LPI would otherwise be requeued and re-injected
while GICR_CTLR.EnableLPIs is 0, violating the flush semantics (and
the architecture, which gives no expectation of the pending state
being retained across a disable).
Note: this closes the primary race (the last_lr_irq node itself is no
longer unlinkable while in-flight), but the fold traversal can still
race with a remote flush unlinking a subsequent non-LR node in the
ap_list tail. Fully closing that window needs the fold side to take
references before dropping locks (in the spirit of the prune-side fix
in commit 7258770e5814 ("KVM: arm64: vgic: Handle race between
interrupt affinity change and LPI disabling")) and is left as a
follow-up.
Fixes: 6da5e537f5af ("KVM: arm64: vgic: Pick EOIcount deactivations from AP-list tail")
Cc: stable@vger.kernel.org
Signed-off-by: Yuchao Zhang <ndaugoing@gmail.com>
---
arch/arm64/kvm/vgic/vgic-v2.c | 3 +++
arch/arm64/kvm/vgic/vgic-v3.c | 14 ++++++++++++--
arch/arm64/kvm/vgic/vgic.c | 10 +++++++---
3 files changed, 22 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-v2.c b/arch/arm64/kvm/vgic/vgic-v2.c
index 7182f63fc938..7b6cd05ce32d 100644
--- a/arch/arm64/kvm/vgic/vgic-v2.c
+++ b/arch/arm64/kvm/vgic/vgic-v2.c
@@ -122,6 +122,9 @@ void vgic_v2_fold_lr_state(struct kvm_vcpu *vcpu)
for (int lr = 0; lr < vgic_cpu->vgic_v2.used_lrs; lr++)
vgic_v2_fold_lr(vcpu, cpuif->vgic_lr[lr]);
+ if (!eoicount)
+ return;
+
/* See the GICv3 equivalent for the EOIcount handling rationale */
list_for_each_entry_continue(irq, &vgic_cpu->ap_list_head, ap_list) {
u32 lr;
diff --git a/arch/arm64/kvm/vgic/vgic-v3.c b/arch/arm64/kvm/vgic/vgic-v3.c
index 726e20a1da6e..dd1314f4543d 100644
--- a/arch/arm64/kvm/vgic/vgic-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-v3.c
@@ -96,9 +96,16 @@ static void vgic_v3_fold_lr(struct kvm_vcpu *vcpu, u64 val)
deactivated = irq->active && !(val & ICH_LR_ACTIVE_BIT);
irq->active = !!(val & ICH_LR_ACTIVE_BIT);
- /* Edge is the only case where we preserve the pending bit */
+ /*
+ * Edge is the only case where we preserve the pending bit.
+ * Do not resurrect the pending state of LPIs once the
+ * redistributor has them disabled: vgic_flush_pending_lpis()
+ * has discarded them, and the LRs of a running vCPU cannot
+ * be remotely cleared.
+ */
if (irq->config == VGIC_CONFIG_EDGE &&
- (val & ICH_LR_PENDING_BIT))
+ (val & ICH_LR_PENDING_BIT) &&
+ (irq->intid < VGIC_MIN_LPI || vgic_lpis_enabled(vcpu)))
irq->pending_latch = true;
/*
@@ -155,6 +162,9 @@ void vgic_v3_fold_lr_state(struct kvm_vcpu *vcpu)
for (int lr = 0; lr < cpuif->used_lrs; lr++)
vgic_v3_fold_lr(vcpu, cpuif->vgic_lr[lr]);
+ if (!eoicount)
+ return;
+
/*
* EOIMode=0: use EOIcount to emulate deactivation. We are
* guaranteed to deactivate in reverse order of the activation, so
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index b25303d9919f..0d7d75c3ac93 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -205,10 +205,12 @@ void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu)
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;
+ if (!irq->on_lr) {
+ list_del(&irq->ap_list);
+ irq->vcpu = NULL;
+ deleted |= vgic_put_irq_norelease(vcpu->kvm, irq);
+ }
raw_spin_unlock(&irq->irq_lock);
- deleted |= vgic_put_irq_norelease(vcpu->kvm, irq);
}
}
@@ -873,6 +875,8 @@ static void vgic_fold_state(struct kvm_vcpu *vcpu)
vgic_v2_fold_lr_state(vcpu);
else
vgic_v3_fold_lr_state(vcpu);
+
+ *host_data_ptr(last_lr_irq) = NULL;
}
/* Requires the irq_lock to be held. */
--
2.53.0
next prev parent reply other threads:[~2026-09-18 4:02 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-18 4:02 [PATCH v2 0/1] KVM: arm64: vgic: fix UAF/crash on remote LPI disable zjamg
2026-09-18 4:02 ` zjamg [this message]
2026-09-18 7:22 ` [PATCH v2] KVM: arm64: vgic: Do not remove in-flight LPIs from AP list on disable Fuad Tabba
2026-09-18 11:50 ` Yuchao Zhang
2026-09-18 11:58 ` Fuad Tabba
2026-09-18 19:15 ` Oliver Upton
2026-09-20 23:54 ` Marc Zyngier
2026-09-20 1:49 ` [PATCH v3 0/1] KVM: arm64: vgic: Drop last_lr_irq and serialize overflow EOI replay Yuchao Zhang
2026-09-20 1:49 ` [PATCH v3] " Yuchao Zhang
2026-09-20 23:47 ` [PATCH v3 0/1] " Marc Zyngier
2026-09-22 10:16 ` Yuchao Zhang
2026-09-22 10:16 ` [PATCH] KVM: selftests: arm64: Add test for cross-vCPU LPI disable race Yuchao Zhang
2026-09-22 10:29 ` sashiko-bot
2026-09-22 10:53 ` Yuchao Zhang
2026-09-22 10:53 ` [PATCH v2] " Yuchao Zhang
2026-09-22 11:03 ` sashiko-bot
2026-09-22 11:24 ` Yuchao Zhang
2026-09-22 17:08 ` Fuad Tabba
2026-09-22 17:05 ` [PATCH v3 0/1] KVM: arm64: vgic: Drop last_lr_irq and serialize overflow EOI replay Fuad Tabba
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260918040214.85580-2-ndaugoing@gmail.com \
--to=ndaugoing@gmail.com \
--cc=catalin.marinas@arm.com \
--cc=james.morse@arm.com \
--cc=kvmarm@lists.linux.dev \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=maz@kernel.org \
--cc=oliver.upton@linux.dev \
--cc=stable@vger.kernel.org \
--cc=suzuki.poulose@arm.com \
--cc=will@kernel.org \
--cc=yuzenghui@huawei.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.