* [PATCH 0/2] KVM: Fix dangling IRQ bypass on x86 and arm64
@ 2026-01-13 17:46 Sean Christopherson
2026-01-13 17:46 ` [PATCH 1/2] KVM: Don't clobber irqfd routing type when deassigning irqfd Sean Christopherson
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Sean Christopherson @ 2026-01-13 17:46 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini
Cc: kvm, linux-kernel, Marc Zyngier, Oliver Upton
Fix three bugs in one, where KVM can incorrectly leave an IRQ configured
for bypass after the associated irqfd is deassigned from the VM (if the VMM
deassigns the irqfd while it's in bypass mode).
Two of the bugs are recent-ish, one each in x86 and arm64. The x86 bug is
the most visible/noisy as it leads to kernel panics on AMD due to SVM's use
of a per-CPU list to track IRQs/irqfds that are being posted to the vCPU.
The third bug has existed since IRQ bypass was added ~10 years ago. That
bug is much less likely as it requires hitting a race with a small window,
and likely requires the VMM to do some rather weird stuff on top.
The underlying issue of the x86 and arm64 bugs is that KVM clobbers the
irqfd's copy of the routing information prior to fully deassigning the irqfd,
which results in false negatives when kvm_arch_irq_bypass_del_producer()
checks for "irqfd->irq_entry.type == KVM_IRQ_ROUTING_MSI".
While the x86/arm64 bugs are fixable in arch code, e.g. by explicitly tracking
if an irqfd is in bypass mode (x86 already does this), leaving the routing
information in the irqfd as-is and instead checking if the irqfd is still
active prior to consuming its copy of the routing information fixes both the
arch bugs as well as the latent third bug.
The second patches hardens x86 against similar bugs in the future, by
essentially implementing the arch fix describes above, but wrapped in a WARN
so that bugs elsewhere are detected but less likely to be fatal.
The SVM bug was originally hit with Google's VMM, and confirmed via selftest
(which is also how I verified the fix). I'm not entirely sure why this hasn't
been hit with QEMU. My best guess is that QEMU always updates IRQ routing
when deassigning a VFIO IRQFD?
Sean Christopherson (2):
KVM: Don't clobber irqfd routing type when deassigning irqfd
KVM: x86: Assert that non-MSI doesn't have bypass vCPU when deleting
producer
arch/x86/kvm/irq.c | 3 ++-
virt/kvm/eventfd.c | 44 ++++++++++++++++++++++++--------------------
2 files changed, 26 insertions(+), 21 deletions(-)
base-commit: f62b64b970570c92fe22503b0cdc65be7ce7fc7c
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/2] KVM: Don't clobber irqfd routing type when deassigning irqfd
2026-01-13 17:46 [PATCH 0/2] KVM: Fix dangling IRQ bypass on x86 and arm64 Sean Christopherson
@ 2026-01-13 17:46 ` Sean Christopherson
2026-01-13 17:46 ` [PATCH 2/2] KVM: x86: Assert that non-MSI doesn't have bypass vCPU when deleting producer Sean Christopherson
2026-01-15 18:03 ` [PATCH 0/2] KVM: Fix dangling IRQ bypass on x86 and arm64 Sean Christopherson
2 siblings, 0 replies; 4+ messages in thread
From: Sean Christopherson @ 2026-01-13 17:46 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini
Cc: kvm, linux-kernel, Marc Zyngier, Oliver Upton
When deassigning a KVM_IRQFD, don't clobber the irqfd's copy of the IRQ's
routing entry as doing so breaks kvm_arch_irq_bypass_del_producer() on x86
and arm64, which explicitly look for KVM_IRQ_ROUTING_MSI. Instead, to
handle a concurrent routing update, verify that the irqfd is still active
before consuming the routing information. As evidenced by the x86 and
arm64 bugs, and another bug in kvm_arch_update_irqfd_routing() (see below),
clobbering the entry type without notifying arch code is surprising and
error prone.
As a bonus, checking that the irqfd is active provides a convenient
location for documenting _why_ KVM must not consume the routing entry for
an irqfd that is in the process of being deassigned: once the irqfd is
deleted from the list (which happens *before* the eventfd is detached), it
will no longer receive updates via kvm_irq_routing_update(), and so KVM
could deliver an event using stale routing information (relative to
KVM_SET_GSI_ROUTING returning to userspace).
As an even better bonus, explicitly checking for the irqfd being active
fixes a similar bug to the one the clobbering is trying to prevent: if an
irqfd is deactivated, and then its routing is changed,
kvm_irq_routing_update() won't invoke kvm_arch_update_irqfd_routing()
(because the irqfd isn't in the list). And so if the irqfd is in bypass
mode, IRQs will continue to be posted using the old routing information.
As for kvm_arch_irq_bypass_del_producer(), clobbering the routing type
results in KVM incorrectly keeping the IRQ in bypass mode, which is
especially problematic on AMD as KVM tracks IRQs that are being posted to
a vCPU in a list whose lifetime is tied to the irqfd.
Without the help of KASAN to detect use-after-free, the most common
sympton on AMD is a NULL pointer deref in amd_iommu_update_ga() due to
the memory for irqfd structure being re-allocated and zeroed, resulting
in irqfd->irq_bypass_data being NULL when read by
avic_update_iommu_vcpu_affinity():
BUG: kernel NULL pointer dereference, address: 0000000000000018
#PF: supervisor read access in kernel mode
#PF: error_code(0x0000) - not-present page
PGD 40cf2b9067 P4D 40cf2b9067 PUD 408362a067 PMD 0
Oops: Oops: 0000 [#1] SMP
CPU: 6 UID: 0 PID: 40383 Comm: vfio_irq_test
Tainted: G U W O 6.19.0-smp--5dddc257e6b2-irqfd #31 NONE
Tainted: [U]=USER, [W]=WARN, [O]=OOT_MODULE
Hardware name: Google, Inc. Arcadia_IT_80/Arcadia_IT_80, BIOS 34.78.2-0 09/05/2025
RIP: 0010:amd_iommu_update_ga+0x19/0xe0
Call Trace:
<TASK>
avic_update_iommu_vcpu_affinity+0x3d/0x90 [kvm_amd]
__avic_vcpu_load+0xf4/0x130 [kvm_amd]
kvm_arch_vcpu_load+0x89/0x210 [kvm]
vcpu_load+0x30/0x40 [kvm]
kvm_arch_vcpu_ioctl_run+0x45/0x620 [kvm]
kvm_vcpu_ioctl+0x571/0x6a0 [kvm]
__se_sys_ioctl+0x6d/0xb0
do_syscall_64+0x6f/0x9d0
entry_SYSCALL_64_after_hwframe+0x4b/0x53
RIP: 0033:0x46893b
</TASK>
---[ end trace 0000000000000000 ]---
If AVIC is inhibited when the irfd is deassigned, the bug will manifest as
list corruption, e.g. on the next irqfd assignment.
list_add corruption. next->prev should be prev (ffff8d474d5cd588),
but was 0000000000000000. (next=ffff8d8658f86530).
------------[ cut here ]------------
kernel BUG at lib/list_debug.c:31!
Oops: invalid opcode: 0000 [#1] SMP
CPU: 128 UID: 0 PID: 80818 Comm: vfio_irq_test
Tainted: G U W O 6.19.0-smp--f19dc4d680ba-irqfd #28 NONE
Tainted: [U]=USER, [W]=WARN, [O]=OOT_MODULE
Hardware name: Google, Inc. Arcadia_IT_80/Arcadia_IT_80, BIOS 34.78.2-0 09/05/2025
RIP: 0010:__list_add_valid_or_report+0x97/0xc0
Call Trace:
<TASK>
avic_pi_update_irte+0x28e/0x2b0 [kvm_amd]
kvm_pi_update_irte+0xbf/0x190 [kvm]
kvm_arch_irq_bypass_add_producer+0x72/0x90 [kvm]
irq_bypass_register_consumer+0xcd/0x170 [irqbypass]
kvm_irqfd+0x4c6/0x540 [kvm]
kvm_vm_ioctl+0x118/0x5d0 [kvm]
__se_sys_ioctl+0x6d/0xb0
do_syscall_64+0x6f/0x9d0
entry_SYSCALL_64_after_hwframe+0x4b/0x53
</TASK>
---[ end trace 0000000000000000 ]---
On Intel and arm64, the bug is less noisy, as the end result is that the
device keeps posting IRQs to the vCPU even after it's been deassigned.
Note, the worst of the breakage can be traced back to commit cb210737675e
("KVM: Pass new routing entries and irqfd when updating IRTEs"), as before
that commit KVM would pull the routing information from the per-VM routing
table. But as above, similar bugs have existed since support for IRQ
bypass was added. E.g. if a routing change finished before irq_shutdown()
invoked kvm_arch_irq_bypass_del_producer(), VMX and SVM would see stale
routing information and potentially leave the irqfd in bypass mode.
Alternatively, x86 could be fixed by explicitly checking irq_bypass_vcpu
instead of irq_entry.type in kvm_arch_irq_bypass_del_producer(), and arm64
could be modified to utilize irq_bypass_vcpu in a similar manner. But (a)
that wouldn't fix the routing updates bug, and (b) fixing core code doesn't
preclude x86 (or arm64) from adding such code as a sanity check (spoiler
alert).
Fixes: f70c20aaf141 ("KVM: Add an arch specific hooks in 'struct kvm_kernel_irqfd'")
Fixes: cb210737675e ("KVM: Pass new routing entries and irqfd when updating IRTEs")
Fixes: a0d7e2fc61ab ("KVM: arm64: vgic-v4: Only attempt vLPI mapping for actual MSIs")
Cc: stable@vger.kernel.org
Cc: Marc Zyngier <maz@kernel.org>
Cc: Oliver Upton <oupton@kernel.org>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
virt/kvm/eventfd.c | 44 ++++++++++++++++++++++++--------------------
1 file changed, 24 insertions(+), 20 deletions(-)
diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c
index 0e8b5277be3b..a369b20d47f0 100644
--- a/virt/kvm/eventfd.c
+++ b/virt/kvm/eventfd.c
@@ -157,21 +157,28 @@ irqfd_shutdown(struct work_struct *work)
}
-/* assumes kvm->irqfds.lock is held */
-static bool
-irqfd_is_active(struct kvm_kernel_irqfd *irqfd)
+static bool irqfd_is_active(struct kvm_kernel_irqfd *irqfd)
{
+ /*
+ * Assert that either irqfds.lock or SRCU is held, as irqfds.lock must
+ * be held to prevent false positives (on the irqfd being active), and
+ * while false negatives are impossible as irqfds are never added back
+ * to the list once they're deactivated, the caller must at least hold
+ * SRCU to guard against routing changes if the irqfd is deactivated.
+ */
+ lockdep_assert_once(lockdep_is_held(&irqfd->kvm->irqfds.lock) ||
+ srcu_read_lock_held(&irqfd->kvm->irq_srcu));
+
return list_empty(&irqfd->list) ? false : true;
}
/*
* Mark the irqfd as inactive and schedule it for removal
- *
- * assumes kvm->irqfds.lock is held
*/
-static void
-irqfd_deactivate(struct kvm_kernel_irqfd *irqfd)
+static void irqfd_deactivate(struct kvm_kernel_irqfd *irqfd)
{
+ lockdep_assert_held(&irqfd->kvm->irqfds.lock);
+
BUG_ON(!irqfd_is_active(irqfd));
list_del_init(&irqfd->list);
@@ -217,8 +224,15 @@ irqfd_wakeup(wait_queue_entry_t *wait, unsigned mode, int sync, void *key)
seq = read_seqcount_begin(&irqfd->irq_entry_sc);
irq = irqfd->irq_entry;
} while (read_seqcount_retry(&irqfd->irq_entry_sc, seq));
- /* An event has been signaled, inject an interrupt */
- if (kvm_arch_set_irq_inatomic(&irq, kvm,
+
+ /*
+ * An event has been signaled, inject an interrupt unless the
+ * irqfd is being deassigned (isn't active), in which case the
+ * routing information may be stale (once the irqfd is removed
+ * from the list, it will stop receiving routing updates).
+ */
+ if (unlikely(!irqfd_is_active(irqfd)) ||
+ kvm_arch_set_irq_inatomic(&irq, kvm,
KVM_USERSPACE_IRQ_SOURCE_ID, 1,
false) == -EWOULDBLOCK)
schedule_work(&irqfd->inject);
@@ -585,18 +599,8 @@ kvm_irqfd_deassign(struct kvm *kvm, struct kvm_irqfd *args)
spin_lock_irq(&kvm->irqfds.lock);
list_for_each_entry_safe(irqfd, tmp, &kvm->irqfds.items, list) {
- if (irqfd->eventfd == eventfd && irqfd->gsi == args->gsi) {
- /*
- * This clearing of irq_entry.type is needed for when
- * another thread calls kvm_irq_routing_update before
- * we flush workqueue below (we synchronize with
- * kvm_irq_routing_update using irqfds.lock).
- */
- write_seqcount_begin(&irqfd->irq_entry_sc);
- irqfd->irq_entry.type = 0;
- write_seqcount_end(&irqfd->irq_entry_sc);
+ if (irqfd->eventfd == eventfd && irqfd->gsi == args->gsi)
irqfd_deactivate(irqfd);
- }
}
spin_unlock_irq(&kvm->irqfds.lock);
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 2/2] KVM: x86: Assert that non-MSI doesn't have bypass vCPU when deleting producer
2026-01-13 17:46 [PATCH 0/2] KVM: Fix dangling IRQ bypass on x86 and arm64 Sean Christopherson
2026-01-13 17:46 ` [PATCH 1/2] KVM: Don't clobber irqfd routing type when deassigning irqfd Sean Christopherson
@ 2026-01-13 17:46 ` Sean Christopherson
2026-01-15 18:03 ` [PATCH 0/2] KVM: Fix dangling IRQ bypass on x86 and arm64 Sean Christopherson
2 siblings, 0 replies; 4+ messages in thread
From: Sean Christopherson @ 2026-01-13 17:46 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini
Cc: kvm, linux-kernel, Marc Zyngier, Oliver Upton
When disconnecting a non-MSI irqfd from an IRQ bypass producer, WARN if
the irqfd is configured for IRQ bypass and set its IRTE back to remapped
mode to harden against kernel/KVM bugs (keeping the irqfd in bypass mode
is often fatal to the host).
Deactivating an irqfd (removing it from the list of irqfds), updating
irqfd routes, and the code in question are all mutually exclusive (all
run under irqfds.lock). If an irqfd is configured for bypass, and the
irqfd is deassigned at the same time IRQ routing is updated (to change the
routing to non-MSI), then either kvm_arch_update_irqfd_routing() should
process the irqfd routing change and put the IRTE into remapped mode
(routing update "wins"), or kvm_arch_irq_bypass_del_producer() should see
the MSI routing info (deactivation "wins").
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/irq.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/irq.c b/arch/x86/kvm/irq.c
index a52115441c07..9519fec09ee6 100644
--- a/arch/x86/kvm/irq.c
+++ b/arch/x86/kvm/irq.c
@@ -514,7 +514,8 @@ void kvm_arch_irq_bypass_del_producer(struct irq_bypass_consumer *cons,
*/
spin_lock_irq(&kvm->irqfds.lock);
- if (irqfd->irq_entry.type == KVM_IRQ_ROUTING_MSI) {
+ if (irqfd->irq_entry.type == KVM_IRQ_ROUTING_MSI ||
+ WARN_ON_ONCE(irqfd->irq_bypass_vcpu)) {
ret = kvm_pi_update_irte(irqfd, NULL);
if (ret)
pr_info("irq bypass consumer (eventfd %p) unregistration fails: %d\n",
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 0/2] KVM: Fix dangling IRQ bypass on x86 and arm64
2026-01-13 17:46 [PATCH 0/2] KVM: Fix dangling IRQ bypass on x86 and arm64 Sean Christopherson
2026-01-13 17:46 ` [PATCH 1/2] KVM: Don't clobber irqfd routing type when deassigning irqfd Sean Christopherson
2026-01-13 17:46 ` [PATCH 2/2] KVM: x86: Assert that non-MSI doesn't have bypass vCPU when deleting producer Sean Christopherson
@ 2026-01-15 18:03 ` Sean Christopherson
2 siblings, 0 replies; 4+ messages in thread
From: Sean Christopherson @ 2026-01-15 18:03 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini
Cc: kvm, linux-kernel, Marc Zyngier, Oliver Upton
On Tue, 13 Jan 2026 09:46:04 -0800, Sean Christopherson wrote:
> Fix three bugs in one, where KVM can incorrectly leave an IRQ configured
> for bypass after the associated irqfd is deassigned from the VM (if the VMM
> deassigns the irqfd while it's in bypass mode).
>
> Two of the bugs are recent-ish, one each in x86 and arm64. The x86 bug is
> the most visible/noisy as it leads to kernel panics on AMD due to SVM's use
> of a per-CPU list to track IRQs/irqfds that are being posted to the vCPU.
>
> [...]
Applied rather quickly to kvm-x86 fixes, so that these can get as much time in
-next as possible. I'll wait until next week to send a pull request (hooray
for -rc8), and these are sitting at the top of the branch so I can amend (or
drop) them as needed.
[1/2] KVM: Don't clobber irqfd routing type when deassigning irqfd
https://github.com/kvm-x86/linux/commit/b4d37cdb77a0
[2/2] KVM: x86: Assert that non-MSI doesn't have bypass vCPU when deleting producer
https://github.com/kvm-x86/linux/commit/ef3719e33e66
--
https://github.com/kvm-x86/linux/tree/next
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-01-15 18:05 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-13 17:46 [PATCH 0/2] KVM: Fix dangling IRQ bypass on x86 and arm64 Sean Christopherson
2026-01-13 17:46 ` [PATCH 1/2] KVM: Don't clobber irqfd routing type when deassigning irqfd Sean Christopherson
2026-01-13 17:46 ` [PATCH 2/2] KVM: x86: Assert that non-MSI doesn't have bypass vCPU when deleting producer Sean Christopherson
2026-01-15 18:03 ` [PATCH 0/2] KVM: Fix dangling IRQ bypass on x86 and arm64 Sean Christopherson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®