From: sashiko-bot@kernel.org
To: "Sascha Bischoff" <sascha.bischoff@arm.com>,
kvmarm@lists.linux.dev, kvm@vger.kernel.org
Cc: "Oliver Upton" <oupton@kernel.org>, "Marc Zyngier" <maz@kernel.org>
Subject: Re: [PATCH 13/16] KVM: arm64: gic-v5: Implement irq forwarding
Date: Fri, 16 Oct 2026 16:25:17 +0000 [thread overview]
Message-ID: <sashiko-outbox-174635@kernel.org> (raw)
In-Reply-To: <20261016155741.979985-14-sascha.bischoff@arm.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] kvm: passing full typed INTID to hardware VDPEND instruction
- [High] kvm: use of undefined macro kzalloc_obj
- [High] kvm: ABBA deadlock due to translation_cache_lock acquired without disabling IRQs
- [High] kvm/vgic: TOCTOU race in its_cache_drop_translation disables wrong host IRQ
- [Medium] kvm: data race from lockless read of irq->hw bitfield
- [Low] cleanup: mixing goto-based cleanup with guard() declarations
--
commit 3a376f5ffceb9df2e3023c6def306e4b00a9eba1
Author: Sascha Bischoff <sascha.bischoff@arm.com>
KVM: arm64: gic-v5: Implement irq forwarding
GICv5, like GICv4, supports direct injection of interrupts via the
ITS. Move the existing GICv4 interface behind a common shim that selects
the appropriate implementation. For GICv5 forwarding, the ITS driver
replaces a host ITTE with one targeting a guest. The host no longer
receives that interrupt until forwarding is removed and the host ITTE is
restored.
> diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
> index 62c1ca8004007..e71cd67652bae 100644
> --- a/arch/arm64/kvm/vgic/vgic-its-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
[ ... ]
> @@ -31,7 +32,41 @@ static void its_cache_drop_translation(struct kvm *kvm,
> struct its_cache_translation_entry *entry)
> {
> struct vgic_irq *irq = entry->irq;
> + unsigned long flags;
> + int virq = 0;
> + bool hw = false;
> + bool drop_forwarding_ref = false;
>
> + /*
> + * Multiple translations can share an LPI as we have no way of stopping
> + * the guest from doing so. Only the entry associated with direct injection
> + * may tear down the IRQ's forwarding state, as otherwise we can end up
> + * accidentally tearing something down. Realistically, this only would
> + * result in a performance degradation, but we might as well avoid that
> + * in the first place if we can.
> + */
> + raw_spin_lock_irqsave(&irq->irq_lock, flags);
> + if (irq->forwarding_owner == entry) {
> + /* The entry is about to be freed, even if unforwarding fails. */
> + irq->forwarding_owner = NULL;
> + virq = irq->host_irq;
> + hw = irq->hw;
> + }
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> +
> + if (hw && !WARN_ON_ONCE(gicv5_its_disable_direct_injection(virq))) {
[Severity: High]
Is there a TOCTOU race here when the lock is dropped before calling
gicv5_its_disable_direct_injection?
While the lock is released, a concurrent host unbind operation could invoke
irq_bypass_del_producer, unset the forwarding, and free the host IRQ. The Linux
IRQ number could then be immediately reallocated to a different device. When
this thread resumes, it would unconditionally disable hardware direct injection
for the newly assigned device/VM using the stale virq identifier, potentially
causing lost interrupts.
> + raw_spin_lock_irqsave(&irq->irq_lock, flags);
> + if (irq->hw && irq->host_irq == virq &&
> + !irq->forwarding_owner) {
> + irq->hw = false;
> + irq->host_irq = 0;
> + drop_forwarding_ref = true;
> + }
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> + }
[ ... ]
> @@ -50,7 +85,8 @@ static void its_cache_drop_removed_translations(struct kvm *kvm,
>
> static int its_cache_add_translation(struct kvm *kvm, struct vgic_v5_its *its,
> u32 device_id, u16 event_id,
> - struct vgic_irq *irq)
> + struct vgic_irq *irq,
> + void **owner)
> {
> u64 key = ((u64)device_id << 16) | (u64)event_id;
> struct its_cache_translation_entry *entry;
> @@ -78,6 +114,8 @@ static int its_cache_add_translation(struct kvm *kvm, struct vgic_v5_its *its,
> + struct its_cache_translation_entry *old;
> + struct hlist_node *tmp;
> + HLIST_HEAD(removed);
>
> - entry = kzalloc(sizeof(*entry), GFP_KERNEL);
> + entry = kzalloc_obj(*entry, GFP_KERNEL);
[Severity: High]
Is kzalloc_obj defined anywhere in the tree? It looks like this might be a
typo for kzalloc(sizeof(*entry), GFP_KERNEL) which will cause compilation
failures.
> if (!entry)
> return -ENOMEM;
>
> entry->key = key;
> entry->irq = irq;
>
> - raw_spin_lock(&its->translation_cache_lock);
> + scoped_guard(raw_spinlock, &its->translation_cache_lock) {
[Severity: High]
Should this use the irqsave variant of scoped_guard? Acquiring
translation_cache_lock in process context without disabling local hardware
interrupts could lead to an ABBA deadlock. If irqfd_wakeup runs in HardIRQ
context on a CPU that already holds this lock, the interrupt handler might
spin forever trying to acquire it.
> hash_for_each_possible_safe(its->translation_cache, old, tmp,
> node, key) {
> if (old->key == key) {
[ ... ]
> @@ -129,7 +167,8 @@ static void its_cache_clear_translations(struct vgic_v5_its *its)
>
> static int its_cache_look_up_translation(struct vgic_v5_its *its,
> u32 device_id, u16 event_id,
> - struct vgic_irq **irq)
> + struct vgic_irq **irq,
> + void **owner)
> {
> u64 key = ((u64)device_id << 16) | (u64)event_id;
> struct its_cache_translation_entry *entry;
>
> - raw_spin_lock(&its->translation_cache_lock);
> + guard(raw_spinlock)(&its->translation_cache_lock);
[Severity: High]
Similarly here, acquiring translation_cache_lock without disabling interrupts
leaves the system vulnerable to the same ABBA deadlock if the lock is
contended by HardIRQ context.
> hash_for_each_possible(its->translation_cache, entry, node, key) {
> if (entry->key == key) {
[ ... ]
> @@ -657,8 +735,8 @@ int vgic_v5_its_inject_cached_translation(struct kvm *kvm, struct kvm_msi *msi)
> static void vgic_v5_its_inject_lpi(struct kvm *kvm, struct vgic_irq *irq)
> {
> kvm_call_hyp(__vgic_v5_vdpend, irq->intid, true,
> kvm->arch.vgic.gicv5_vm.vm_id);
> }
[Severity: High]
Does this pass the full typed INTID directly to the hardware GIC VDPEND
instruction?
GICv5 INTIDs encode the type in bits [31:29]. The architecture limits hardware
instructions to expecting a raw hardware ID. Should this be masked using
vgic_v5_get_hwirq_id(irq->intid) to avoid feeding the type bits into the
hardware operand?
[ ... ]
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index c90e00f56b298..970df3f41cf24 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
[ ... ]
> +int kvm_vgic_v5_set_forwarding(struct kvm *kvm, int virq,
> + struct kvm_kernel_irq_routing_entry *irq_entry)
> +{
> + struct vgic_v5_its *its_data;
> + struct vgic_irq *irq;
> + void *owner;
> + bool hw;
> + int ret;
> +
> + if (!vgic_has_its(kvm))
> + return 0;
> +
> + /* Look up the ITS; bail out if we can't look it up */
> + its_data = vgic_v5_get_its(kvm, irq_entry);
> + if (IS_ERR(its_data))
> + return 0;
> +
> + /*
> + * Look up and cache the translation. Bail out if we failed to look up
> + * the translation at this stage. If the translation becomes valid at a
> + * later stage, we should fall back to software-based injection.
> + */
> + guard(mutex)(&its_data->its_lock);
> +
> + ret = vgic_v5_its_resolve_lpi_locked(kvm, its_data,
> + irq_entry->msi.devid,
> + irq_entry->msi.data, &irq, &owner);
> + if (ret)
> + return 0;
> +
> + scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
> + hw = irq->hw;
> + }
> +
> + if (hw)
> + goto out_put_irq;
[Severity: Low]
Does this mix goto-based error handling with scope-based cleanup helpers like
guard() and scoped_guard()? The kernel cleanup guidelines strongly discourage
mixing these in the same function to avoid confusing ownership semantics.
Should this function be standardized to use only one of the approaches?
[ ... ]
> +static struct vgic_irq *vgic_v5_host_irq_get_lpi(struct kvm *kvm, int host_irq)
> +{
> + struct vgic_irq *irq;
> + unsigned long idx;
> +
> + guard(rcu)();
> + xa_for_each(&kvm->arch.vgic.lpi_xa, idx, irq) {
> + if (!irq->hw || irq->host_irq != host_irq)
> + continue;
[Severity: Medium]
Is it safe to locklessly read the irq->hw bitfield here?
This bitfield is modified concurrently by kvm_vgic_v5_set_forwarding and
kvm_vgic_v5_unset_forwarding under irq_lock. Concurrent read and write access
to a C bitfield is undefined behavior and can result in torn reads or
corrupted data.
> +
> + if (!vgic_try_get_irq_ref(irq))
> + return NULL;
> +
> + return irq;
> + }
> +
> + return NULL;
> +}
> +
> +void kvm_vgic_v5_unset_forwarding(struct kvm *kvm, int virq)
> +{
> + struct vgic_irq *irq;
> + bool drop_forwarding_ref = false;
> +
> + if (!vgic_has_its(kvm))
> + return;
> +
> + /*
> + * Note: We don't remove the entry from our translation cache here. It
> + * is up to the guest to invalidate the old translations, and hence the
> + * removal path is tied into ITS_INV_DEVICER and ITS_INV_EVENTR, rather
> + * than being explicitly called here.
> + */
> + irq = vgic_v5_host_irq_get_lpi(kvm, virq);
> + if (!irq)
> + return;
> +
> + if (WARN_ON_ONCE(gicv5_its_disable_direct_injection(virq)))
> + goto out_put_irq;
> +
> + scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
[Severity: Low]
This is another instance of mixing goto-based error handling alongside
scoped_guard(). Should we convert this completely to scope-based cleanup or
stick entirely to traditional goto labels?
> + if (irq->hw && irq->host_irq == virq) {
> + irq->hw = false;
> + irq->host_irq = 0;
> + irq->forwarding_owner = NULL;
> + drop_forwarding_ref = true;
> + }
> + }
> +
> + if (drop_forwarding_ref)
> + vgic_put_irq(kvm, irq);
> +
> +out_put_irq:
> + vgic_put_irq(kvm, irq);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016155741.979985-1-sascha.bischoff@arm.com?part=13
next prev parent reply other threads:[~2026-10-16 16:25 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
2026-10-16 15:57 ` [PATCH 01/16] KVM: arm64: vgic: Split out common LPI allocation Sascha Bischoff
2026-10-16 16:14 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 02/16] KVM: arm64: vgic: Pass opaque device pointer to ITS MMIO callbacks Sascha Bischoff
2026-10-16 15:57 ` [PATCH 03/16] KVM: arm64: gic-v5: Share CoreSight ID register emulation Sascha Bischoff
2026-10-16 15:57 ` [PATCH 04/16] irqchip/gic-v5: Add ITS MMIO register definitions Sascha Bischoff
2026-10-16 15:57 ` [PATCH 05/16] KVM: arm64: gic-v5: Add ITS device skeleton Sascha Bischoff
2026-10-16 15:57 ` [PATCH 06/16] KVM: arm64: gic-v5: Add ITS MMIO state emulation Sascha Bischoff
2026-10-16 16:11 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 07/16] KVM: arm64: gic-v5: Add ITS guest table translation emulation Sascha Bischoff
2026-10-16 16:17 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 08/16] KVM: arm64: gic-v5: Add ITS translation cache maintenance Sascha Bischoff
2026-10-16 16:10 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 09/16] KVM: arm64: gic-v5: Add ITS MSI injection Sascha Bischoff
2026-10-16 16:23 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 10/16] KVM: arm64: gic-v5: Add ITS userspace register access Sascha Bischoff
2026-10-16 16:19 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 11/16] irqchip/gic-v5: Enable direct injection in ITS Sascha Bischoff
2026-10-16 16:22 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 12/16] KVM: arm64: vgic: Move direct MSI support helper to common MMIO code Sascha Bischoff
2026-10-16 15:57 ` [PATCH 13/16] KVM: arm64: gic-v5: Implement irq forwarding Sascha Bischoff
2026-10-16 16:25 ` sashiko-bot [this message]
2026-10-16 15:57 ` [PATCH 14/16] KVM: selftests: Add VGICv5 ITS attribute tests Sascha Bischoff
2026-10-16 16:18 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 15/16] KVM: selftests: Add VGICv5 ITS translation cache tests Sascha Bischoff
2026-10-16 16:22 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 16/16] KVM: arm64: gic-v5: Document ITS userspace ABI Sascha Bischoff
2026-10-16 16:27 ` sashiko-bot
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=sashiko-outbox-174635@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=kvm@vger.kernel.org \
--cc=kvmarm@lists.linux.dev \
--cc=maz@kernel.org \
--cc=oupton@kernel.org \
--cc=sascha.bischoff@arm.com \
--cc=sashiko-reviews@lists.linux.dev \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox