From 75d276e5bb68778b2916f98a2bc30f142ebadc64 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Mon, 17 Aug 2026 22:32:29 +0000 Subject: [PATCH 01/27] virtio_ring: fix stale descriptor flags after a failed packed add In a packed ring the AVAIL and USED bits sit in the descriptor itself, so writing them makes that descriptor available. Those bit combinations flip meaning on every round of the ring, tracked by a wrap counter, so invalidating or validating a descriptor means inverting both bits. Commit 1ce9e6055fa0 ("virtio_ring: introduce packed ring support") has virtqueue_add_packed() make every descriptor of a chain available as it maps the chain, and write the head last. The device consumes the ring in order and stops at a head that is not available yet, so it never reaches the rest. When vring_map_one_sg() fails partway, unmap_release unmaps the segments and restores avail_used_flags, but the descriptors it wrote to in the ring stay marked with AVAIL and USED bits. The head is now the only entry that keeps the device from consuming these stale entries. For example, the ring would look like this now. Z - pre-previous command A - previous command B - aborted command C - current command [A1 DONE] [A2 DONE] [B2] [B3] [Z1 DONE] When the driver now attempts to issue the C command, the next add starts at the same head as B. If C spans less descriptors than B, there is no end marker because AVAIL and USED bits were still in place. And that means the device will start interpreting these stale entries (B2/B3) as another command entry, which then blocks the queue. This effect typically happens in swiotlb configurations under memory pressure, because vring_map_one_sg() can then fail with larger I/O requests which then leads to command abortions. There are broadly 2 ways to avoid leaving those flags behind: 1) Defer those flags too until the chain is complete. 2) Rewrite those flags for the previous wrap counter. Implement the second option in both packed add paths. The first option traverses the chain a second time on every successful add. The second option invalidates all added descriptors when any add fails. With this patch applied, a packed virtqueue keeps completing requests after a failed add. Fixes: 1ce9e6055fa0 ("virtio_ring: introduce packed ring support") Fixes: f6a15d854986 ("virtio_ring: add in order support") Assisted-by: Kiro:claude-opus-5 checkpatch sparse Signed-off-by: Alexander Graf Signed-off-by: Michael S. Tsirkin Message-ID: <20260817223229.28954-1-graf@amazon.com> --- drivers/virtio/virtio_ring.c | 38 ++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index 5c169fbb418a..db678f5a80e0 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -1670,7 +1670,7 @@ static inline int virtqueue_add_packed(struct vring_virtqueue *vq, struct scatterlist *sg; unsigned int i, n, c, descs_used, err_idx, len; __le16 head_flags, flags; - u16 head, id, prev, curr, avail_used_flags; + u16 head, id, prev, curr, avail_used_flags, unpub_flags; int err; START_USE(vq); @@ -1798,15 +1798,30 @@ static inline int virtqueue_add_packed(struct vring_virtqueue *vq, curr = vq->free_head; vq->packed.avail_used_flags = avail_used_flags; + unpub_flags = avail_used_flags ^ (1 << VRING_PACKED_DESC_F_AVAIL | + 1 << VRING_PACKED_DESC_F_USED); for (n = 0; n < total_sg; n++) { if (i == err_idx) break; + /* + * The mapping loop made every descriptor but the head + * available. Stamp the previous wrap counter's AVAIL and USED + * bits on those, so that a later and shorter chain at this head + * does not leave one of them available beyond its own last + * descriptor. Marking them used instead would hand + * is_used_desc_packed() a completion we never made. + */ + if (i != head) + desc[i].flags = cpu_to_le16(unpub_flags); vring_unmap_extra_packed(vq, &vq->packed.desc_extra[curr]); curr = vq->packed.desc_extra[curr].next; i++; - if (i >= vq->packed.vring.num) + if (i >= vq->packed.vring.num) { i = 0; + unpub_flags ^= 1 << VRING_PACKED_DESC_F_AVAIL | + 1 << VRING_PACKED_DESC_F_USED; + } } END_USE(vq); @@ -1828,7 +1843,7 @@ static inline int virtqueue_add_packed_in_order(struct vring_virtqueue *vq, struct scatterlist *sg; unsigned int i, n, sg_count, err_idx, total_in_len = 0; __le16 head_flags, flags; - u16 head, avail_used_flags; + u16 head, avail_used_flags, unpub_flags; bool avail_wrap_counter; int err; @@ -1955,14 +1970,29 @@ static inline int virtqueue_add_packed_in_order(struct vring_virtqueue *vq, i = head; vq->packed.avail_used_flags = avail_used_flags; vq->packed.avail_wrap_counter = avail_wrap_counter; + unpub_flags = avail_used_flags ^ (1 << VRING_PACKED_DESC_F_AVAIL | + 1 << VRING_PACKED_DESC_F_USED); for (n = 0; n < total_sg; n++) { if (i == err_idx) break; + /* + * The mapping loop made every descriptor but the head + * available. Stamp the previous wrap counter's AVAIL and USED + * bits on those, so that a later and shorter chain at this head + * does not leave one of them available beyond its own last + * descriptor. Marking them used instead would hand + * is_used_desc_packed() a completion we never made. + */ + if (i != head) + desc[i].flags = cpu_to_le16(unpub_flags); vring_unmap_extra_packed(vq, &vq->packed.desc_extra[i]); i++; - if (i >= vq->packed.vring.num) + if (i >= vq->packed.vring.num) { i = 0; + unpub_flags ^= 1 << VRING_PACKED_DESC_F_AVAIL | + 1 << VRING_PACKED_DESC_F_USED; + } } END_USE(vq); From 3f9a0fceb730f5107d52421ead5568eae25a0049 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 21 Aug 2026 23:39:53 +0200 Subject: [PATCH 02/27] virtio: fix use-after-free in unregister_virtio_device() device_unregister() is device_del() plus put_device(). When the caller holds no extra reference, that drops the last one and runs the release callback, which for several transports frees the memory the embedded struct virtio_device sits in. unregister_virtio_device() then calls virtio_debug_device_exit(), which reads dev->debugfs_dir out of the freed object. Affected transports are the ones whose release callback frees and whose remove path takes no reference: virtio_mmio, virtio_vdpa, virtio_uml, mlxbf-tmfifo and virtio_ccw. virtio_pci is unaffected because virtio_pci_remove() brackets the call with get_device() and put_device(). Remove the debugfs entries before the device can go away. They are only accessed through the protected debugfs interface, so debugfs_remove_recursive() waits for in-progress file operations before returning. Tearing them down while the device is still alive is therefore safe. Reproduced on User-Mode Linux with CONFIG_KASAN and CONFIG_VIRTIO_DEBUG by unbinding a virtio-uml device: BUG: KASAN: slab-use-after-free in virtio_debug_device_exit+0x36/0x4d Read of size 8 at addr 00000000616e0b10 by task init/1 __asan_report_load8_noabort virtio_debug_device_exit+0x36/0x4d unregister_virtio_device+0x48/0x75 virtio_uml_remove platform_remove device_release_driver_internal unbind_store Freed by task 1: kfree virtio_uml_release_dev device_release kobject_put put_device device_unregister With this applied, the report is gone and unbind is clean. Fixes: 96a8326d69ff ("virtio: add debugfs infrastructure to allow to debug virtio features") Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260821213953.76906-1-kmehltretter@gmail.com> --- drivers/virtio/virtio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/virtio/virtio.c b/drivers/virtio/virtio.c index 75bb4ffe3b87..b6c9e927bef5 100644 --- a/drivers/virtio/virtio.c +++ b/drivers/virtio/virtio.c @@ -604,8 +604,8 @@ void unregister_virtio_device(struct virtio_device *dev) { int index = dev->index; /* save for after device release */ - device_unregister(&dev->dev); virtio_debug_device_exit(dev); + device_unregister(&dev->dev); ida_free(&virtio_index_ida, index); } EXPORT_SYMBOL_GPL(unregister_virtio_device); From 894f98e73983f37354214a89a3a7fd35bf9e3072 Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Wed, 19 Aug 2026 10:12:30 +0800 Subject: [PATCH 03/27] virtio_console: do not free control-out buffers on remove __send_control_msg() publishes &portdev->cpkt as the control-out virtqueue cookie. remove_vqs() walks every virtqueue and passes leftover cookies to free_buf(), which treats them as struct port_buffer and reads sgpages. If a control message is still on c_ovq when the device is unbound, free_buf() reads past the ports_device object. KASAN reported slab-out-of-bounds in free_buf(): free_buf remove_vqs virtcons_remove unbind_store The object was the ports_device allocated in virtcons_probe(). Drain c_ovq without freeing. The packet lives in portdev and is released with it. Fixes: a7a69ec0d8e4 ("virtio_console: free buffers after reset") Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260819021230.292696-1-physicalmtea@gmail.com> --- drivers/char/virtio_console.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 7f6cbe851d1e..019bcae81af5 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -1964,13 +1964,28 @@ static const struct file_operations portdev_fops = { static void remove_vqs(struct ports_device *portdev) { struct virtqueue *vq; + bool multiport = use_multiport(portdev); virtio_device_for_each_vq(portdev->vdev, vq) { struct port_buffer *buf; + unsigned int len; - flush_bufs(vq, true); - while ((buf = virtqueue_detach_unused_buf(vq))) - free_buf(buf, true); + /* + * c_ovq cookies are &portdev->cpkt, not port_buffer. + * Detach them but do not free_buf(). + */ + if (multiport && vq == portdev->c_ovq) { + spin_lock(&portdev->c_ovq_lock); + while (virtqueue_get_buf(vq, &len)) + ; + while (virtqueue_detach_unused_buf(vq)) + ; + spin_unlock(&portdev->c_ovq_lock); + } else { + flush_bufs(vq, true); + while ((buf = virtqueue_detach_unused_buf(vq))) + free_buf(buf, true); + } cond_resched(); } portdev->vdev->config->del_vqs(portdev->vdev); From ccb1dc7c527f8c925925cf92afc76ae590dac311 Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Mon, 10 Aug 2026 09:03:00 +0800 Subject: [PATCH 04/27] vhost/vdpa: reject VRING_NUM larger than device max vhost_vring_set_num() accepts any non-zero power-of-two queue size that fits in 16 bits. vhost-vdpa then passes that value to set_vq_num() without comparing it with get_vq_num_max(). A process with access to /dev/vhost-vdpa-* can therefore configure a queue larger than the device advertises. With vdpa_sim, the worker can walk descriptors beyond the mapped descriptor ring. KASAN reports a 16-byte out-of-bounds read, corresponding to one vring_desc, in the vringh IOTLB path: BUG: KASAN: out-of-bounds in _copy_from_iter Read of size 16 copy_from_iotlb copydesc_iotlb vringh_getdesc_iotlb vdpasim_net_work Cache get_vq_num_max() immediately after reset. Some backends derive it from writable queue-size state, so querying it after SET_NUM may return the current size instead of the device capability. Invalidate the cached value before reset so a failed reset leaves SET_NUM disabled. For VHOST_SET_VRING_NUM, copy the complete vring state once and use the same index and size for validation, vq->num, and set_vq_num(). This ensures that validation and use operate on the same copied values. Fixes: 4c8cf31885f6 ("vhost: introduce vDPA-based backend") Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260810010300.132959-1-physicalmtea@gmail.com> --- drivers/vhost/vdpa.c | 44 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index c3d913bd7cac..4eb1eb5e5c79 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -58,6 +58,7 @@ struct vhost_vdpa { struct cdev cdev; atomic_t opened; u32 nvqs; + u16 vq_num_max; int virtio_id; int minor; struct eventfd_ctx *config_ctx; @@ -236,7 +237,9 @@ static void vhost_vdpa_unsetup_vq_irq(struct vhost_vdpa *v, u16 qid) static int _compat_vdpa_reset(struct vhost_vdpa *v) { struct vdpa_device *vdpa = v->vdpa; + const struct vdpa_config_ops *ops = vdpa->config; u32 flags = 0; + int ret; v->suspended = false; @@ -246,7 +249,14 @@ static int _compat_vdpa_reset(struct vhost_vdpa *v) VDPA_RESET_F_CLEAN_MAP : 0; } - return vdpa_reset(vdpa, flags); + v->vq_num_max = 0; + ret = vdpa_reset(vdpa, flags); + if (!ret) { + /* Some backends derive the max from mutable queue state. */ + v->vq_num_max = ops->get_vq_num_max(vdpa); + } + + return ret; } static int vhost_vdpa_reset(struct vhost_vdpa *v) @@ -648,9 +658,15 @@ static long vhost_vdpa_vring_ioctl(struct vhost_vdpa *v, unsigned int cmd, u32 idx; long r; - r = get_user(idx, (u32 __user *)argp); - if (r < 0) - return r; + if (cmd == VHOST_SET_VRING_NUM) { + if (copy_from_user(&s, argp, sizeof(s))) + return -EFAULT; + idx = s.index; + } else { + r = get_user(idx, (u32 __user *)argp); + if (r < 0) + return r; + } if (idx >= v->nvqs) return -ENOBUFS; @@ -659,6 +675,23 @@ static long vhost_vdpa_vring_ioctl(struct vhost_vdpa *v, unsigned int cmd, vq = &v->vqs[idx]; switch (cmd) { + case VHOST_SET_VRING_NUM: + mutex_lock(&vq->mutex); + if (vq->private_data) { + r = -EBUSY; + } else if (!s.num || s.num > 0xffff || + s.num > v->vq_num_max || + (s.num & (s.num - 1))) { + r = -EINVAL; + } else { + vq->num = s.num; + r = 0; + } + mutex_unlock(&vq->mutex); + if (r) + return r; + ops->set_vq_num(vdpa, idx, s.num); + return 0; case VHOST_VDPA_SET_VRING_ENABLE: if (copy_from_user(&s, argp, sizeof(s))) return -EFAULT; @@ -772,9 +805,6 @@ static long vhost_vdpa_vring_ioctl(struct vhost_vdpa *v, unsigned int cmd, ops->set_vq_cb(vdpa, idx, &cb); break; - case VHOST_SET_VRING_NUM: - ops->set_vq_num(vdpa, idx, vq->num); - break; } return r; From e74a9fa50749b9940b4fb13199652325e08d3c4a Mon Sep 17 00:00:00 2001 From: Yu Zhang Date: Fri, 7 Aug 2026 20:00:24 +1000 Subject: [PATCH 05/27] vhost-vdpa: don't install the eventfd_ctx_fdget() error in config_ctx vhost_vdpa_set_config_call() swaps the eventfd_ctx_fdget() return value into v->config_ctx before checking it, so on failure the field briefly holds an ERR_PTR: ctx = fd == VHOST_FILE_UNBIND ? NULL : eventfd_ctx_fdget(fd); swap(ctx, v->config_ctx); if (!IS_ERR_OR_NULL(ctx)) eventfd_ctx_put(ctx); if (IS_ERR(v->config_ctx)) { long ret = PTR_ERR(v->config_ctx); v->config_ctx = NULL; return ret; } Commit 0bde59c1723a ("vhost-vdpa: set v->config_ctx to NULL if eventfd_ctx_fdget() fails") added that clearing, and spelled out the invariant the rest of the file relies on: "we consider 'v->config_ctx' valid if it is not NULL". The window between the swap and the clearing still breaks it. vhost_vdpa_config_cb() only tests for NULL, so a config interrupt delivered inside the window hands the ERR_PTR to eventfd_signal(). Check the fd before installing it instead. That closes the window and matches how vhost_vring_ioctl() handles the same failure for the vq call fd. It also stops a rejected fd from tearing down a config interrupt that was working: until now the swap replaced the live context and put it, so after an EBADF the device silently stopped delivering config interrupts until userspace installed a new fd. Fixes: 776f395004d8 ("vhost_vdpa: Support config interrupt in vdpa") Signed-off-by: Yu Zhang Signed-off-by: Michael S. Tsirkin Message-ID: <20260807100025.19750-2-yuz08559@gmail.com> --- drivers/vhost/vdpa.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index 4eb1eb5e5c79..3e5165b7c094 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -546,18 +546,14 @@ static long vhost_vdpa_set_config_call(struct vhost_vdpa *v, u32 __user *argp) return -EFAULT; ctx = fd == VHOST_FILE_UNBIND ? NULL : eventfd_ctx_fdget(fd); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + swap(ctx, v->config_ctx); - if (!IS_ERR_OR_NULL(ctx)) + if (ctx) eventfd_ctx_put(ctx); - if (IS_ERR(v->config_ctx)) { - long ret = PTR_ERR(v->config_ctx); - - v->config_ctx = NULL; - return ret; - } - v->vdpa->config->set_config_cb(v->vdpa, &cb); return 0; From 62be4e3e5f5f947fbf765b914cebdc478f715d12 Mon Sep 17 00:00:00 2001 From: Yu Zhang Date: Fri, 7 Aug 2026 20:00:25 +1000 Subject: [PATCH 06/27] vhost-vdpa: protect config_ctx from being freed under the config callback vhost_vdpa_config_cb() loads v->config_ctx and signals it without taking a reference and without holding any lock: struct eventfd_ctx *config_ctx = v->config_ctx; if (config_ctx) eventfd_signal(config_ctx); VHOST_VDPA_SET_CONFIG_CALL replaces that field and drops what is normally the last reference to the old context: swap(ctx, v->config_ctx); if (ctx) eventfd_ctx_put(ctx); eventfd_ctx_put() drops the last kref and frees the context immediately, with no RCU grace period, so a callback that has already loaded the pointer goes on to dereference freed memory. The two sides share no lock: the ioctl runs under vhost_dev.mutex, while the parent invokes the callback from its own interrupt or workqueue context. This is not the reopen refcount underflow fixed by commit f6bbf0010ba0 ("vhost-vdpa: fix use-after-free of v->config_ctx"), which was about vhost_vdpa_config_put() leaving a stale pointer behind. Here the pointer is maintained correctly and it is the read side that is unprotected. With VDUSE as the parent this is reachable from userspace with access to /dev/vduse (root by default). VDUSE_DEV_INJECT_CONFIG_IRQ queues dev->inject, and vduse_dev_irq_inject() runs the callback under VDUSE's own dev->irq_lock, which vhost does not hold. vduse_dev_reset() does flush_work(&dev->inject), but VHOST_VDPA_SET_CONFIG_CALL never goes through reset, so an inject already in flight is not waited for. A process that injects config interrupts on the VDUSE fd while another thread swaps the call fd on the vhost-vdpa fd hits it in seconds: BUG: KASAN: slab-use-after-free in native_queued_spin_lock_slowpath Read of size 4 at addr ffff888107d21808 by task kworker/u17:1/2993 Workqueue: vduse-irq vduse_dev_irq_inject Call Trace: native_queued_spin_lock_slowpath+0x97/0x5b0 _raw_spin_lock_irqsave+0xd4/0xe0 eventfd_signal_mask+0x69/0x120 vhost_vdpa_config_cb+0x34/0x50 vduse_dev_irq_inject+0x46/0x60 process_one_work+0x468/0x950 Allocated by task 2992: do_eventfd+0x50/0x200 __x64_sys_eventfd2+0x2e/0x40 Freed by task 2992: eventfd_ctx_put+0xb9/0xc0 vhost_vdpa_unlocked_ioctl+0x116c/0x2190 Add a spinlock covering every access to config_ctx, so the callback either signals a context that is still alive or observes NULL, and the put happens only once no callback can reach the old value. Clearing the parent's callback before the put would not be enough: of the in-tree set_config_cb() implementations only VDUSE takes a lock, the rest store the pointer unlocked, so that would not order against an in-flight invocation. Fixes: 776f395004d8 ("vhost_vdpa: Support config interrupt in vdpa") Signed-off-by: Yu Zhang Signed-off-by: Michael S. Tsirkin Message-ID: <20260807100025.19750-3-yuz08559@gmail.com> --- drivers/vhost/vdpa.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index 3e5165b7c094..a31786796d4c 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -62,6 +62,8 @@ struct vhost_vdpa { int virtio_id; int minor; struct eventfd_ctx *config_ctx; + /* Serialises vhost_vdpa_config_cb() against config_ctx being replaced. */ + spinlock_t config_lock; int in_batch; struct vdpa_iova_range range; u32 batch_asid; @@ -195,10 +197,12 @@ static irqreturn_t vhost_vdpa_virtqueue_cb(void *private) static irqreturn_t vhost_vdpa_config_cb(void *private) { struct vhost_vdpa *v = private; - struct eventfd_ctx *config_ctx = v->config_ctx; + unsigned long flags; - if (config_ctx) - eventfd_signal(config_ctx); + spin_lock_irqsave(&v->config_lock, flags); + if (v->config_ctx) + eventfd_signal(v->config_ctx); + spin_unlock_irqrestore(&v->config_lock, flags); return IRQ_HANDLED; } @@ -528,15 +532,22 @@ static long vhost_vdpa_get_vring_num(struct vhost_vdpa *v, u16 __user *argp) static void vhost_vdpa_config_put(struct vhost_vdpa *v) { - if (v->config_ctx) { - eventfd_ctx_put(v->config_ctx); - v->config_ctx = NULL; - } + struct eventfd_ctx *ctx; + unsigned long flags; + + spin_lock_irqsave(&v->config_lock, flags); + ctx = v->config_ctx; + v->config_ctx = NULL; + spin_unlock_irqrestore(&v->config_lock, flags); + + if (ctx) + eventfd_ctx_put(ctx); } static long vhost_vdpa_set_config_call(struct vhost_vdpa *v, u32 __user *argp) { struct vdpa_callback cb; + unsigned long flags; int fd; struct eventfd_ctx *ctx; @@ -549,8 +560,14 @@ static long vhost_vdpa_set_config_call(struct vhost_vdpa *v, u32 __user *argp) if (IS_ERR(ctx)) return PTR_ERR(ctx); + spin_lock_irqsave(&v->config_lock, flags); swap(ctx, v->config_ctx); + spin_unlock_irqrestore(&v->config_lock, flags); + /* + * The callback can no longer reach the old context, so this is the + * last reference to it. + */ if (ctx) eventfd_ctx_put(ctx); @@ -1639,6 +1656,7 @@ static int vhost_vdpa_probe(struct vdpa_device *vdpa) } atomic_set(&v->opened, 0); + spin_lock_init(&v->config_lock); v->minor = minor; v->vdpa = vdpa; v->nvqs = vdpa->nvqs; From d14d693adb055e98ca705822ba6daebc18602d9a Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Wed, 5 Aug 2026 11:29:37 +0800 Subject: [PATCH 07/27] virtio_mmio: disable IRQ wake before free_irq When the DT node has "wakeup-source", vm_find_vqs() calls enable_irq_wake() on the shared IRQ, but vm_del_vqs() freed that IRQ without a matching disable_irq_wake(). That leaves a wake reference behind and can warn on later free_irq()/request_irq() cycles. Record whether enable_irq_wake() succeeded, and disable it in vm_del_vqs() before free_irq(). Fixes: 02213273f72a ("virtio_mmio: add support to set IRQ of a virtio device as wakeup source") Cc: stable@vger.kernel.org Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260805032937.1606737-1-xiongweimin@kylinos.cn> --- drivers/virtio/virtio_mmio.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c index 316f03b97356..faae58e3401a 100644 --- a/drivers/virtio/virtio_mmio.c +++ b/drivers/virtio/virtio_mmio.c @@ -88,6 +88,9 @@ struct virtio_mmio_device { void __iomem *base; unsigned long version; + + /* True if enable_irq_wake() succeeded for the shared IRQ. */ + bool wake_irq_enabled; }; /* Configuration interface */ @@ -336,11 +339,17 @@ static void vm_del_vqs(struct virtio_device *vdev) { struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev); struct virtqueue *vq, *n; + int irq = platform_get_irq(vm_dev->pdev, 0); list_for_each_entry_safe(vq, n, &vdev->vqs, list) vm_del_vq(vq); - free_irq(platform_get_irq(vm_dev->pdev, 0), vm_dev); + if (vm_dev->wake_irq_enabled) { + disable_irq_wake(irq); + vm_dev->wake_irq_enabled = false; + } + + free_irq(irq, vm_dev); } static void vm_synchronize_cbs(struct virtio_device *vdev) @@ -467,8 +476,9 @@ static int vm_find_vqs(struct virtio_device *vdev, unsigned int nvqs, if (err) return err; - if (of_property_read_bool(vm_dev->pdev->dev.of_node, "wakeup-source")) - enable_irq_wake(irq); + if (of_property_read_bool(vm_dev->pdev->dev.of_node, "wakeup-source") && + !enable_irq_wake(irq)) + vm_dev->wake_irq_enabled = true; for (i = 0; i < nvqs; ++i) { struct virtqueue_info *vqi = &vqs_info[i]; From 6601d5a00899e7fa7e6b2d18113cee385ed3801b Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Thu, 6 Aug 2026 08:58:09 +0800 Subject: [PATCH 08/27] vdpa/pds: check virtqueue notify mapping vp_modern_map_vq_notify() can fail and return NULL. Check the notify mapping while adding a pds vDPA device and use the existing teardown path instead of storing a NULL doorbell pointer in the virtqueue state. Signed-off-by: Xiong Weimin Reviewed-by: Brett Creeley Signed-off-by: Michael S. Tsirkin Message-ID: <20260806005809.1875257-1-xiongweimin@kylinos.cn> --- drivers/vdpa/pds/vdpa_dev.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/vdpa/pds/vdpa_dev.c b/drivers/vdpa/pds/vdpa_dev.c index 43426bd971ac..77d679f6763d 100644 --- a/drivers/vdpa/pds/vdpa_dev.c +++ b/drivers/vdpa/pds/vdpa_dev.c @@ -731,6 +731,12 @@ static int pds_vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name, notify = vp_modern_map_vq_notify(&pdsv->vdpa_aux->vd_mdev, i, &pdsv->vqs[i].notify_pa); + if (!notify) { + err = -EINVAL; + dev_err(dev, "Fail to map vq notify %d\n", i); + goto err_unmap; + } + pds_vdpa_init_vqs_entry(pdsv, i, notify); } From 9ab9b4f4eb4288588707ec359ac3d5b7ccf07fa6 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Tue, 4 Aug 2026 17:26:07 +0800 Subject: [PATCH 09/27] vdpa: alibaba: Keep DRIVER_OK clear if IRQ setup fails If requesting MSI-X interrupts fails while DRIVER_OK is being set, leave the device status unchanged instead of advertising a ready device without working interrupts. Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260804092608.1344269-1-xiongweimin@kylinos.cn> --- drivers/vdpa/alibaba/eni_vdpa.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/alibaba/eni_vdpa.c b/drivers/vdpa/alibaba/eni_vdpa.c index fd6fdba46094..1288402d3dd8 100644 --- a/drivers/vdpa/alibaba/eni_vdpa.c +++ b/drivers/vdpa/alibaba/eni_vdpa.c @@ -216,7 +216,10 @@ static void eni_vdpa_set_status(struct vdpa_device *vdpa, u8 status) if (status & VIRTIO_CONFIG_S_DRIVER_OK && !(s & VIRTIO_CONFIG_S_DRIVER_OK)) { - eni_vdpa_request_irq(eni_vdpa); + if (eni_vdpa_request_irq(eni_vdpa)) { + WARN_ON(1); + return; + } } vp_legacy_set_status(ldev, status); From e847542ab0545c73354849126150206c29d83929 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Wed, 5 Aug 2026 09:51:53 +0800 Subject: [PATCH 10/27] vdpa: solidrun: Free IRQs after request failure Unwind IRQs already requested by snet_request_irqs() before returning a VQ IRQ request error so a later DRIVER_OK retry starts from a clean state. The IRQs are requested and freed while the PCI device remains bound, so the driver cannot wait for devres cleanup at detach time. Fixes: 51a8f9d7f587 ("virtio: vdpa: new SolidNET DPU driver.") Cc: stable@vger.kernel.org # v6.3+ Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <178589471328.1556376.15570536900532373521@kylinos.cn> --- drivers/vdpa/solidrun/snet_main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/solidrun/snet_main.c b/drivers/vdpa/solidrun/snet_main.c index 28d55315df2a..3e2cea1e45f3 100644 --- a/drivers/vdpa/solidrun/snet_main.c +++ b/drivers/vdpa/solidrun/snet_main.c @@ -418,11 +418,15 @@ static int snet_request_irqs(struct pci_dev *pdev, struct snet *snet) snet->vqs[i]->irq_name, snet->vqs[i]); if (ret) { SNET_ERR(pdev, "Failed to request IRQ\n"); - return ret; + goto err_free_irqs; } snet->vqs[i]->irq = irq; } return 0; + +err_free_irqs: + snet_free_irqs(snet); + return ret; } static void snet_set_status(struct vdpa_device *vdev, u8 status) From 4d470be71196ca0ce302e6623454533dc31b465b Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Wed, 5 Aug 2026 09:51:52 +0800 Subject: [PATCH 11/27] vdpa: ifcvf: Put device on unsupported feature error Route unsupported provisioned features through the common error path after vdpa_alloc_device() so the allocated device and adapter pointer are released consistently. Fixes: 46fc0917bbab ("vDPA/ifcvf: implement features provisioning") Cc: stable@vger.kernel.org # v6.3+ Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <178589471294.1556376.4816776800128323034@kylinos.cn> --- drivers/vdpa/ifcvf/ifcvf_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/ifcvf/ifcvf_main.c b/drivers/vdpa/ifcvf/ifcvf_main.c index ab6d6ab3b3d8..2af1cec95884 100644 --- a/drivers/vdpa/ifcvf/ifcvf_main.c +++ b/drivers/vdpa/ifcvf/ifcvf_main.c @@ -724,7 +724,8 @@ static int ifcvf_vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name, if (config->device_features & ~device_features) { IFCVF_ERR(pdev, "The provisioned features 0x%llx are not supported by this device with features 0x%llx\n", config->device_features, device_features); - return -EINVAL; + ret = -EINVAL; + goto err; } device_features &= config->device_features; } From 6519ca235131c3281a83cc9e8b05af709ab98a89 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Tue, 4 Aug 2026 17:26:36 +0800 Subject: [PATCH 12/27] vdpa: octeon_ep: Check dev_set_name() in dev add Handle dev_set_name() failures before registering the vDPA device so allocation is unwound through the existing put_device() path. Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260804092636.1344431-1-xiongweimin@kylinos.cn> --- drivers/vdpa/octeon_ep/octep_vdpa_main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/vdpa/octeon_ep/octep_vdpa_main.c b/drivers/vdpa/octeon_ep/octep_vdpa_main.c index 23e280a29209..85a3d35ea1e4 100644 --- a/drivers/vdpa/octeon_ep/octep_vdpa_main.c +++ b/drivers/vdpa/octeon_ep/octep_vdpa_main.c @@ -600,6 +600,8 @@ static int octep_vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name, ret = dev_set_name(&vdpa_dev->dev, "%s", name); else ret = dev_set_name(&vdpa_dev->dev, "vdpa%u", vdpa_dev->index); + if (ret) + goto vdpa_dev_put; ret = _vdpa_register_device(&oct_vdpa->vdpa, oct_hw->nr_vring); if (ret) { From ca2c2165a02e499b591a367224346a7e52664d9c Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Tue, 4 Aug 2026 17:26:49 +0800 Subject: [PATCH 13/27] virtio-vdpa: Use queue id when setting vq affinity When optional queues are skipped, pass the compressed vDPA queue id to set_vq_affinity() so affinity is applied to the queue that was actually created. Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260804092649.1344478-1-xiongweimin@kylinos.cn> --- drivers/virtio/virtio_vdpa.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/virtio/virtio_vdpa.c b/drivers/virtio/virtio_vdpa.c index de2af696de6c..6bcf4567a929 100644 --- a/drivers/virtio/virtio_vdpa.c +++ b/drivers/virtio/virtio_vdpa.c @@ -352,7 +352,7 @@ static int virtio_vdpa_find_vqs(struct virtio_device *vdev, unsigned int nvqs, continue; } - vqs[i] = virtio_vdpa_setup_vq(vdev, queue_idx++, vqi->callback, + vqs[i] = virtio_vdpa_setup_vq(vdev, queue_idx, vqi->callback, vqi->name, vqi->ctx); if (IS_ERR(vqs[i])) { err = PTR_ERR(vqs[i]); @@ -360,7 +360,8 @@ static int virtio_vdpa_find_vqs(struct virtio_device *vdev, unsigned int nvqs, } if (has_affinity) - ops->set_vq_affinity(vdpa, i, &masks[i]); + ops->set_vq_affinity(vdpa, queue_idx, &masks[i]); + queue_idx++; } cb.callback = virtio_vdpa_config_cb; From 0a8693f00c408d85f086ad85d29e7030bf1e2055 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Tue, 1 Sep 2026 17:48:00 +0800 Subject: [PATCH 14/27] vdpa_sim_blk: reject out-of-range sector starts vdpasim_blk_check_range() logs an invalid start sector but continues validating the request. The subsequent unsigned capacity subtraction can underflow and let an out-of-range buffer offset reach the data path. The invalid offset is used by three request paths. VIRTIO_BLK_T_OUT copies guest data to blk->buffer + offset through vringh_iov_pull_iotlb(), causing an out-of-bounds write in _copy_from_iter() or memcpy(). VIRTIO_BLK_T_IN copies from blk->buffer + offset to the guest through vringh_iov_push_iotlb(), causing an out-of-bounds read in _copy_to_iter(). VIRTIO_BLK_T_WRITE_ZEROES passes blk->buffer + offset to memset(), causing an out-of-bounds write. Reject starts at or beyond the capacity before the subtraction. Treat the capacity boundary as invalid because the IN and OUT paths round byte counts down to sectors for validation but later copy the original byte counts. A sub-sector request at the capacity boundary would otherwise still access past the end of the buffer. I found this bug myself, though the patch was written with AI assistance. Fixes: 7d189f617f83 ("vdpa_sim_blk: implement ramdisk behaviour") Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260901094800.25475-1-linfeng.sun.dev@gmail.com> --- drivers/vdpa/vdpa_sim/vdpa_sim_blk.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim_blk.c b/drivers/vdpa/vdpa_sim/vdpa_sim_blk.c index f70f454dde8e..76dd5b0828d7 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim_blk.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim_blk.c @@ -79,10 +79,11 @@ static void vdpasim_blk_buffer_unlock(struct vdpasim_blk *blk) static bool vdpasim_blk_check_range(struct vdpasim *vdpasim, u64 start_sector, u64 num_sectors, u64 max_sectors) { - if (start_sector > VDPASIM_BLK_CAPACITY) { + if (start_sector >= VDPASIM_BLK_CAPACITY) { dev_dbg(&vdpasim->vdpa.dev, "starting sector exceeds the capacity - start: 0x%llx capacity: 0x%x\n", start_sector, VDPASIM_BLK_CAPACITY); + return false; } if (num_sectors > max_sectors) { From 0d195797a80b77f2ec56718cd26d3ee65d0093e8 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Tue, 1 Sep 2026 17:48:42 +0800 Subject: [PATCH 15/27] vdpa_sim_net: check TX pull result before RX copy vringh_iov_pull_iotlb() returns a signed byte count. A failed TX pull is currently added to the unsigned byte counter and then passed as a size_t length to receive_filter() and vringh_iov_push_iotlb(). A negative error can therefore become a large length in the RX path. Handle non-positive pull results before every length use. Count the TX error and complete the consumed TX descriptor with zero bytes. I found this bug myself, though the patch was written with AI assistance. Fixes: cfe226892913 ("vdpa_sim: filter destination mac address") Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260901094842.25875-1-linfeng.sun.dev@gmail.com> --- drivers/vdpa/vdpa_sim/vdpa_sim_net.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim_net.c b/drivers/vdpa/vdpa_sim/vdpa_sim_net.c index 29fd14ce5860..a6514b5ccd86 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim_net.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim_net.c @@ -225,10 +225,15 @@ static void vdpasim_net_work(struct vdpasim *vdpasim) break; } - ++tx_pkts; read = vringh_iov_pull_iotlb(&txq->vring, &txq->out_iov, net->buffer, PAGE_SIZE); + if (read <= 0) { + ++tx_errors; + vdpasim_net_complete(txq, 0); + continue; + } + ++tx_pkts; tx_bytes += read; if (!receive_filter(vdpasim, read)) { From 7034e6c8dadaf4a2c95669890095ebafa8d9cee7 Mon Sep 17 00:00:00 2001 From: Laurent Vivier Date: Tue, 18 Aug 2026 15:39:13 +0200 Subject: [PATCH 16/27] MAINTAINERS: Add a section for virtio-rng At Michael's request, add a MAINTAINERS entry for the virtio-rng driver and list myself as its maintainer. I already maintain the corresponding QEMU implementation. Cc: Michael S. Tsirkin Signed-off-by: Laurent Vivier Signed-off-by: Michael S. Tsirkin Message-ID: <20260818133913.162471-1-lvivier@redhat.com> --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb07770..35ddf814de94 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29017,6 +29017,13 @@ S: Maintained F: drivers/nvdimm/nd_virtio.c F: drivers/nvdimm/virtio_pmem.c +VIRTIO RNG DRIVER +M: Laurent Vivier +L: virtualization@lists.linux.dev +S: Maintained +F: drivers/char/hw_random/virtio-rng.c +F: include/uapi/linux/virtio_rng.h + VIRTIO RTC DRIVER M: Peter Hilber L: virtualization@lists.linux.dev From 84cd1f879968ae75da15c25de4cb390428e89e6d Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Thu, 3 Sep 2026 12:13:33 +0800 Subject: [PATCH 17/27] vhost: limit outstanding IOTLB misses per virtqueue vhost allocates a message node whenever address translation misses. If userspace reads these messages without resolving them, repeated virtqueue kicks can grow the pending message list until the host runs out of memory. Virtqueue processing stops at the first translation miss and cannot make progress until userspace installs a mapping. Keep a pointer to that outstanding message in the virtqueue and suppress additional misses until the node is resolved or discarded. The pointer remains set while the message is queued for reading, copied to userspace, or waiting on the pending list. Clear it under the IOTLB lock when the owning node is freed. This bounds outstanding miss messages by the fixed number of virtqueues without introducing an arbitrary queue limit. Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260903-fix-kernel-panic-in-vhost_iotlb_miss_pending_list-v1-1-39b8cd427978@gmail.com> --- drivers/vhost/vhost.c | 38 +++++++++++++++++++++++++++++++++----- drivers/vhost/vhost.h | 3 +++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 14637cff0bd4..02588b64b1bb 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -392,6 +392,7 @@ static void vhost_vq_reset(struct vhost_dev *dev, vq->busyloop_timeout = 0; vq->umem = NULL; vq->iotlb = NULL; + vq->iotlb_miss = NULL; rcu_assign_pointer(vq->worker, NULL); vhost_vring_call_reset(&vq->call_ctx); __vhost_vq_meta_reset(vq); @@ -1180,6 +1181,21 @@ void vhost_dev_stop(struct vhost_dev *dev) } EXPORT_SYMBOL_GPL(vhost_dev_stop); +static void vhost_free_msg_locked(struct vhost_msg_node *node) +{ + if (node->vq->iotlb_miss == node) + node->vq->iotlb_miss = NULL; + kfree(node); +} + +static void vhost_free_msg(struct vhost_dev *dev, + struct vhost_msg_node *node) +{ + spin_lock(&dev->iotlb_lock); + vhost_free_msg_locked(node); + spin_unlock(&dev->iotlb_lock); +} + void vhost_clear_msg(struct vhost_dev *dev) { struct vhost_msg_node *node, *n; @@ -1188,12 +1204,12 @@ void vhost_clear_msg(struct vhost_dev *dev) list_for_each_entry_safe(node, n, &dev->read_list, node) { list_del(&node->node); - kfree(node); + vhost_free_msg_locked(node); } list_for_each_entry_safe(node, n, &dev->pending_list, node) { list_del(&node->node); - kfree(node); + vhost_free_msg_locked(node); } spin_unlock(&dev->iotlb_lock); @@ -1602,7 +1618,7 @@ static void vhost_iotlb_notify_vq(struct vhost_dev *d, vq_msg->type == VHOST_IOTLB_MISS) { vhost_poll_queue(&node->vq->poll); list_del(&node->node); - kfree(node); + vhost_free_msg_locked(node); } } @@ -1816,7 +1832,7 @@ ssize_t vhost_chr_read_iter(struct vhost_dev *dev, struct iov_iter *to, ret = copy_to_iter(start, size, to); if (ret != size || msg->type != VHOST_IOTLB_MISS) { - kfree(node); + vhost_free_msg(dev, node); return ret; } vhost_enqueue_msg(dev, &dev->pending_list, node); @@ -1848,7 +1864,19 @@ static int vhost_iotlb_miss(struct vhost_virtqueue *vq, u64 iova, int access) msg->iova = iova; msg->perm = access; - vhost_enqueue_msg(dev, &dev->read_list, node); + spin_lock(&dev->iotlb_lock); + /* VQ processing stops at the first miss until userspace resolves it. */ + if (vq->iotlb_miss) { + spin_unlock(&dev->iotlb_lock); + kfree(node); + return 0; + } + + vq->iotlb_miss = node; + list_add_tail(&node->node, &dev->read_list); + spin_unlock(&dev->iotlb_lock); + + wake_up_interruptible_poll(&dev->wait, EPOLLIN | EPOLLRDNORM); return 0; } diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index 0192ade6e749..fa76b7d44662 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -29,6 +29,7 @@ struct vhost_work { struct vhost_worker; struct vhost_dev; +struct vhost_msg_node; struct vhost_worker_ops { int (*create)(struct vhost_worker *worker, struct vhost_dev *dev, @@ -148,6 +149,8 @@ struct vhost_virtqueue { /* Protected by virtqueue mutex. */ struct vhost_iotlb *umem; struct vhost_iotlb *iotlb; + /* Protected by dev->iotlb_lock. */ + struct vhost_msg_node *iotlb_miss; void *private_data; VIRTIO_DECLARE_FEATURES(acked_features); u64 acked_backend_features; From 8dd505a45de0e63829d8bb4116b1d66db64813be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Thu, 3 Sep 2026 10:18:31 +0200 Subject: [PATCH 18/27] =?UTF-8?q?virtio:=20add=20Eugenio=20P=C3=A9rez=20as?= =?UTF-8?q?=20Maintainer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eugenio Pérez Reviewed-by: Stefano Garzarella Signed-off-by: Michael S. Tsirkin Message-ID: <20260903081831.2129729-1-eperezma@redhat.com> --- MAINTAINERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 35ddf814de94..fdbd46fce4b3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -28867,8 +28867,8 @@ F: include/uapi/linux/virtio_console.h VIRTIO CORE M: "Michael S. Tsirkin" M: Jason Wang +M: Eugenio Pérez R: Xuan Zhuo -R: Eugenio Pérez L: virtualization@lists.linux.dev S: Maintained F: Documentation/ABI/testing/sysfs-bus-vdpa @@ -28945,7 +28945,7 @@ F: include/uapi/linux/virtio_gpu.h VIRTIO HOST (VHOST) M: "Michael S. Tsirkin" M: Jason Wang -R: Eugenio Pérez +M: Eugenio Pérez L: kvm@vger.kernel.org L: virtualization@lists.linux.dev L: netdev@vger.kernel.org @@ -29000,8 +29000,8 @@ F: include/uapi/linux/virtio_mem.h VIRTIO NET DRIVER M: "Michael S. Tsirkin" M: Jason Wang +M: Eugenio Pérez R: Xuan Zhuo -R: Eugenio Pérez L: netdev@vger.kernel.org L: virtualization@lists.linux.dev S: Maintained From 93fa09455fb1a9624b73d42ac1f83771f4818e80 Mon Sep 17 00:00:00 2001 From: Andrew Stellman Date: Fri, 4 Sep 2026 10:13:18 -0400 Subject: [PATCH 19/27] virtio-pci: return IRQ_HANDLED after non-zero ISR vp_interrupt() reads the ISR before dispatching config-change and vring handling. Reading the ISR also clears it, so once the read returns non-zero the interrupt was from this device and has already been consumed. Currently vp_interrupt() returns the result of vp_vring_interrupt(). For a config-change interrupt with no vring work, that can return IRQ_NONE even though the ISR was non-zero and the interrupt was handled. Call vp_vring_interrupt() for any queue work, but once the ISR is non-zero return IRQ_HANDLED. Tested with QEMU virtio-blk-pci forced to INTx using vectors=0 and pci=nomsi. On an idle device, 200 config-change interrupts were generated using QMP block_resize. Before this change, irq_handler_exit reported ret=unhandled and /proc/irq/11/spurious increased from 0 to 200 unhandled interrupts. After this change, irq_handler_exit reported ret=handled and the unhandled count remained at 0. The issue was found during an LLM-assisted Quality Playbook review. Fixes: 77cf524654a8 ("virtio_pci: split up vp_interrupt") Suggested-by: Michael S. Tsirkin Assisted-by: LLM Signed-off-by: Andrew Stellman Message-ID: <20260904141318.30278-1-astellman@stellman-greene.com> Signed-off-by: Michael S. Tsirkin --- drivers/virtio/virtio_pci_common.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c index 10371ecbc054..b90c174450b2 100644 --- a/drivers/virtio/virtio_pci_common.c +++ b/drivers/virtio/virtio_pci_common.c @@ -120,7 +120,9 @@ static irqreturn_t vp_interrupt(int irq, void *opaque) if (isr & VIRTIO_PCI_ISR_CONFIG) vp_config_changed(irq, opaque); - return vp_vring_interrupt(irq, opaque); + vp_vring_interrupt(irq, opaque); + + return IRQ_HANDLED; } static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors, From c952e607cb4aa3640e5ae07243d3f609dac94424 Mon Sep 17 00:00:00 2001 From: Dongli Zhang Date: Sun, 2 Aug 2026 10:24:55 -0700 Subject: [PATCH 20/27] vhost-scsi: use kvzalloc for vq array allocation vhost_scsi_open() allocates one "struct vhost_scsi_virtqueue" for each virtqueue. With large max_io_vqs values, this array can require a high-order contiguous allocation and trigger a page allocator warning. hv# cat /sys/module/vhost_scsi/parameters/max_io_vqs 256 [ 766.075787] ------------[ cut here ]------------ [ 766.077030] WARNING: mm/page_alloc.c:5280 at __alloc_frozen_pages_noprof+0x32c/0x15c0, CPU#23: qemu-system-x86/5964 ... ... [ 766.080351] RIP: 0010:__alloc_frozen_pages_noprof+0x32c/0x15c0 ... ... [ 766.085813] Call Trace: [ 766.085969] [ 766.086098] ? srso_alias_return_thunk+0x5/0xfbef5 [ 766.086365] ? context_struct_compute_av+0x38a/0x4b0 [ 766.086652] alloc_pages_mpol+0x9f/0x170 [ 766.086883] ___kmalloc_large_node+0xb6/0xd0 [ 766.087124] ? srso_alias_return_thunk+0x5/0xfbef5 [ 766.087389] __kmalloc_large_node_noprof+0x18/0xa0 [ 766.087655] __kmalloc_noprof+0x3a0/0x440 [ 766.087877] ? vhost_scsi_open+0xcb/0x2d0 [vhost_scsi] [ 766.088162] vhost_scsi_open+0xcb/0x2d0 [vhost_scsi] [ 766.088449] misc_open+0x123/0x160 [ 766.088679] chrdev_open+0xb1/0x230 [ 766.088885] ? __pfx_chrdev_open+0x10/0x10 [ 766.089157] do_dentry_open+0x11a/0x470 [ 766.089389] vfs_open+0x29/0xf0 [ 766.089596] path_openat+0x7c0/0x1100 [ 766.089821] do_file_open+0xdd/0x190 [ 766.090032] ? srso_alias_return_thunk+0x5/0xfbef5 [ 766.090332] do_sys_openat2+0x7e/0x100 [ 766.090601] __x64_sys_openat+0x51/0xa0 [ 766.090857] do_syscall_64+0xfe/0x590 [ 766.091087] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 766.091411] RIP: 0033:0x7f9525a11fa6 The array does not require physical contiguity, so allocate it with kvzalloc_objs() and free it with kvfree(). Signed-off-by: Dongli Zhang Reviewed-by: Mike Christie Reviewed-by: Stefan Hajnoczi Signed-off-by: Michael S. Tsirkin Message-ID: <20260802172534.260047-2-dongli.zhang@oracle.com> --- drivers/vhost/scsi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 7a1f39a327da..223549313ce9 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -2312,7 +2312,7 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) if (!vs->old_inflight) goto err_inflight; - vs->vqs = kmalloc_objs(*vs->vqs, nvqs, GFP_KERNEL | __GFP_ZERO); + vs->vqs = kvzalloc_objs(*vs->vqs, nvqs); if (!vs->vqs) goto err_vqs; @@ -2348,7 +2348,7 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) return 0; err_local_vqs: - kfree(vs->vqs); + kvfree(vs->vqs); err_vqs: kfree(vs->old_inflight); err_inflight: @@ -2369,7 +2369,7 @@ static int vhost_scsi_release(struct inode *inode, struct file *f) vhost_dev_stop(&vs->dev); vhost_dev_cleanup(&vs->dev); kfree(vs->dev.vqs); - kfree(vs->vqs); + kvfree(vs->vqs); kfree(vs->old_inflight); kvfree(vs); return 0; From 4e3ec5b1b427e02082e8b3491731f8c3bcf85c53 Mon Sep 17 00:00:00 2001 From: Dongli Zhang Date: Sun, 2 Aug 2026 10:24:56 -0700 Subject: [PATCH 21/27] vhost-scsi: clamp max_io_vqs module parameter max_io_vqs is currently validated only when a vhost-scsi device is opened. This allows sysfs to show values larger than the driver will actually use, e.g. writing 2048 succeeds even though vhost_scsi_open() later clamps it to VHOST_SCSI_MAX_IO_VQ. This makes the sysfs value differ from the value that will actually be used. hv# echo 2048 > /sys/module/vhost_scsi/parameters/max_io_vqs hv# cat /sys/module/vhost_scsi/parameters/max_io_vqs 2048 [ 315.630495] Invalid max_io_vqs of 2048. Using 1024. Keep accepting out-of-range values for compatibility, but clamp them in the module parameter setter and store the effective value. This preserves the existing behavior that invalid values do not make module loading or sysfs writes fail. It also makes reads report the value that will actually be used. With the parameter value kept in range, remove the duplicate validation from vhost_scsi_open(). Signed-off-by: Dongli Zhang Reviewed-by: Mike Christie Reviewed-by: Stefan Hajnoczi Signed-off-by: Michael S. Tsirkin Message-ID: <20260802172534.260047-3-dongli.zhang@oracle.com> --- drivers/vhost/scsi.c | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 223549313ce9..4f8c0260bc9e 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -210,7 +210,37 @@ static const int vhost_scsi_bits[] = { #define VHOST_SCSI_MAX_EVENT 128 static unsigned vhost_scsi_max_io_vqs = 128; -module_param_named(max_io_vqs, vhost_scsi_max_io_vqs, uint, 0644); + +static int vhost_scsi_set_max_io_vqs(const char *val, + const struct kernel_param *kp) +{ + unsigned int max_io_vqs; + int ret; + + ret = kstrtouint(val, 0, &max_io_vqs); + if (ret) + return ret; + + if (max_io_vqs > VHOST_SCSI_MAX_IO_VQ) { + pr_err("Invalid max_io_vqs of %u. Using %u.\n", + max_io_vqs, VHOST_SCSI_MAX_IO_VQ); + max_io_vqs = VHOST_SCSI_MAX_IO_VQ; + } else if (!max_io_vqs) { + pr_err("Invalid max_io_vqs of 0. Using 1.\n"); + max_io_vqs = 1; + } + + WRITE_ONCE(vhost_scsi_max_io_vqs, max_io_vqs); + return 0; +} + +static const struct kernel_param_ops vhost_scsi_max_io_vqs_op = { + .set = vhost_scsi_set_max_io_vqs, + .get = param_get_uint, +}; + +module_param_cb(max_io_vqs, &vhost_scsi_max_io_vqs_op, + &vhost_scsi_max_io_vqs, 0644); MODULE_PARM_DESC(max_io_vqs, "Set the max number of IO virtqueues a vhost scsi device can support. The default is 128. The max is 1024."); struct vhost_scsi_virtqueue { @@ -2290,21 +2320,14 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) struct vhost_scsi_virtqueue *svq; struct vhost_scsi *vs; struct vhost_virtqueue **vqs; - int r = -ENOMEM, i, nvqs = vhost_scsi_max_io_vqs; + int r = -ENOMEM, i, nvqs; vs = kvzalloc_obj(*vs); if (!vs) goto err_vs; vs->inline_sg_cnt = vhost_scsi_inline_sg_cnt; - if (nvqs > VHOST_SCSI_MAX_IO_VQ) { - pr_err("Invalid max_io_vqs of %d. Using %d.\n", nvqs, - VHOST_SCSI_MAX_IO_VQ); - nvqs = VHOST_SCSI_MAX_IO_VQ; - } else if (nvqs == 0) { - pr_err("Invalid max_io_vqs of %d. Using 1.\n", nvqs); - nvqs = 1; - } + nvqs = READ_ONCE(vhost_scsi_max_io_vqs); nvqs += VHOST_SCSI_VQ_IO; vs->old_inflight = kmalloc_objs(*vs->old_inflight, nvqs, From 7474f3a61043934e9c351febc56f4d85cd5ddc96 Mon Sep 17 00:00:00 2001 From: Nikhil Date: Sun, 30 Aug 2026 04:24:57 +0530 Subject: [PATCH 22/27] vduse: do not take dev->rwsem in the virtqueue kick path vduse_vq_kick() runs in the context of the vdpa .kick_vq callback. With the virtio_vdpa bus driver that callback is invoked by virtqueue_notify() from the virtio device driver, which may be an atomic context: virtio-blk kicks from ->queue_rq(), which blk-mq dispatches under rcu_read_lock() (the tag set does not use BLK_MQ_F_BLOCKING), and virtio-net kicks from its xmit path with the tx queue lock held. Commit b282418bc366 ("vduse: Add suspend") made vduse_vq_kick() take dev->rwsem for reading in order to check dev->suspended. down_read() may sleep, so with CONFIG_DEBUG_ATOMIC_SLEEP the first I/O on a VDUSE-backed virtio-blk device bound to virtio_vdpa now triggers: BUG: sleeping function called from invalid context at kernel/locking/rwsem.c:1573 in_atomic(): 0, irqs_disabled(): 0, non_block: 0, pid: 27, name: kworker/1:0H preempt_count: 0, expected: 0 RCU nest depth: 1, expected: 0 3 locks held by kworker/1:0H/27: #0: ((wq_completion)kblockd){+.+.}-{0:0}, at: process_one_work+0xac7/0xcf0 #1: ((work_completion)(&(&hctx->run_work)->work)){+.+.}-{0:0}, at: process_one_work+0x51f/0xcf0 #2: (rcu_read_lock){....}-{1:3}, at: blk_mq_run_work_fn+0x119/0x220 Workqueue: kblockd blk_mq_run_work_fn Call Trace: dump_stack_lvl+0x80/0xa0 __might_resched+0x231/0x370 down_read+0x73/0x330 vduse_vq_kick+0x30/0x120 virtio_vdpa_notify+0x63/0x80 virtqueue_notify+0x45/0x70 virtio_queue_rq+0x19d/0x300 blk_mq_dispatch_rq_list+0x269/0xe20 __blk_mq_sched_dispatch_requests+0x761/0xa60 blk_mq_sched_dispatch_requests+0x6b/0xc0 blk_mq_run_work_fn+0x143/0x220 process_one_work+0x581/0xcf0 worker_thread+0x2fc/0x5a0 kthread+0x1cc/0x210 ret_from_fork+0x3c4/0x540 ret_from_fork_asm+0x1a/0x30 Without CONFIG_DEBUG_ATOMIC_SLEEP, a kick that finds the rwsem write-locked by vduse_dev_reset() or vduse_vdpa_suspend() blocks inside an RCU read-side critical section. The vhost_vdpa path kicks from the vhost worker, i.e. process context, which is why this went unnoticed. Check dev->suspended under vq->kick_lock instead, which the kick path already takes, and have vduse_vdpa_suspend() cycle every virtqueue's kick_lock after setting the flag. A kick that observed suspended == false has thus finished signalling before suspend returns, which is the guarantee the rwsem used to provide. The flag is now also read outside the rwsem, so access it with READ_ONCE()/WRITE_ONCE(). Fixes: b282418bc366 ("vduse: Add suspend") Signed-off-by: Nikhil Signed-off-by: Michael S. Tsirkin Message-ID: <20260829225457.1037867-1-nikhilljatt@gmail.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 9891cd2cf712..766789a7bbfa 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -506,7 +506,7 @@ static void vduse_dev_reset(struct vduse_dev *dev) } scoped_guard(rwsem_write, &dev->rwsem) { - dev->suspended = false; + WRITE_ONCE(dev->suspended, false); dev->status = 0; dev->driver_features = 0; dev->generation++; @@ -567,11 +567,17 @@ static int vduse_vdpa_set_vq_address(struct vdpa_device *vdpa, u16 idx, static void vduse_vq_kick(struct vduse_virtqueue *vq) { - guard(rwsem_read)(&vq->dev->rwsem); - if (vq->dev->suspended) + /* + * This runs in the context of the vdpa kick_vq op, which may be + * atomic (e.g. virtio-blk kicks from blk-mq dispatch under + * rcu_read_lock()), so dev->rwsem must not be taken here. + * dev->suspended is checked under kick_lock instead and + * vduse_vdpa_suspend() cycles every kick_lock after setting it. + */ + guard(spinlock)(&vq->kick_lock); + if (READ_ONCE(vq->dev->suspended)) return; - guard(spinlock)(&vq->kick_lock); scoped_guard(spinlock_bh, &vq->ready_lock) if (!vq->ready) return; @@ -946,7 +952,17 @@ static int vduse_vdpa_suspend(struct vdpa_device *vdpa) ret = vduse_dev_msg_sync(dev, &msg); if (ret == 0) { scoped_guard(rwsem_write, &dev->rwsem) - dev->suspended = true; + WRITE_ONCE(dev->suspended, true); + + /* + * Kicks check dev->suspended under kick_lock without taking + * the rwsem: cycle each kick_lock so that no kick that has + * already passed the check is still in flight after this. + */ + for (u32 i = 0; i < dev->vq_num; i++) { + spin_lock(&dev->vqs[i]->kick_lock); + spin_unlock(&dev->vqs[i]->kick_lock); + } cancel_work_sync(&dev->inject); for (u32 i = 0; i < dev->vq_num; i++) From fa2c25b4add57888acfa89e398389e267bff3dcf Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Sun, 30 Aug 2026 10:33:54 +0800 Subject: [PATCH 23/27] vduse: validate virtqueue alignment vduse_validate_config() only checks the upper bound of vq_align. Invalid values can therefore reach vring_create_virtqueue_map(). The split-ring helpers use align - 1 as a bit mask, so the alignment must be a non-zero power of two. A zero value makes vring_size() drop the descriptor and available-ring part and vring_init() leave the used ring pointer NULL. The VIRTIO spec requires the used ring to start at an address aligned to at least 4 bytes. Reject values below VRING_USED_ALIGN_SIZE as well as non-power-of-two values before they reach the virtio ring helpers. Opening a virtio-net device created with vq_align=0 triggered: BUG: KASAN: null-ptr-deref in virtqueue_kick_prepare_split+0xe3/0x100 Read of size 2 at addr 0000000000000000 by task systemd-network/1062 Call Trace (relevant frames): dump_stack_lvl print_report kasan_report __asan_load2 virtqueue_kick_prepare_split+0xe3/0x100 virtqueue_kick_prepare+0x40/0x60 try_fill_recv+0x857/0x1250 virtnet_open+0x189/0x460 __dev_open+0x225/0x390 __dev_change_flags+0x368/0x3b0 netif_change_flags+0x56/0xc0 do_setlink.isra.0+0x68c/0x1e30 Validate the value before it reaches the virtio ring helpers. Fixes: c8a6153b6c59 ("vduse: Introduce VDUSE - vDPA Device in Userspace") Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260830023354.115333-1-physicalmtea@gmail.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 766789a7bbfa..4dea4d6a3855 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -2227,7 +2227,9 @@ static bool vduse_validate_config(struct vduse_dev_config *config, return false; } - if (config->vq_align > PAGE_SIZE) + if (config->vq_align < VRING_USED_ALIGN_SIZE || + !is_power_of_2(config->vq_align) || + config->vq_align > PAGE_SIZE) return false; if (config->config_size > PAGE_SIZE) From e4f4761879a230aa59e569102a6ab9851847d833 Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Fri, 28 Aug 2026 16:57:21 +0800 Subject: [PATCH 24/27] vhost: invalidate vring access on IOTLB transitions When VIRTIO_F_ACCESS_PLATFORM changes, cached vring pointers and IOTLB metadata are interpreted in a different address space. Keeping them across the transition can leave stale ring mappings in use. Clearing d->iotlb before taking the VQ locks also lets a worker observe a transient NULL d->iotlb and fall back to d->umem while translating a descriptor. Add a common vhost_clear_device_iotlb() helper for vhost-net and vhost-vsock. Take all VQ mutexes in index order before dropping the device-wide IOTLB, invalidate each VQ's cached ring access and metadata, clear pending IOTLB messages, and free the old table after the handoff. This serializes the transition with workers and prevents mixed address space mappings. On the first direct-to-IOTLB transition, invalidate the cached vring addresses. When an existing device IOTLB is replaced, preserve the GIOVA ring addresses and reset only the metadata cache. After clearing ACCESS_PLATFORM, userspace must configure the vring addresses for the new address mode. vhost_vq_invalidate_access() clears desc, avail, and used together. Treat the VQ as invalidated only when all three are NULL, since a single GIOVA address may legitimately be zero. Fixes: 6b1e6cc7855b ("vhost: new device IOTLB API") Fixes: e13a6915a03f ("vhost/vsock: add IOTLB API support") Suggested-by: Michael S. Tsirkin Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260828085721.57816-1-physicalmtea@gmail.com> --- drivers/vhost/net.c | 2 ++ drivers/vhost/vhost.c | 57 ++++++++++++++++++++++++++++++++++++++++++- drivers/vhost/vhost.h | 1 + drivers/vhost/vsock.c | 2 ++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index c25929dd4425..2cc730729e08 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -1705,6 +1705,8 @@ static int vhost_net_set_features(struct vhost_net *n, const u64 *features) if (virtio_features_test_bit(features, VIRTIO_F_ACCESS_PLATFORM)) { if (vhost_init_device_iotlb(&n->dev)) goto out_unlock; + } else { + vhost_clear_device_iotlb(&n->dev); } for (i = 0; i < VHOST_NET_VQ_MAX; ++i) { diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 02588b64b1bb..44cac11b68d2 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -344,6 +344,17 @@ static void __vhost_vq_meta_reset(struct vhost_virtqueue *vq) vq->meta_iotlb[j] = NULL; } +/* Caller must hold the virtqueue mutex. */ +static void vhost_vq_invalidate_access(struct vhost_virtqueue *vq) +{ + vq->desc = NULL; + vq->avail = NULL; + vq->used = NULL; + vq->log_used = false; + vq->log_addr = -1ull; + __vhost_vq_meta_reset(vq); +} + static void vhost_vq_meta_reset(struct vhost_dev *d) { int i; @@ -1946,6 +1957,13 @@ int vq_meta_prefetch(struct vhost_virtqueue *vq) { unsigned int num = vq->num; + /* + * vhost_vq_invalidate_access() clears all three addresses together. + * A single zero address may be a valid GIOVA in IOTLB mode. + */ + if (!vq->desc && !vq->avail && !vq->used) + return 0; + if (!vq->iotlb) return 1; @@ -2315,6 +2333,40 @@ long vhost_vring_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *arg } EXPORT_SYMBOL_GPL(vhost_vring_ioctl); +/* Caller must hold the device mutex. */ +void vhost_clear_device_iotlb(struct vhost_dev *d) +{ + struct vhost_iotlb *iotlb; + int i; + + iotlb = d->iotlb; + if (!iotlb) + return; + + vhost_dev_lock_vqs(d); + + /* + * vhost_dev_lock_vqs() takes all VQ mutexes in index order. Drop the + * device-wide view while they are held, then clear each per-VQ view + * and its cached ring access before releasing the locks. Workers + * cannot observe a mixed address-space state during this handoff. + */ + d->iotlb = NULL; + + for (i = 0; i < d->nvqs; ++i) { + struct vhost_virtqueue *vq = d->vqs[i]; + + vq->iotlb = NULL; + vhost_vq_invalidate_access(vq); + } + + vhost_dev_unlock_vqs(d); + vhost_clear_msg(d); + vhost_iotlb_free(iotlb); + wake_up_interruptible_poll(&d->wait, EPOLLIN | EPOLLRDNORM); +} +EXPORT_SYMBOL_GPL(vhost_clear_device_iotlb); + int vhost_init_device_iotlb(struct vhost_dev *d) { struct vhost_iotlb *niotlb, *oiotlb; @@ -2335,7 +2387,10 @@ int vhost_init_device_iotlb(struct vhost_dev *d) mutex_lock(&vq->mutex); vq->iotlb = niotlb; - __vhost_vq_meta_reset(vq); + if (oiotlb) + __vhost_vq_meta_reset(vq); + else + vhost_vq_invalidate_access(vq); mutex_unlock(&vq->mutex); } diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index fa76b7d44662..39e6121f7525 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -280,6 +280,7 @@ ssize_t vhost_chr_read_iter(struct vhost_dev *dev, struct iov_iter *to, int noblock); ssize_t vhost_chr_write_iter(struct vhost_dev *dev, struct iov_iter *from); +void vhost_clear_device_iotlb(struct vhost_dev *d); int vhost_init_device_iotlb(struct vhost_dev *d); void vhost_iotlb_map_free(struct vhost_iotlb *iotlb, diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index 9aaab6bb8061..abed1fbcf66c 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -868,6 +868,8 @@ static int vhost_vsock_set_features(struct vhost_vsock *vsock, u64 features) if ((features & (1ULL << VIRTIO_F_ACCESS_PLATFORM))) { if (vhost_init_device_iotlb(&vsock->dev)) goto err; + } else { + vhost_clear_device_iotlb(&vsock->dev); } vsock->seqpacket_allow = features & (1ULL << VIRTIO_VSOCK_F_SEQPACKET); From 81489b32a21c9360f8750d1fb600155d27452e19 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Wed, 5 Aug 2026 11:29:31 +0800 Subject: [PATCH 25/27] virtio_input: reset device if input_register_device() fails Probe marks the device DRIVER_OK with virtio_device_ready() before calling input_register_device(). If registration fails, the error path cleared vi->ready and called del_vqs() while the device was still live, so the device could keep DMA to queues that were already torn down. Match remove/freeze: call virtio_reset_device() on that path before tearing down the virtqueues. Fixes: 271c865161c5 ("Add virtio-input driver.") Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260805032931.1606652-1-xiongweimin@kylinos.cn> --- drivers/virtio/virtio_input.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/virtio/virtio_input.c b/drivers/virtio/virtio_input.c index deec24e8e682..1a87be4c88cf 100644 --- a/drivers/virtio/virtio_input.c +++ b/drivers/virtio/virtio_input.c @@ -331,6 +331,7 @@ static int virtinput_probe(struct virtio_device *vdev) spin_lock_irqsave(&vi->lock, flags); vi->ready = false; spin_unlock_irqrestore(&vi->lock, flags); + virtio_reset_device(vdev); err_mt_init_slots: input_free_device(vi->idev); err_input_alloc: From d7808b37da0a619cf1fa541c2384e783fecc2480 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 17:20:58 +0200 Subject: [PATCH 26/27] virtio_input: stop callbacks before unregistering input device virtinput_remove() unregisters the input device before resetting the virtio device. virtinput_recv_events() drops vi->lock around input_event(), so clearing vi->ready does not stop a callback that passed the entry check. It can still use vi->idev, requeue buffers and kick the queue. Reset first, as virtinput_freeze() already does. With the preceding core change, reset waits for callbacks before input_unregister_device() can free vi->idev. Recheck vi->ready after taking the lock again: keep draining completed events so an input packet is not truncated, but stop requeueing buffers and kicking the queue. With evdev attached, input_unregister_handle() currently waits for an RCU grace period, which also waits out IRQ callbacks. This masks the lifetime bug on PCI and MMIO, but does not protect sleepable callbacks on other transports. Fixes: 271c865161c5 ("Add virtio-input driver.") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260905152059.89560-3-kmehltretter@gmail.com> --- drivers/virtio/virtio_input.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/virtio/virtio_input.c b/drivers/virtio/virtio_input.c index 1a87be4c88cf..e3bd0b9616f9 100644 --- a/drivers/virtio/virtio_input.c +++ b/drivers/virtio/virtio_input.c @@ -49,9 +49,12 @@ static void virtinput_recv_events(struct virtqueue *vq) le16_to_cpu(event->code), le32_to_cpu(event->value)); spin_lock_irqsave(&vi->lock, flags); + if (!vi->ready) + continue; virtinput_queue_evtbuf(vi, event); } - virtqueue_kick(vq); + if (vi->ready) + virtqueue_kick(vq); } spin_unlock_irqrestore(&vi->lock, flags); } @@ -351,8 +354,9 @@ static void virtinput_remove(struct virtio_device *vdev) vi->ready = false; spin_unlock_irqrestore(&vi->lock, flags); - input_unregister_device(vi->idev); + /* Callbacks use vi->idev. */ virtio_reset_device(vdev); + input_unregister_device(vi->idev); while ((buf = virtqueue_detach_unused_buf(vi->sts)) != NULL) kfree(buf); vdev->config->del_vqs(vdev); From 48a4ee65e677559776349128e6a81a6041986c99 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Tue, 8 Sep 2026 15:31:51 +0800 Subject: [PATCH 27/27] vduse: return compat ioctl results directly The compat handler handles VDUSE_IOTLB_GET_FD and VDUSE_VQ_GET_INFO, but then calls the native handler. Their different command sizes make native dispatch return -ENOIOCTLCMD. For GET_FD, this overwrites receive_fd()'s return value after the descriptor is installed, leaking one fd per call. Return handled compat results directly and use native dispatch only for other commands. Fixes: 455a2a1af926 ("vduse: fix compat handling for VDUSE_IOTLB_GET_FD/VDUSE_VQ_GET_INFO") Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260908-fix-vduse_dev_compat_ioctl-v1-1-62264d9bfb8d@gmail.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 4dea4d6a3855..49a231bdf948 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -1882,11 +1882,11 @@ static long vduse_dev_compat_ioctl(struct file *file, unsigned int cmd, break; } default: - ret = -ENOIOCTLCMD; - break; + return vduse_dev_ioctl(file, cmd, + (unsigned long)compat_ptr(arg)); } - return vduse_dev_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); + return ret; } #else #define vduse_dev_compat_ioctl compat_ptr_ioctl