[PATCH v2] KVM: arm64: vgic-its: Update GITS_CTLR.Enabled under its_lock

From: Fuad Tabba

Date: Mon Nov 02 2026 - 03:29:45 EST


A GITS_CTLR write that disables the ITS clears its->enabled and
invalidates the translation cache under cmd_lock, while an MSI
injection tests the enable bit and populates the cache under its_lock.
An injection that passed the test before the disable can cache its
translation after the invalidation, and the cached path then injects
the LPI from the disabled ITS on every later MSI for that event.

Take its_lock around the store and the invalidation, so that it
serializes the store against the test. Nest it under cmd_lock, as the
command handlers do.

Fixes: 86a7dae884f38 ("KVM: arm/arm64: vgic-its: Check the LPI translation cache on MSI injection")
Reported-by: Hyunwoo Kim <imv4bel@xxxxxxxxx>
Link: https://lore.kernel.org/ah6Lsi4MfKUU6wBR@v4bel/
Suggested-by: Oliver Upton <oupton@xxxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Fuad Tabba <fuad.tabba@xxxxxxxxx>
---

Notes:
Changes since v1 [1]:
- Reworded the commit message.
- Rebased on 7.4-rc1.

Picking up the fix Marc was worried had stalled [2].

The GICR_CTLR.EnableLPIs path has the same window and is left as
is. vgic_its_invalidate_all_caches() can't take kvm->lock there (it
runs with vcpu->mutex held) or config_lock (the userspace GICR_CTLR
route already holds it). Instead, the vgic_target_oracle() check in
my LPIs-disabled series [3] keeps an LPI off a redistributor with
EnableLPIs clear. The two don't depend on each other.

vgic_its_free_collection_list() still leaves the cache alone. A
guest reaches it only through a GITS_BASER write, which already
drops the cache (8cd92f77ae4f). An ITS reset frees the devices
first, which drops it too. That leaves a failed collection-table
restore. A VMM gets there with translations cached only by enabling
the ITS before the restore, against the documented order. Those
translations then keep injecting their LPIs into the old target
vCPUs.

Based on Linux 7.4-rc1 (6628e7d4c6b25; also applies to kvmarm/fixes
79f3724ca8913).

[1] https://lore.kernel.org/all/20260917162020.1612577-1-fuad.tabba@xxxxxxxxx/
[2] https://lore.kernel.org/861pbr6emw.wl-maz@xxxxxxxxxx/
[3] https://lore.kernel.org/all/20261026092802.2636798-1-fuad.tabba@xxxxxxxxx/

arch/arm64/kvm/vgic/vgic-its.c | 3 +++
1 file changed, 3 insertions(+)

diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c
index 7e9b265b1f521..68e3034e52236 100644
--- a/arch/arm64/kvm/vgic/vgic-its.c
+++ b/arch/arm64/kvm/vgic/vgic-its.c
@@ -1800,9 +1800,12 @@ static void vgic_mmio_write_its_ctlr(struct kvm *kvm, struct vgic_its *its,
!(its->cbaser & GITS_CBASER_VALID)))
goto out;

+ /* vgic_its_resolve_lpi() tests the enable bit and caches under its_lock. */
+ mutex_lock(&its->its_lock);
its->enabled = !!(val & GITS_CTLR_ENABLE);
if (!its->enabled)
vgic_its_invalidate_cache(its);
+ mutex_unlock(&its->its_lock);

/*
* Try to process any pending commands. This function bails out early

base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
--
2.47.3