A resampler-bound irqfd is only meaningful for level-triggered interrupts,
because the resampler signals userspace on guest EOI and edge-triggered
interrupts have no EOI to react to. mshv_irqfd_assign() already rejects
the combination at registration time, but nothing prevented a subsequent
MSHV_SET_MSI_ROUTING ioctl from flipping the cached
lapic_control.level_triggered bit to edge-triggered while the resampler was
still attached. Once that happened, the WARN_ON() in mshv_assert_irq_slow()
was the only signal of an inconsistency the kernel had no way to recover
from: the resampler stayed wired in but no EOI ever arrived, so userspace
never saw a resample signal and the guest interrupt could become stuck.
Add mshv_irqfd_validate_routing() and call it from
mshv_update_routing_table() before publishing the new table. It walks
pt_irqfds_list looking for resampler-bound irqfds whose new routing entry
would be valid but not level-triggered, and returns -EINVAL to reject the
routing change atomically. The check is x86-only because
lapic_control.level_triggered is only populated on x86; on ARM64
mshv_copy_girq_info() unconditionally sets asserted = 1 and the invariant
does not apply.
The validator briefly takes pt_irqfds_lock for the list walk and drops it
before mshv_irqfd_routing_update() reacquires it. This is safe because the
partition ioctl dispatcher holds pt_mutex for the duration of every
partition ioctl, so MSHV_IRQFD (which calls mshv_irqfd_assign()) cannot run
concurrently with MSHV_SET_MSI_ROUTING; no new resampler-bound irqfd can be
inserted between validation and refresh.
Fixes: 621191d709b14 ("Drivers: hv: Introduce mshv_root module to expose
/dev/mshv to VMMs")
Signed-off-by: Stanislav Kinsburskii <[email protected]>
---
drivers/hv/mshv_irq.c | 44 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 43 insertions(+), 1 deletion(-)
diff --git a/drivers/hv/mshv_irq.c b/drivers/hv/mshv_irq.c
index b3142c84dcbc2..59584a132ca9f 100644
--- a/drivers/hv/mshv_irq.c
+++ b/drivers/hv/mshv_irq.c
@@ -14,7 +14,44 @@
#include "mshv.h"
#include "mshv_root.h"
-/* called from the ioctl code, user wants to update the guest irq table */
+static int mshv_irqfd_validate_routing(struct mshv_partition *pt,
+ struct mshv_girq_routing_table *new)
+{
+ int r = 0;
+#if IS_ENABLED(CONFIG_X86)
+ struct mshv_irqfd *irqfd;
+
+ if (!new)
+ return 0;
+
+ spin_lock_irq(&pt->pt_irqfds_lock);
+ hlist_for_each_entry(irqfd, &pt->pt_irqfds_list, irqfd_hnode) {
+ struct mshv_guest_irq_ent ent = {};
+ struct mshv_lapic_irq lirq;
+
+ if (!irqfd->irqfd_resampler)
+ continue;
+
+ if (irqfd->irqfd_irqnum < new->num_rt_entries)
+ ent = new->mshv_girq_info_tbl[irqfd->irqfd_irqnum];
+
+ mshv_copy_girq_info(&ent, &lirq);
+
+ if (ent.girq_entry_valid &&
+ !lirq.lapic_control.level_triggered) {
+ r = -EINVAL;
+ break;
+ }
+ }
+ spin_unlock_irq(&pt->pt_irqfds_lock);
+#endif
+ return r;
+}
+
+/*
+ * Called from the ioctl code, user wants to update the guest irq table.
+ * Serialized with mshv_irqfd_assign by partition mutex.
+ */
int mshv_update_routing_table(struct mshv_partition *partition,
const struct mshv_user_irq_entry *ue,
unsigned int numents)
@@ -65,6 +102,11 @@ int mshv_update_routing_table(struct mshv_partition
*partition,
swap_routes:
mutex_lock(&partition->pt_irq_lock);
+ r = mshv_irqfd_validate_routing(partition, new);
+ if (r) {
+ mutex_unlock(&partition->pt_irq_lock);
+ goto out;
+ }
old = rcu_dereference_protected(partition->pt_girq_tbl, 1);
rcu_assign_pointer(partition->pt_girq_tbl, new);
mshv_irqfd_routing_update(partition);