Kernel KVM virtualization development
 help / color / mirror / Atom feed
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

  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