From 62b0a9b11e94a4bc7f67c89348d0619cba82b54b Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 9 Sep 2026 08:49:38 +0200 Subject: [PATCH 01/38] virtio-spi: Fix virtio_spi_config kernel-doc struct virtio_spi_config contains max_cs_inactive_ns, but its kernel-doc describes max_cs_incative_ns. This leaves the real member undescribed and adds a nonexistent member to the generated documentation. Correct the spelling. Fixes: 6a1f3390fafe ("virtio-spi: Add virtio-spi.h") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260909064938.90995-1-kmehltretter@gmail.com> --- include/uapi/linux/virtio_spi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/uapi/linux/virtio_spi.h b/include/uapi/linux/virtio_spi.h index 8ab3c970cdd3..459c2276c938 100644 --- a/include/uapi/linux/virtio_spi.h +++ b/include/uapi/linux/virtio_spi.h @@ -73,7 +73,7 @@ * @max_cs_hold_ns: the maximum delay supported before chipselect is deasserted, * in ns unit, 0 means delay is not supported to introduce before chipselect * is deasserted. - * @max_cs_incative_ns: maximum delay supported after chipselect is deasserted, + * @max_cs_inactive_ns: maximum delay supported after chipselect is deasserted, * in ns unit, 0 means delay is not supported to introduce after chipselect is * deasserted. */ From d7825623a9d4452f97da7ea0d84b2967d507ccf9 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Thu, 10 Sep 2026 20:39:03 +0800 Subject: [PATCH 02/38] virtio_ring: reset IN_ORDER state in virtqueue_init() The IN_ORDER feature uses additional software state to track the next available descriptor and in-flight descriptor batches. virtqueue_init() resets the common virtqueue state, but free_head and batch_last.id are initialized only when the virtqueue is created. When an IN_ORDER virtqueue is reset, these fields can retain state from the previous queue instance. Reset free_head and invalidate batch_last when initializing an IN_ORDER virtqueue. Keep this in virtqueue_init() so the state is initialized consistently for both newly created and reset virtqueues. Fixes: f6a15d854986 ("virtio_ring: add in order support") Signed-off-by: Li RongQing Signed-off-by: Michael S. Tsirkin Message-ID: <20260910123903.1717-1-lirongqing@baidu.com> --- drivers/virtio/virtio_ring.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index db678f5a80e0..d0dc4645b5b3 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -559,6 +559,11 @@ static void virtqueue_init(struct vring_virtqueue *vq, u32 num) vq->in_use = false; vq->last_add_time_valid = false; #endif + + if (virtqueue_is_in_order(vq)) { + vq->free_head = 0; + vq->batch_last.id = UINT_MAX; + } } From 8517ed4429a2eefd002dce00ea46e23e6714350b Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Fri, 11 Sep 2026 17:00:58 +0800 Subject: [PATCH 03/38] vdpa_sim: serialize IOTLB updates with dataplane access The simulator translates IOTLB entries into iovecs or bvecs under iommu_lock, then drops the lock before copying data. A concurrent DMA unmap can delete the mapping and release its pinned backing pages while the copy is still in progress, leading to a UAF in _copy_from_iter(). Use the existing simulator mutex to serialize DMA map changes with the full backend work function. Also make vhost-vdpa stop device access before unpinning the backing pages. Otherwise, waiting in the simulator DMA unmap callback happens after the pages have already been released and cannot protect their lifetime. Keep iommu_lock for the IOTLB table itself and avoid adding a second long-lived lock to the simulator. I found this bug myself, though the patch was written with AI assistance. Fixes: 2c53d0f64c06 ("vdpasim: vDPA device simulator") Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng Sun Message-ID: <20260911-fix-vdpa-iotlb-lifetime-v3-1-8766d371748e@gmail.com> Signed-off-by: Michael S. Tsirkin --- drivers/vdpa/vdpa_sim/vdpa_sim.c | 4 ++++ drivers/vhost/vdpa.c | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim.c b/drivers/vdpa/vdpa_sim/vdpa_sim.c index cfa88a60a38f..017366cb6684 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim.c @@ -713,6 +713,7 @@ static int vdpasim_dma_map(struct vdpa_device *vdpa, unsigned int asid, if (asid >= vdpasim->dev_attr.nas) return -EINVAL; + mutex_lock(&vdpasim->mutex); spin_lock(&vdpasim->iommu_lock); if (vdpasim->iommu_pt[asid]) { vhost_iotlb_reset(&vdpasim->iommu[asid]); @@ -721,6 +722,7 @@ static int vdpasim_dma_map(struct vdpa_device *vdpa, unsigned int asid, ret = vhost_iotlb_add_range_ctx(&vdpasim->iommu[asid], iova, iova + size - 1, pa, perm, opaque); spin_unlock(&vdpasim->iommu_lock); + mutex_unlock(&vdpasim->mutex); return ret; } @@ -733,6 +735,7 @@ static int vdpasim_dma_unmap(struct vdpa_device *vdpa, unsigned int asid, if (asid >= vdpasim->dev_attr.nas) return -EINVAL; + mutex_lock(&vdpasim->mutex); spin_lock(&vdpasim->iommu_lock); if (vdpasim->iommu_pt[asid]) { vhost_iotlb_reset(&vdpasim->iommu[asid]); @@ -740,6 +743,7 @@ static int vdpasim_dma_unmap(struct vdpa_device *vdpa, unsigned int asid, } vhost_iotlb_del_range(&vdpasim->iommu[asid], iova, iova + size - 1); spin_unlock(&vdpasim->iommu_lock); + mutex_unlock(&vdpasim->mutex); return 0; } diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index ff7d90719171..43678124646a 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -976,6 +976,8 @@ static void vhost_vdpa_pa_unmap(struct vhost_vdpa *v, struct vhost_iotlb *iotlb, unsigned long pfn, pinned; while ((map = vhost_iotlb_itree_first(iotlb, start, last)) != NULL) { + /* Stop device access before releasing the pinned pages. */ + vhost_vdpa_general_unmap(v, map, asid); pinned = PFN_DOWN(map->size); for (pfn = PFN_DOWN(map->addr); pinned > 0; pfn++, pinned--) { @@ -985,7 +987,6 @@ static void vhost_vdpa_pa_unmap(struct vhost_vdpa *v, struct vhost_iotlb *iotlb, unpin_user_page(page); } atomic64_sub(PFN_DOWN(map->size), &dev->mm->pinned_vm); - vhost_vdpa_general_unmap(v, map, asid); vhost_iotlb_map_free(iotlb, map); } } From acbf36deb53d9e14e4768bf58883795a8232aa5b Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Fri, 11 Sep 2026 17:00:59 +0800 Subject: [PATCH 04/38] vdpa: invalidate DMA mappings before releasing memory vdpa can release pinned pages and file references before the device stops using the corresponding mappings, allowing stale DMA to corrupt memory. Invalidate mappings before dropping their backing references. For set_map backends, publish an IOTLB without the invalidated range and keep the old references if that fails. Do this immediately even within a batch. Serialize vdpa_sim set_map updates with its worker so publishing a new map cannot race an active transfer. Abort mlx5 map replacement when resumable virtqueues cannot be suspended, since changing the memory region while those queues remain active would preserve the same stale DMA window. Reject zero-sized and wrapping invalidations before calculating their inclusive range end. I found this bug myself, though the patch was written with AI assistance. Fixes: 4c8cf31885f6 ("vhost: introduce vDPA-based backend") Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng Sun Message-ID: <20260911-fix-vdpa-iotlb-lifetime-v3-2-8766d371748e@gmail.com> Signed-off-by: Michael S. Tsirkin --- drivers/vdpa/mlx5/net/mlx5_vnet.c | 10 +++++++- drivers/vdpa/vdpa_sim/vdpa_sim.c | 3 +++ drivers/vhost/vdpa.c | 40 ++++++++++++++++++++++++------- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/drivers/vdpa/mlx5/net/mlx5_vnet.c b/drivers/vdpa/mlx5/net/mlx5_vnet.c index 8563fec2855d..0fcb18b5f30a 100644 --- a/drivers/vdpa/mlx5/net/mlx5_vnet.c +++ b/drivers/vdpa/mlx5/net/mlx5_vnet.c @@ -3059,7 +3059,15 @@ static int mlx5_vdpa_change_map(struct mlx5_vdpa_dev *mvdev, bool teardown = !is_resumable(ndev); int err; - suspend_vqs(ndev, 0, ndev->cur_num_vqs); + if ((mvdev->status & VIRTIO_CONFIG_S_DRIVER_OK) && !mvdev->suspended) { + err = suspend_vqs(ndev, 0, ndev->cur_num_vqs); + if (err && !teardown) { + for (int i = 0; i < ndev->cur_num_vqs; i++) + resume_vqs(ndev, i, 1); + mlx5_vdpa_put_mr(mvdev, new_mr); + return err; + } + } if (teardown) { err = save_channels_info(ndev); if (err) { diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim.c b/drivers/vdpa/vdpa_sim/vdpa_sim.c index 017366cb6684..97d437d89524 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim.c @@ -638,6 +638,7 @@ static int vdpasim_set_map(struct vdpa_device *vdpa, unsigned int asid, if (asid >= vdpasim->dev_attr.nas) return -EINVAL; + mutex_lock(&vdpasim->mutex); spin_lock(&vdpasim->iommu_lock); iommu = &vdpasim->iommu[asid]; @@ -652,11 +653,13 @@ static int vdpasim_set_map(struct vdpa_device *vdpa, unsigned int asid, goto err; } spin_unlock(&vdpasim->iommu_lock); + mutex_unlock(&vdpasim->mutex); return 0; err: vhost_iotlb_reset(iommu); spin_unlock(&vdpasim->iommu_lock); + mutex_unlock(&vdpasim->mutex); return ret; } diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index 43678124646a..ad3a157b4050 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -998,10 +998,10 @@ static void vhost_vdpa_va_unmap(struct vhost_vdpa *v, struct vhost_iotlb *iotlb, struct vdpa_map_file *map_file; while ((map = vhost_iotlb_itree_first(iotlb, start, last)) != NULL) { + vhost_vdpa_general_unmap(v, map, asid); map_file = (struct vdpa_map_file *)map->opaque; fput(map_file->file); kfree(map_file); - vhost_vdpa_general_unmap(v, map, asid); vhost_iotlb_map_free(iotlb, map); } } @@ -1075,21 +1075,39 @@ static int vhost_vdpa_map(struct vhost_vdpa *v, struct vhost_iotlb *iotlb, return 0; } -static void vhost_vdpa_unmap(struct vhost_vdpa *v, - struct vhost_iotlb *iotlb, - u64 iova, u64 size) +static int vhost_vdpa_unmap(struct vhost_vdpa *v, + struct vhost_iotlb *iotlb, + u64 iova, u64 size) { struct vdpa_device *vdpa = v->vdpa; const struct vdpa_config_ops *ops = vdpa->config; + struct vhost_iotlb_map *map; + struct vhost_iotlb new_iotlb; u32 asid = iotlb_to_asid(iotlb); + u64 last = iova + size - 1; + int ret; - vhost_vdpa_iotlb_unmap(v, iotlb, iova, iova + size - 1, asid); + if (ops->set_map && !ops->dma_map) { + vhost_iotlb_init(&new_iotlb, iotlb->limit, iotlb->flags); + list_for_each_entry(map, &iotlb->list, link) { + if (map->start <= last && map->last >= iova) + continue; + ret = vhost_iotlb_add_range_ctx(&new_iotlb, map->start, + map->last, map->addr, + map->perm, map->opaque); + if (ret) + goto reset; + } - if (ops->set_map) { - if (!v->in_batch) - ops->set_map(vdpa, asid, iotlb); + ret = ops->set_map(vdpa, asid, &new_iotlb); +reset: + vhost_iotlb_reset(&new_iotlb); + if (ret) + return ret; } + vhost_vdpa_iotlb_unmap(v, iotlb, iova, last, asid); + return 0; } static int vhost_vdpa_va_map(struct vhost_vdpa *v, @@ -1343,7 +1361,11 @@ static int vhost_vdpa_process_iotlb_msg(struct vhost_dev *dev, u32 asid, r = vhost_vdpa_process_iotlb_update(v, iotlb, msg); break; case VHOST_IOTLB_INVALIDATE: - vhost_vdpa_unmap(v, iotlb, msg->iova, msg->size); + if (!msg->size || msg->iova > U64_MAX - msg->size + 1) { + r = -EINVAL; + break; + } + r = vhost_vdpa_unmap(v, iotlb, msg->iova, msg->size); break; case VHOST_IOTLB_BATCH_BEGIN: v->batch_asid = asid; From a9a428115b7d8aa45e12e41899f90f60551bfa82 Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Tue, 15 Sep 2026 17:42:43 +0800 Subject: [PATCH 05/38] vhost-scsi: preserve event ordering vhost_scsi_send_evt() uses llist_add(), so pending events are LIFO. vhost_scsi_complete_events() walks the detached list directly. This can deliver a later hotplug or hotunplug event before an earlier one. Reverse the detached list before processing it. This keeps delivery in enqueue order. Fixes: a6c9af87363c ("tcm_vhost: Add hotplug/hotunplug support") Signed-off-by: Jia Jia Reviewed-by: Mike Christie Signed-off-by: Michael S. Tsirkin Message-ID: <20260915094244.7900-2-physicalmtea@gmail.com> --- drivers/vhost/scsi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 885fac5ed042..7c521406d5ca 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -667,7 +667,7 @@ static void vhost_scsi_complete_events(struct vhost_scsi *vs, bool drop) struct llist_node *llnode; mutex_lock(&vq->mutex); - llnode = llist_del_all(&vs->vs_event_list); + llnode = llist_reverse_order(llist_del_all(&vs->vs_event_list)); llist_for_each_entry_safe(evt, t, llnode, list) { if (!drop) vhost_scsi_do_evt_work(vs, evt); From a016d03a9cfe29aeeba438f32f8e8f63291e3275 Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Tue, 15 Sep 2026 17:42:44 +0800 Subject: [PATCH 06/38] vhost-scsi: do not relock event vq mutex on send_evt fallback vhost_scsi_send_evt() is called with the event virtqueue mutex held. If the worker is gone, the fallback currently calls vhost_scsi_complete_events(), which tries to acquire the same mutex again and deadlocks the caller. Split event completion into a helper for callers that already hold the mutex and a locking wrapper for the event worker. Use the helper on the fallback path. Link: https://lore.kernel.org/all/20260905005352.1E5B01F00A3D@smtp.kernel.org/ Fixes: b1b2ce58ed23 ("vhost-scsi: Handle vhost_vq_work_queue failures for events") Signed-off-by: Jia Jia Reviewed-by: Mike Christie Signed-off-by: Michael S. Tsirkin Message-ID: <20260915094244.7900-3-physicalmtea@gmail.com> --- drivers/vhost/scsi.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 7c521406d5ca..1a0b75cc4cef 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -660,19 +660,26 @@ vhost_scsi_do_evt_work(struct vhost_scsi *vs, struct vhost_scsi_evt *evt) vhost_scsi_log_write(vq, vq_log, log_num); } -static void vhost_scsi_complete_events(struct vhost_scsi *vs, bool drop) +/* Caller must hold the event virtqueue mutex. */ +static void __vhost_scsi_complete_events(struct vhost_scsi *vs, bool drop) { - struct vhost_virtqueue *vq = &vs->vqs[VHOST_SCSI_VQ_EVT].vq; struct vhost_scsi_evt *evt, *t; struct llist_node *llnode; - mutex_lock(&vq->mutex); llnode = llist_reverse_order(llist_del_all(&vs->vs_event_list)); llist_for_each_entry_safe(evt, t, llnode, list) { if (!drop) vhost_scsi_do_evt_work(vs, evt); vhost_scsi_free_evt(vs, evt); } +} + +static void vhost_scsi_complete_events(struct vhost_scsi *vs, bool drop) +{ + struct vhost_virtqueue *vq = &vs->vqs[VHOST_SCSI_VQ_EVT].vq; + + mutex_lock(&vq->mutex); + __vhost_scsi_complete_events(vs, drop); mutex_unlock(&vq->mutex); } @@ -1859,7 +1866,7 @@ vhost_scsi_send_evt(struct vhost_scsi *vs, struct vhost_virtqueue *vq, llist_add(&evt->list, &vs->vs_event_list); if (!vhost_vq_work_queue(vq, &vs->vs_event_work)) - vhost_scsi_complete_events(vs, true); + __vhost_scsi_complete_events(vs, true); } static void vhost_scsi_evt_handle_kick(struct vhost_work *work) From fe812fd89636f30adf77147e168ec8240ae345c1 Mon Sep 17 00:00:00 2001 From: Dudu Lu Date: Mon, 13 Apr 2026 17:05:26 +0800 Subject: [PATCH 07/38] vhost/vringh: Fix IOTLB permission in putu16_iotlb() putu16_iotlb() performs a write operation (__put_user / WRITE_ONCE) but requests IOTLB translation with VHOST_MAP_RO (read-only permission). This means the IOTLB lookup succeeds only if the page has read permission, ignoring write permission entirely. A page mapped read-only in the IOTLB would incorrectly allow the write, while a page mapped write-only would incorrectly deny it. The neighbouring getu16_iotlb() correctly uses VHOST_MAP_RO for its read operation, and the copydesc write path (putdesc_iotlb) correctly uses VHOST_MAP_WO, confirming this is a copy-paste error. Fix by changing VHOST_MAP_RO to VHOST_MAP_WO to match the write semantics of putu16_iotlb(). Fixes: 0ea9ee430111 ("vhost/vringh: add IOTLB support") Signed-off-by: Dudu Lu Signed-off-by: Michael S. Tsirkin Message-ID: <20260413090526.80563-1-phx0fer@gmail.com> --- drivers/vhost/vringh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/vhost/vringh.c b/drivers/vhost/vringh.c index 74836eb60c7a..51edd54f7107 100644 --- a/drivers/vhost/vringh.c +++ b/drivers/vhost/vringh.c @@ -1244,7 +1244,7 @@ static inline int putu16_iotlb(const struct vringh *vrh, /* Atomic write is needed for putu16 */ ret = iotlb_translate(vrh, (u64)(uintptr_t)p, sizeof(*p), - NULL, &ivec, VHOST_MAP_RO); + NULL, &ivec, VHOST_MAP_WO); if (ret < 0) return ret; From bfcde6b64769bc168037b8bd882de4def4b2b3f4 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Sat, 12 Sep 2026 22:59:24 +0800 Subject: [PATCH 08/38] vhost: fix dirty logging for in-order used ring entries With VIRTIO_F_IN_ORDER, one used ring element can represent multiple buffers, so vhost_add_used_n_in_order() can write consecutive elements to nonadjacent slots. Dirty logging combines the initial slot with a length based on the final position, which can miss written elements or log past the end of the ring. Replay nheads[] after the write barrier and log the written slots, coalescing adjacent elements and splitting at gaps and wraps. Fixes: 67a873df0c41 ("vhost: basic in order support") Signed-off-by: Linfeng Sun Message-ID: <20260912-fix-log_write-v1-1-72c23db01268@gmail.com> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/vhost.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 4d5d32e68d35..f2b9f4866628 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -3179,7 +3179,6 @@ static int vhost_add_used_n_in_order(struct vhost_virtqueue *vq, return -EINVAL; start = vq->last_used_idx & (vq->num - 1); - used = vq->used->ring + start; for (i = 0; i < count; i++) { if (vhost_put_used(vq, &heads[i], start, 1)) { @@ -3193,14 +3192,31 @@ static int vhost_add_used_n_in_order(struct vhost_virtqueue *vq, } if (unlikely(vq->log_used)) { + unsigned int log_count = 0; + int log_start = 0, next; + /* Make sure data is seen before log. */ smp_wmb(); - /* Log used ring entry write. */ - log_used(vq, ((void __user *)used - (void __user *)vq->used), - (vq->num - start) * sizeof *used); - if (start + count > vq->num) - log_used(vq, 0, - (start + count - vq->num) * sizeof *used); + /* Log contiguous runs of used ring entries that were written. */ + start = vq->last_used_idx & (vq->num - 1); + for (i = 0; i < count; i++) { + if (!log_count) + log_start = start; + log_count++; + + next = start + nheads[i]; + if (next >= vq->num) + next -= vq->num; + if (i + 1 == count || next != start + 1) { + used = vq->used->ring + log_start; + log_used(vq, + ((void __user *)used - + (void __user *)vq->used), + log_count * sizeof(*used)); + log_count = 0; + } + start = next; + } } old = vq->last_used_idx; From 104d58a6d2592898fbb6989732600b07d543a8e5 Mon Sep 17 00:00:00 2001 From: Maoxia <2312891301@qq.com> Date: Sun, 13 Sep 2026 02:03:21 +0800 Subject: [PATCH 09/38] virtio_pci: add missing newline to the pmcsr error message The error message emitted when the PCI power management control register cannot be read is not newline-terminated, so the next kernel message is appended to it on the same line. Add the missing '\n'. Fixes: dff4fa0e5785 ("virtio: Add support for no-reset virtio PCI PM") Signed-off-by: Maoxia <2312891301@qq.com> Signed-off-by: Michael S. Tsirkin Message-ID: --- drivers/virtio/virtio_pci_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c index b90c174450b2..24af84b0c6ad 100644 --- a/drivers/virtio/virtio_pci_common.c +++ b/drivers/virtio/virtio_pci_common.c @@ -633,7 +633,7 @@ static bool vp_supports_pm_no_reset(struct device *dev) pci_read_config_word(pci_dev, pci_dev->pm_cap + PCI_PM_CTRL, &pmcsr); if (PCI_POSSIBLE_ERROR(pmcsr)) { - dev_err(dev, "Unable to query pmcsr"); + dev_err(dev, "Unable to query pmcsr\n"); return false; } From 1f8824fe7f6eaaf2355404db08d3cd05a28aa576 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sat, 12 Sep 2026 14:54:01 -0700 Subject: [PATCH 10/38] vdpa: fix double free of the reply skb in vdpa_nl_cmd_dev_config_get_doit() genlmsg_reply() consumes the reply skb on every outcome: netlink_unicast() frees it whether it queues it, drops it, or bails out early. vdpa_nl_cmd_dev_config_get_doit() lets its return value fall through to the shared error label, which calls nlmsg_free(msg) again and double-frees the sk_buff on any reply failure. The failure is reachable unprivileged: VDPA_CMD_DEV_CONFIG_GET carries no GENL_ADMIN_PERM, so batching several requests into one sendto() on a socket with a shrunken SO_RCVBUF fills the receive queue, and netlink_attachskb() then takes its MSG_DONTWAIT path, freeing the skb and returning -EAGAIN. Set msg to NULL once genlmsg_reply() has consumed it so the shared exit path frees the skb only on the early error gotos that still own it; nlmsg_free(NULL) is a no-op. BUG: KASAN: slab-use-after-free in sk_skb_reason_drop (net/core/skbuff.c:1220) Read of size 4 at addr ffff88800d99d49c by task exploit/147 Call Trace: sk_skb_reason_drop (net/core/skbuff.c:1220) vdpa_nl_cmd_dev_config_get_doit (drivers/vdpa/vdpa.c:1324) genl_family_rcv_msg_doit (net/netlink/genetlink.c:1114) genl_rcv_msg (net/netlink/genetlink.c:1194) netlink_rcv_skb (net/netlink/af_netlink.c:2556) genl_rcv (net/netlink/genetlink.c:1218) netlink_unicast (net/netlink/af_netlink.c:1345) netlink_sendmsg (net/netlink/af_netlink.c:1900) __sys_sendto (net/socket.c:2281) __x64_sys_sendto (net/socket.c:2284) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) The buggy address belongs to the object at ffff88800d99d3c0 which belongs to the cache skbuff_head_cache of size 232 The buggy address is located 220 bytes inside of freed 232-byte region [ffff88800d99d3c0, ffff88800d99d4a8) Fixes: ad69dd0bf26b ("vdpa: Introduce query of device config layout") Reported-by: co+6dcbccedc9ec6452@bugs.sh Closes: https://lore.kernel.org/all/ILTHcT8oe1HP47sY25JVxssvKmkCTjYlX9Hs%40bugs.sh/ Assisted-by: Claude:claude-opus-5 Signed-off-by: Xiang Mei Signed-off-by: Michael S. Tsirkin Reported-by: Paul Moses Message-ID: <20260912215401.126194-1-xmei5@asu.edu> --- drivers/vdpa/vdpa.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/vdpa/vdpa.c b/drivers/vdpa/vdpa.c index 47c6c3d23f5c..6455f837dd26 100644 --- a/drivers/vdpa/vdpa.c +++ b/drivers/vdpa/vdpa.c @@ -1313,15 +1313,16 @@ static int vdpa_nl_cmd_dev_config_get_doit(struct sk_buff *skb, struct genl_info } err = vdpa_dev_config_fill(vdev, msg, info->snd_portid, info->snd_seq, 0, info->extack); - if (!err) + if (!err) { err = genlmsg_reply(msg, info); + msg = NULL; + } mdev_err: put_device(dev); dev_err: up_read(&vdpa_dev_lock); - if (err) - nlmsg_free(msg); + nlmsg_free(msg); return err; } From b179f21d2d8a60ede29a00472bffdefb0284ef39 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Sun, 13 Sep 2026 21:00:27 +0800 Subject: [PATCH 11/38] vhost: reject invalid IOTLB update permissions vhost_chr_write_iter() validates the type and size of an IOTLB update, but does not validate its permission field. An invalid permission can therefore reach perm_to_iommu_flags() and trigger its warning in vhost_vdpa_map(). Reject IOTLB UPDATE messages whose permission field is empty or contains bits outside VHOST_ACCESS_RW before dispatching them to a backend. Fixes: 4c8cf31885f6 ("vhost: introduce vDPA-based backend") Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260913-fix-vhost_chr_write_iter-v1-1-76bf1f436cfe@gmail.com> --- drivers/vhost/vhost.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index f2b9f4866628..9ce46445a46b 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -1766,7 +1766,8 @@ ssize_t vhost_chr_write_iter(struct vhost_dev *dev, goto done; } - if (msg.type == VHOST_IOTLB_UPDATE && msg.size == 0) { + if (msg.type == VHOST_IOTLB_UPDATE && + (!msg.size || !msg.perm || (msg.perm & ~VHOST_ACCESS_RW))) { ret = -EINVAL; goto done; } From fd1462c96bafe2cb14e88c837b270f8990b717b5 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Sat, 31 Oct 2026 13:45:34 -0400 Subject: [PATCH 12/38] vhost-vdpa: fix pinned page leak in vhost_vdpa_pa_map() error path When pin_user_pages() or vhost_vdpa_map() fails, vhost_vdpa_pa_map() is supposed to unpin the pages pinned after the last successfully mapped chunk, i.e. the range [map_pfn, last_pfn]. The unpin is guarded by nchunks, but nchunks is reset to 0 every time a chunk is mapped from inside the current page list, so a failure after any such boundary leaves that pending range pinned forever. With physically scattered memory nearly every page list contains a boundary, so the leak happens on practically every failed IOTLB update. The pending range is non-empty exactly when a page has been pinned, which last_pfn already tells us, so drop nchunks and test last_pfn. Fixes: ad89653f79f1 ("vhost-vdpa: fix page pinning leakage in error path (rework)") Cc: stable@vger.kernel.org Signed-off-by: Tal Zussman Reported-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20261031-vhost-vdpa-unpin-v1-1-94ffc9683256@columbia.edu> --- drivers/vhost/vdpa.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index ad3a157b4050..46169c8eb022 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -1170,7 +1170,7 @@ static int vhost_vdpa_pa_map(struct vhost_vdpa *v, unsigned long list_size = PAGE_SIZE / sizeof(struct page *); unsigned int gup_flags = FOLL_LONGTERM; unsigned long npages, cur_base, map_pfn, last_pfn = 0; - unsigned long lock_limit, sz2pin, nchunks, i; + unsigned long lock_limit, sz2pin, i; unsigned long page_offset; u64 start = iova; long pinned; @@ -1206,7 +1206,6 @@ static int vhost_vdpa_pa_map(struct vhost_vdpa *v, cur_base = uaddr & PAGE_MASK; iova &= PAGE_MASK; - nchunks = 0; while (npages) { sz2pin = min_t(unsigned long, npages, list_size); @@ -1221,7 +1220,6 @@ static int vhost_vdpa_pa_map(struct vhost_vdpa *v, } goto out; } - nchunks++; if (!last_pfn) map_pfn = page_to_pfn(page_list[0]); @@ -1252,7 +1250,6 @@ static int vhost_vdpa_pa_map(struct vhost_vdpa *v, map_pfn = this_pfn; iova += csize; - nchunks = 0; } last_pfn = this_pfn; @@ -1267,7 +1264,7 @@ static int vhost_vdpa_pa_map(struct vhost_vdpa *v, PFN_PHYS(map_pfn), perm, NULL); out: if (ret) { - if (nchunks) { + if (last_pfn) { unsigned long pfn; /* @@ -1279,7 +1276,6 @@ static int vhost_vdpa_pa_map(struct vhost_vdpa *v, * the corresponding unpinning will be handled by * vdpa_unmap(). */ - WARN_ON(!last_pfn); for (pfn = map_pfn; pfn <= last_pfn; pfn++) unpin_user_page(pfn_to_page(pfn)); } From 73d135601d3b533985a65748c0278ba3cfe48290 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Sat, 31 Oct 2026 13:45:35 -0400 Subject: [PATCH 13/38] vhost-vdpa: unpin mappings by range vhost_vdpa_pa_unmap() unpins each mapping a page at a time, dirtying each page first if the mapping is writable. Each mapping is physically contiguous, so use unpin_user_page_range_dirty_lock() instead, which marks every folio dirty once and drops its pins in one go. Do the same for the pages left pinned in the vhost_vdpa_pa_map() error path. This removes the last use of set_page_dirty_lock() in vhost. Signed-off-by: Tal Zussman Message-ID: <20261031-vhost-vdpa-unpin-v1-2-94ffc9683256@columbia.edu> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/vdpa.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index 46169c8eb022..76066f6ee567 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -972,20 +972,13 @@ static void vhost_vdpa_pa_unmap(struct vhost_vdpa *v, struct vhost_iotlb *iotlb, { struct vhost_dev *dev = &v->vdev; struct vhost_iotlb_map *map; - struct page *page; - unsigned long pfn, pinned; while ((map = vhost_iotlb_itree_first(iotlb, start, last)) != NULL) { /* Stop device access before releasing the pinned pages. */ vhost_vdpa_general_unmap(v, map, asid); - pinned = PFN_DOWN(map->size); - for (pfn = PFN_DOWN(map->addr); - pinned > 0; pfn++, pinned--) { - page = pfn_to_page(pfn); - if (map->perm & VHOST_ACCESS_WO) - set_page_dirty_lock(page); - unpin_user_page(page); - } + unpin_user_page_range_dirty_lock(pfn_to_page(PFN_DOWN(map->addr)), + PFN_DOWN(map->size), + map->perm & VHOST_ACCESS_WO); atomic64_sub(PFN_DOWN(map->size), &dev->mm->pinned_vm); vhost_iotlb_map_free(iotlb, map); } @@ -1265,8 +1258,6 @@ static int vhost_vdpa_pa_map(struct vhost_vdpa *v, out: if (ret) { if (last_pfn) { - unsigned long pfn; - /* * Unpin the outstanding pages which are yet to be * mapped but haven't due to vdpa_map() or @@ -1276,8 +1267,9 @@ static int vhost_vdpa_pa_map(struct vhost_vdpa *v, * the corresponding unpinning will be handled by * vdpa_unmap(). */ - for (pfn = map_pfn; pfn <= last_pfn; pfn++) - unpin_user_page(pfn_to_page(pfn)); + unpin_user_page_range_dirty_lock(pfn_to_page(map_pfn), + last_pfn - map_pfn + 1, + false); } vhost_vdpa_unmap(v, iotlb, start, size); } From c9c1c1b7b31fc8b9a5d6c04f5f0ae647d081a39d Mon Sep 17 00:00:00 2001 From: Yu Zhang Date: Fri, 18 Sep 2026 14:04:36 +1000 Subject: [PATCH 14/38] vhost-vdpa: drop the parent's vq callback before the call fd is released VHOST_SET_VRING_CALL releases the previous call eventfd inside vhost_vring_ioctl() -- it swaps the new context into vq->call_ctx.ctx and then eventfd_ctx_put()s the old one, which is a synchronous kfree(). The parent vdpa device is only told about the change afterwards, when vhost_vdpa_vring_ioctl() reaches ops->set_vq_cb(). Parent drivers cache the pointer handed to them in vdpa_callback::trigger and do not take a reference on it, so throughout that window the parent holds a dangling eventfd_ctx and may signal it. The documentation added with the field describes what signalling it means but says nothing about how long it stays valid. This is the same hazard that "vhost_vdpa: assign irq bypass producer token correctly" addressed for the irq bypass producer token, by moving vhost_vdpa_unsetup_vq_irq() ahead of the vhost_vring_ioctl() call. The producer token was only one of the two consumers of that pointer; the one the parent keeps via ->set_vq_cb() was left behind the free. With VDUSE the window is directly reachable from userspace, because the device emulation daemon can inject an interrupt at any time from a different fd, and neither side shares a lock with the other: VDUSE takes vq->irq_lock, vhost takes vhost_dev.mutex + vq->mutex. BUG: KASAN: slab-use-after-free in _raw_spin_lock_irqsave+0x76/0xe0 Write of size 4 at addr ffff8881084e8788 by task vduse_uaf/2987 _raw_spin_lock_irqsave+0x76/0xe0 eventfd_signal_mask+0x69/0x120 vduse_dev_ioctl+0x337/0x1a60 <- vduse_vq_signal_irqfd(), inlined __x64_sys_ioctl+0x120/0x170 <- VDUSE_VQ_INJECT_IRQ Allocated by task 2986: do_eventfd+0x50/0x200 __x64_sys_eventfd2+0x2e/0x40 kmalloc-64, freed 64-byte region [ffff8881084e8780, ffff8881084e87c0) One thread loops VHOST_SET_VRING_CALL on /dev/vhost-vdpa-N with a fresh eventfd and then unbinds it, while another loops VDUSE_VQ_INJECT_IRQ on /dev/vduse/. This reproduces in 5 out of 5 ten-second runs on v7.1.6 and 3 out of 3 on v7.2-rc6. With the patch there are no reports in 3 out of 3 runs on either, while the same workload still gets ~30000 interrupts per run delivered into live eventfds, so the path is still being exercised. Tell the parent to drop the callback before vhost_vring_ioctl() can free the eventfd, mirroring what is already done for the bypass producer, and restore it if the ioctl fails -- on failure the swap never happened, the old context is still installed, and leaving the parent without a callback would silently drop that vq's interrupts. Clearing the callback only stops a parent from starting to use the context; a handler that already loaded it can still be running. For parents with a real interrupt, wait for it with synchronize_irq() on the vq's irq. VDUSE does not implement get_vq_irq, so that case stays covered by the teardown above. Fixes: 5e68470f4e80 ("vdpa: Add eventfd for the vdpa callback") Signed-off-by: Yu Zhang Signed-off-by: Michael S. Tsirkin Message-ID: <20260918040436.47982-1-yuz08559@gmail.com> --- drivers/vhost/vdpa.c | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index 76066f6ee567..f2ef7b852b2b 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -767,13 +767,46 @@ static long vhost_vdpa_vring_ioctl(struct vhost_vdpa *v, unsigned int cmd, if (ops->get_status(vdpa) & VIRTIO_CONFIG_S_DRIVER_OK) vhost_vdpa_unsetup_vq_irq(v, idx); + /* + * The parent caches call_ctx.ctx in cb.trigger without + * holding a reference, so it has to stop using it + * before vhost_vring_ioctl() drops the last one. + */ + cb.callback = NULL; + cb.private = NULL; + cb.trigger = NULL; + ops->set_vq_cb(vdpa, idx, &cb); + + /* + * A parent interrupt handler that loaded the callback + * before that store can still be running, so wait for + * it to finish with the context too. + */ + if (ops->get_vq_irq) { + int irq = ops->get_vq_irq(vdpa, idx); + + if (irq >= 0) + synchronize_irq(irq); + } } break; } r = vhost_vring_ioctl(&v->vdev, cmd, argp); - if (r) + if (r) { + /* + * A failure here means the swap never happened and the old + * context is still installed, so give the parent back the + * callback that was torn down above. + */ + if (cmd == VHOST_SET_VRING_CALL && vq->call_ctx.ctx) { + cb.callback = vhost_vdpa_virtqueue_cb; + cb.private = vq; + cb.trigger = vq->call_ctx.ctx; + ops->set_vq_cb(vdpa, idx, &cb); + } return r; + } switch (cmd) { case VHOST_SET_VRING_ADDR: From 434d2bc41bd0c0ac576d4d259bd9f2ab49071174 Mon Sep 17 00:00:00 2001 From: Sergii Ushakov Date: Fri, 18 Sep 2026 15:51:20 +0200 Subject: [PATCH 15/38] virtio-blk: clamp max_segments to virtqueue ring size In virtblk_add_req(), each request consumes 2 extra descriptors (out_hdr and in_hdr) in addition to the data scatter-gather segments. When a hypervisor (e.g. QNX Hypervisor) advertises VIRTIO_BLK_F_SEG_MAX with seg_max = 1024 alongside a 1024-entry split ring (vring.num = 1024) and VIRTIO_RING_F_INDIRECT_DESC disabled, lim->max_segments is set to 1024. When the block layer submits requests with 1023 or 1024 data segments, total_sg reaches 1025 or 1026. This exceeds vring.num (1024), causing virtqueue_add_split() to return -ENOSPC and permanently wedge the blk-mq queue. Furthermore, the Virtio specification (2.7.5.3.1) requires that a descriptor chain never exceed the Queue Size, and virtqueue_add_split() falls back to direct descriptors if indirect table allocation fails. Fix this by: 1. Rejecting queues with ring_size < 3 at probe, or ring_size < (queue_max_segments + 2) during resume/reset recovery in init_vq(). 2. Unconditionally clamping sg_elems to (ring_size - 2) across all virtqueues in virtblk_read_limits(). Reported-by: Stjepan Poljak Fixes: 0864b79a1533 ("virtio: block: dynamic maximum segments") Signed-off-by: Sergii Ushakov Signed-off-by: Michael S. Tsirkin Message-ID: <20260918135120.1261150-1-sergiiushakov@google.com> --- drivers/block/virtio_blk.c | 44 +++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c index c8bfbf8435ac..603bf92de5ba 100644 --- a/drivers/block/virtio_blk.c +++ b/drivers/block/virtio_blk.c @@ -1022,6 +1022,30 @@ static int init_vq(struct virtio_blk *vblk) goto out; for (i = 0; i < num_vqs; i++) { + unsigned int ring_size = virtqueue_get_vring_size(vqs[i]); + /* + * Each request consumes 2 extra descriptors (out_hdr and + * in_hdr) in addition to the data segments. + * + * At initial probe (!vblk->disk), each virtqueue must fit at + * least 1 data segment + 2 header descriptors (3). + * On resume/recovery (vblk->disk), each virtqueue must fit the + * existing queue_max_segments() + 2 header descriptors. + */ + unsigned int min_ring_size = 3; + + if (vblk->disk) + min_ring_size = queue_max_segments(vblk->disk->queue) + 2; + + if (ring_size < min_ring_size) { + dev_err(&vdev->dev, + "virtqueue %u ring size %u is smaller than minimum %u\n", + i, ring_size, min_ring_size); + vdev->config->del_vqs(vdev); + err = -EINVAL; + goto out; + } + spin_lock_init(&vblk->vqs[i].lock); vblk->vqs[i].vq = vqs[i]; } @@ -1254,7 +1278,7 @@ static int virtblk_read_limits(struct virtio_blk *vblk, u16 min_io_size; u8 physical_block_exp, alignment_offset; size_t max_dma_size; - int err; + int err, i; /* We need to know how many segments before we allocate. */ err = virtio_cread_feature(vdev, VIRTIO_BLK_F_SEG_MAX, @@ -1268,6 +1292,23 @@ static int virtblk_read_limits(struct virtio_blk *vblk, /* Prevent integer overflows and honor max vq size */ sg_elems = min_t(u32, sg_elems, VIRTIO_BLK_MAX_SG_ELEMS - 2); + /* + * virtblk_add_req() uses separate outgoing and incoming header + * descriptors (out_hdr and in_hdr), consuming 2 extra descriptors + * per request in addition to the data segments. + * + * Per virtio specification (2.7.5.3.1), a driver MUST NOT create a + * descriptor chain longer than the Queue Size of the device. + * + * Clamp max_segments to (ring_size - 2) across all virtqueues so + * that a request never exceeds the ring size of any queue. + */ + for (i = 0; i < vblk->num_vqs; i++) { + u32 ring_size = virtqueue_get_vring_size(vblk->vqs[i].vq); + + sg_elems = min_t(u32, sg_elems, ring_size - 2); + } + /* We can handle whatever the host told us to handle. */ lim->max_segments = sg_elems; @@ -1467,6 +1508,7 @@ static int virtblk_probe(struct virtio_device *vdev) mutex_init(&vblk->vdev_mutex); vblk->vdev = vdev; + vblk->disk = NULL; INIT_WORK(&vblk->config_work, virtblk_config_changed_work); From cf0a5805d43236e308f14188f9737ddcf83fb53a Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Sat, 19 Sep 2026 16:39:33 +0800 Subject: [PATCH 16/38] vduse: avoid infinite loop when no affinity CPU is online If all CPUs in vq->irq_affinity are offline, cpumask_next() walks to the end of the mask, and the loop then resets the cursor to IRQ_UNBOUND and restarts the scan. When VDUSE_VQ_INJECT_IRQ is issued, the intersection of vq->irq_affinity and cpu_online_mask is empty, so the caller busy-loops in kernel context on its current CPU (e.g. CPU2). Replace the while loop in vduse_vq_update_effective_cpu() with cpumask_next_and_wrap() to find the intersection of vq->irq_affinity and cpu_online_mask in a single wrapped scan. If the intersection is empty, set curr_cpu to IRQ_UNBOUND and let the existing unbound workqueue handle injection, avoiding repeated rescans when all CPUs in the affinity are offline. Use cpus_read_lock()/cpus_read_unlock() to keep the selected CPU online. Fixes: 28f6288eb63d ("vduse: Support set_vq_affinity callback") Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260919083933.104437-1-physicalmtea@gmail.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 49a231bdf948..f953353b50b4 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1430,14 +1431,10 @@ static void vduse_vq_update_effective_cpu(struct vduse_virtqueue *vq) { int curr_cpu = vq->irq_effective_cpu; - while (true) { - curr_cpu = cpumask_next(curr_cpu, &vq->irq_affinity); - if (cpu_online(curr_cpu)) - break; - - if (curr_cpu >= nr_cpu_ids) - curr_cpu = IRQ_UNBOUND; - } + curr_cpu = cpumask_next_and_wrap(curr_cpu, &vq->irq_affinity, + cpu_online_mask); + if (curr_cpu >= nr_cpu_ids) + curr_cpu = IRQ_UNBOUND; vq->irq_effective_cpu = curr_cpu; } @@ -1672,10 +1669,12 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, ret = 0; index = array_index_nospec(index, dev->vq_num); if (!vduse_vq_signal_irqfd(dev->vqs[index])) { + cpus_read_lock(); vduse_vq_update_effective_cpu(dev->vqs[index]); ret = vduse_dev_queue_irq_work(dev, &dev->vqs[index]->inject, dev->vqs[index]->irq_effective_cpu); + cpus_read_unlock(); } break; } From f898439ace7f89922e853b43bcbfcd26ea2e0a98 Mon Sep 17 00:00:00 2001 From: Fang Xieyan Date: Tue, 22 Sep 2026 20:29:54 +0800 Subject: [PATCH 17/38] vringh: bound top-level re-entry into indirect tables In __vringh_iov(), the F_INDIRECT branch descends into the indirect table and continues before the descriptor accounting runs. A top-level indirect descriptor is therefore never charged to count. If such a descriptor sets NEXT to point back at itself, returning from the indirect table resumes at the same top-level descriptor, which is again not counted, so the walk never makes forward progress. Because count stays flat and indirect_count is reset to 0 on every return to the top-level table, neither bound in the loop check trips. A guest can spin the vringh worker at 100% CPU inside __vringh_iov(), an uninterruptible host DoS. Move the descriptor accounting above the indirect switch so that a top-level indirect descriptor is charged one top-level traversal step before the walk descends into its table, bringing this re-entry under the existing vrh->vring.num bound. Each top-level descriptor is still charged at most one step and indirect_count still bounds a single table, so legitimate chains (including the multiple-indirect case in tools/virtio/vringh_test.c) stay within vring.num, while a cyclic indirect descriptor is now rejected with -ELOOP. Fixes: dbd29e075228 ("vringh: Fix loop descriptors check in the indirect cases") Cc: Xie Yongji Cc: stable@vger.kernel.org Assisted-by: Hawkeye:GLM-5.3-flash Assisted-by: Qoder:Qwen3.8-Max Signed-off-by: Fang Xieyan Signed-off-by: Michael S. Tsirkin Message-ID: <20260922122955.69433-2-fangxy@xiaopeng.com> --- drivers/vhost/vringh.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/vhost/vringh.c b/drivers/vhost/vringh.c index 51edd54f7107..e242ae0c1e16 100644 --- a/drivers/vhost/vringh.c +++ b/drivers/vhost/vringh.c @@ -333,6 +333,17 @@ __vringh_iov(struct vringh *vrh, u16 i, if (unlikely(err)) goto fail; + if (up_next == -1) + count++; + else + indirect_count++; + + if (count > vrh->vring.num || indirect_count > desc_max) { + vringh_bad("Descriptor loop in %p", descs); + err = -ELOOP; + goto fail; + } + if (unlikely(desc.flags & cpu_to_vringh16(vrh, VRING_DESC_F_INDIRECT))) { u64 a = vringh64_to_cpu(vrh, desc.addr); @@ -358,17 +369,6 @@ __vringh_iov(struct vringh *vrh, u16 i, continue; } - if (up_next == -1) - count++; - else - indirect_count++; - - if (count > vrh->vring.num || indirect_count > desc_max) { - vringh_bad("Descriptor loop in %p", descs); - err = -ELOOP; - goto fail; - } - if (desc.flags & cpu_to_vringh16(vrh, VRING_DESC_F_WRITE)) iov = wiov; else { From 8689ee3842daf24cd886b75cc8bd3c55e7d02199 Mon Sep 17 00:00:00 2001 From: Fang Xieyan Date: Tue, 22 Sep 2026 20:29:55 +0800 Subject: [PATCH 18/38] vringh: add regression test for cyclic indirect descriptor Add a case to tools/virtio/vringh_test.c that builds a top-level indirect descriptor whose NEXT points back at itself and checks that vringh_getdesc_user() rejects it with -ELOOP. Without the preceding fix, the walk re-enters the same top-level descriptor without making forward progress: count stays flat, so the traversal limit is never reached and -ELOOP is never returned. With the fix, the top-level count advances on each re-entry and vringh_getdesc_user() returns -ELOOP once the traversal limit is reached. Use index 1 rather than 0 for the self-cycle, since returning from an indirect table is only performed for a positive up_next value. A self-cycle at index 0 would instead terminate the walk and would not reproduce the bug. Assisted-by: Hawkeye:GLM-5.3-flash Assisted-by: Qoder:Qwen3.8-Max Signed-off-by: Fang Xieyan Acked-by: Jason Wang Signed-off-by: Michael S. Tsirkin Message-ID: <20260922122955.69433-3-fangxy@xiaopeng.com> --- tools/virtio/vringh_test.c | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tools/virtio/vringh_test.c b/tools/virtio/vringh_test.c index 84961b9ab5ff..2a5d7f7d2d6c 100644 --- a/tools/virtio/vringh_test.c +++ b/tools/virtio/vringh_test.c @@ -458,6 +458,8 @@ int main(int argc, char *argv[]) int err; unsigned i; void *ret; + struct vring_desc *ind; + char *data; bool (*getrange)(struct vringh *vrh, u64 addr, struct vringh_range *r); bool fast_vringh = false, parallel = false; @@ -755,6 +757,45 @@ int main(int argc, char *argv[]) vringh_iov_cleanup(&riov); } + /* + * Regression test: a top-level indirect descriptor whose NEXT + * points back to itself must be rejected with -ELOOP instead of + * looping forever. Use index 1 rather than 0 so that returning + * from the indirect table re-enters the same top-level descriptor. + */ + ind = __user_addr_max - USER_MEM/2; + data = __user_addr_max - USER_MEM/4; + + /* Fresh ring and host state; resets last_avail_idx to 0. */ + vring_init(&vrh.vring, RINGSIZE, __user_addr_min, ALIGN); + vringh_init_user(&vrh, vdev.features, RINGSIZE, true, + vrh.vring.desc, vrh.vring.avail, vrh.vring.used); + + /* Single-entry indirect table pointing at valid data. */ + ind[0].addr = (unsigned long)data; + ind[0].len = 1; + ind[0].flags = 0; + + /* Top-level desc[1]: INDIRECT, and NEXT loops back to itself. */ + vrh.vring.desc[1].addr = (unsigned long)ind; + vrh.vring.desc[1].len = sizeof(*ind); + vrh.vring.desc[1].flags = VRING_DESC_F_INDIRECT | VRING_DESC_F_NEXT; + vrh.vring.desc[1].next = 1; + + /* Publish head 1 on the avail ring. */ + vrh.vring.avail->ring[0] = 1; + vrh.vring.avail->idx = 1; + + vringh_iov_init(&riov, host_riov, ARRAY_SIZE(host_riov)); + vringh_iov_init(&wiov, host_wiov, ARRAY_SIZE(host_wiov)); + + err = vringh_getdesc_user(&vrh, &riov, &wiov, getrange, &head); + if (err != -ELOOP) + errx(1, "self-referential indirect: %i not -ELOOP", err); + + vringh_iov_cleanup(&riov); + vringh_iov_cleanup(&wiov); + /* Don't leak memory... */ vring_del_virtqueue(vq); free(__user_addr_min); From f4f6e9a9cb349003d333f0776de94f12ac95b8d2 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Tue, 22 Sep 2026 17:11:41 -0400 Subject: [PATCH 19/38] virtio_blk: Reset device on probe error paths after DRIVER_OK virtblk_probe() marks the device ready before revalidating zones and adding the disk, and either of those can fail. The out_cleanup_disk path they take goes on to delete the virtqueues, but nothing tells the device to stop first, so it is still live when the rings are freed. Reset the device at out_cleanup_disk, which is only reachable after virtio_device_ready(). virtblk_remove() and virtblk_freeze_priv() both reset before deleting the virtqueues for the same reason. Fixes: dbb301f91fc8 ("virtio_blk: add error handling support for add_disk()") Signed-off-by: Yuho Choi Reviewed-by: Stefan Hajnoczi Signed-off-by: Michael S. Tsirkin Message-ID: <20260922211207.523999-1-oss.patchbox@gmail.com> --- drivers/block/virtio_blk.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c index 603bf92de5ba..ddbb05d3e98c 100644 --- a/drivers/block/virtio_blk.c +++ b/drivers/block/virtio_blk.c @@ -1590,6 +1590,7 @@ static int virtblk_probe(struct virtio_device *vdev) return 0; out_cleanup_disk: + virtio_reset_device(vdev); put_disk(vblk->disk); out_free_tags: blk_mq_free_tag_set(&vblk->tag_set); From 60a792b97933a686316737838cd007d27963011e Mon Sep 17 00:00:00 2001 From: Fang Xieyan Date: Sun, 27 Sep 2026 00:07:55 +0800 Subject: [PATCH 20/38] vringh: reject empty / undersized indirect descriptor tables move_to_indirect() validates an indirect descriptor table only with "len % sizeof(struct vring_desc)". A descriptor flagged VRING_DESC_F_INDIRECT with len == 0 passes that test, so *desc_max is set to 0 while *descs points at the empty table. __vringh_iov() then copies one struct vring_desc from descs[0] -- 16 bytes past the end of the table -- before the "indirect_count > desc_max" loop detection aborts the walk. The over-read value is discarded when the walk aborts and is never used to map anything, but the access itself is out of bounds. Reject any len smaller than one descriptor, alongside the existing stride check, so an empty table is refused with -EINVAL and no descriptor is ever fetched from it. Fixes: f87d0fbb5798 ("vringh: host-side implementation of virtio rings.") Cc: stable@vger.kernel.org Assisted-by: Hawkeye:GLM-5.3-flash Assisted-by: Qoder:Qwen3.8-Max Signed-off-by: Fang Xieyan Message-ID: <20260926160755.21485-1-fangxy@xiaopeng.com> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/vringh.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/vhost/vringh.c b/drivers/vhost/vringh.c index e242ae0c1e16..5094715dd8bc 100644 --- a/drivers/vhost/vringh.c +++ b/drivers/vhost/vringh.c @@ -197,8 +197,9 @@ static int move_to_indirect(const struct vringh *vrh, } len = vringh32_to_cpu(vrh, desc->len); - if (unlikely(len % sizeof(struct vring_desc))) { - vringh_bad("Strange indirect len %u", desc->len); + if (unlikely(len < sizeof(struct vring_desc) || + len % sizeof(struct vring_desc))) { + vringh_bad("Invalid indirect len %u", desc->len); return -EINVAL; } From 01c3a5fa6d641c93c7cef4fb25c5f12938316e18 Mon Sep 17 00:00:00 2001 From: Shubham Antil Date: Tue, 20 Oct 2026 16:25:03 +0530 Subject: [PATCH 21/38] vhost-scsi: zero misaligned I/O bounce pages The misaligned I/O fallback copies the full bounce scatterlist to the request buffer during completion. If a data-in command transfers less than requested, part of a newly allocated bounce page can remain unwritten. Allocate bounce pages with __GFP_ZERO so all bytes copied during completion have a defined value. This path is already the slow fallback for misaligned I/O, so the added initialization cost is acceptable. Found during an LLM-assisted source audit and validated with a QEMU/KVM vhost-scsi target using misaligned data-in commands. Fixes: 5ced58bfa132 ("vhost-scsi: Fix alignment handling with windows") Cc: stable@vger.kernel.org Assisted-by: LLM (OpenAI Codex, gpt-5.6-sol) Signed-off-by: Shubham Antil Reviewed-by: Mike Christie Signed-off-by: Michael S. Tsirkin Message-ID: <20261020105503.75904-1-shubham@octane.security> --- drivers/vhost/scsi.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 1a0b75cc4cef..8b166bf5ae77 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -944,7 +944,12 @@ vhost_scsi_copy_iov_to_sgl(struct vhost_scsi_cmd *cmd, struct iov_iter *iter, } for_each_sgtable_sg(sg_table, sg, i) { - page = alloc_page(GFP_KERNEL); + /* + * Completion copies the full bounce buffer, including bytes the + * target did not write. This is already the slow fallback for + * misaligned I/O, so zeroing is acceptable here. + */ + page = alloc_page(GFP_KERNEL | __GFP_ZERO); if (!page) { ret = -ENOMEM; goto err; From e244725e86be02cd85cb878b3ed8570eba2e4be1 Mon Sep 17 00:00:00 2001 From: Fang Xieyan Date: Wed, 21 Oct 2026 07:29:56 +0800 Subject: [PATCH 22/38] vhost: fix used ring index wrap in vhost_add_used_n_in_order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vhost_add_used_n_in_order() advances start by nheads[i] but wraps it with a single subtraction. If start + nheads[i] >= 2 * vq->num, start can remain outside the ring. With used-ring logging enabled, start > vq->num makes (vq->num - start) underflow. log_write() then sets bits beyond the dirty-log bitmap, potentially corrupting adjacent userspace memory. This is reachable on the vhost-net TX path with VIRTIO_F_IN_ORDER: done_idx can reach VHOST_NET_BATCH (64), triggering the issue with a 32-entry ring. Wrap start with a mask to keep it within the ring. VHOST_SET_VRING_NUM enforces a nonzero power-of-two ring size, allowing start to be wrapped using & (vq->num - 1). Fixes: 67a873df0c41 ("vhost: basic in order support") Cc: stable@vger.kernel.org Reviewed-by: Weibin Liu Signed-off-by: Fang Xieyan Acked-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: <20261020232956.99970-1-fangxy@xiaopeng.com> --- drivers/vhost/vhost.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 9ce46445a46b..ea127af58ada 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -3188,8 +3188,7 @@ static int vhost_add_used_n_in_order(struct vhost_virtqueue *vq, } start += nheads[i]; new += nheads[i]; - if (start >= vq->num) - start -= vq->num; + start &= vq->num - 1; } if (unlikely(vq->log_used)) { From 1f8efac19a0870cdfdf8477e447e5a07ddce9623 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Sun, 4 Oct 2026 14:21:10 -0400 Subject: [PATCH 23/38] iommu/virtio: Reset device before deleting virtqueues on probe failure viommu_probe() marks the device DRIVER_OK before populating the event virtqueue and registering the IOMMU device in sysfs. viommu_fill_evtq() hands the device a set of device-writable buffers through virtqueue_add_inbuf(), so from that point on the device may write into them and into the rings. If either step fails, the error path deletes the virtqueues without resetting the device first. The event buffers are allocated with devm_kmalloc_array() and are released as probe unwinds, so the device can go on writing to memory that has been freed. Reset the device before deleting the virtqueues, the way viommu_remove() already does. Fixes: edcd69ab9a32 ("iommu: Add virtio-iommu driver") Cc: stable@vger.kernel.org Signed-off-by: Yuho Choi Signed-off-by: Michael S. Tsirkin Message-ID: <20261004182218.177343-1-oss.patchbox@gmail.com> --- drivers/iommu/virtio-iommu.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/iommu/virtio-iommu.c b/drivers/iommu/virtio-iommu.c index 587fc13197f1..fa72ae23b8af 100644 --- a/drivers/iommu/virtio-iommu.c +++ b/drivers/iommu/virtio-iommu.c @@ -1227,12 +1227,12 @@ static int viommu_probe(struct virtio_device *vdev) /* Populate the event queue with buffers */ ret = viommu_fill_evtq(viommu); if (ret) - goto err_free_vqs; + goto err_reset_vdev; ret = iommu_device_sysfs_add(&viommu->iommu, dev, NULL, "%s", virtio_bus_name(vdev)); if (ret) - goto err_free_vqs; + goto err_reset_vdev; vdev->priv = viommu; @@ -1244,6 +1244,8 @@ static int viommu_probe(struct virtio_device *vdev) return 0; +err_reset_vdev: + virtio_reset_device(vdev); err_free_vqs: vdev->config->del_vqs(vdev); From f41f153d49b4c18172ec61b69ba934c36e1be745 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Mon, 5 Oct 2026 00:47:45 -0400 Subject: [PATCH 24/38] vdpa/snet: Don't discard the DPU error in snet_send_ctrl_msg() snet_send_ctrl_msg() reads the error code the DPU reports when it ACKs a control message, but then overwrites it with the return value of snet_wait_for_dpu_completion(). As a result, a DESTROY, SUSPEND or RESUME that the DPU rejects is reported to the caller as a success as long as the DPU clears the opcode register in time. For SUSPEND this means vhost-vdpa marks the device as suspended and lets userspace reprogram the vrings while the DPU is still running them; for DESTROY, snet_reset_dev() never even sees that the device failed to stop. Return the error from the saved ACK value once the completion wait itself has succeeded. Fixes: 3f3a1675b731 ("vdpa/snet: support getting and setting VQ state") Cc: stable@vger.kernel.org Signed-off-by: Yuho Choi Signed-off-by: Michael S. Tsirkin Message-ID: <20261005044915.264532-1-oss.patchbox@gmail.com> --- drivers/vdpa/solidrun/snet_ctrl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/solidrun/snet_ctrl.c b/drivers/vdpa/solidrun/snet_ctrl.c index e284c3a06717..09afa0487fd6 100644 --- a/drivers/vdpa/solidrun/snet_ctrl.c +++ b/drivers/vdpa/solidrun/snet_ctrl.c @@ -291,7 +291,6 @@ static int snet_send_ctrl_msg(struct snet *snet, u16 opcode, u16 vq_idx) /* Check for errors */ val = snet_read_ctrl(regs); - ret = SNET_VAL_TO_ERR(val); /* Clear the chunk ready bit */ val &= ~SNET_CTRL_CHUNK_RDY_MASK; @@ -301,6 +300,8 @@ static int snet_send_ctrl_msg(struct snet *snet, u16 opcode, u16 vq_idx) if (ret) SNET_WARN(pdev, "Timeout waiting for DPU to complete a control command, err %d\n", ret); + else + ret = SNET_VAL_TO_ERR(val); exit: mutex_unlock(&snet->ctrl_lock); From 1f5bc40e33d8b3e5e3b89047956099b0c19f1d3d Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Mon, 12 Oct 2026 18:39:54 +0800 Subject: [PATCH 25/38] vhost-scsi: do not count PI bytes in the data length vhost_scsi_handle_vq() moves pi_bytesout or pi_bytesin into the protection sgl and subtracts those bytes from exp_data_len. The length passed to TCM is still exp_data_len + prot_bytes. prot_bytes comes from virtio_scsi_cmd_req_pi. With VIRTIO_SCSI_F_T10_PI negotiated, vhost reads that header and does not check that the CDB is a READ or WRITE. sbc_check_prot() replaces data_length from the CDB only when the protect field is non-zero. target_cmd_size_check() leaves a write's data_length unchanged when the CDB size is smaller, so a command that does not get that replacement keeps a data_length larger than the data sgl. The KASAN report is a MODE SELECT whose header had pi_bytesout set. The data sgl held only the parameter list. spc_emulate_modeselect() walked data_length and read the next physical page. Pass the data sgl length. Protected READ and WRITE still take their length from the CDB in sbc_check_prot(). KASAN reports: BUG: KASAN: use-after-free in spc_emulate_modeselect+0x145/0x380 [target_core_mod] Read of size 1 spc_emulate_modeselect __target_execute_cmd target_execute_cmd vhost_scsi_write_pending transport_generic_new_cmd __target_submit target_queued_submit_work process_one_work worker_thread kthread ret_from_fork ret_from_fork_asm Fixes: 9f977ef7b671 ("vhost-scsi: Include prot_bytes into expected data transfer length") Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20261012103954.136698-1-physicalmtea@gmail.com> --- drivers/vhost/scsi.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 8b166bf5ae77..2e060c76ce76 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -1546,9 +1546,17 @@ vhost_scsi_handle_vq(struct vhost_scsi *vs, struct vhost_virtqueue *vq) * vhost_scsi_queue_data_in() and vhost_scsi_queue_status() */ cmd->tvc_vq_desc = vc.head; + /* + * vhost_scsi_mapal() maps the PI bytes into cmd->prot_table. + * It maps the remaining exp_data_len bytes into cmd->table, + * which is the data SGL passed to the target core. Its + * expected length must therefore exclude prot_bytes. + * + * sbc_check_prot() derives the data length for protected READ + * and WRITE commands from the CDB. + */ vhost_scsi_target_queue_cmd(nexus, cmd, cdb, lun, task_attr, - data_direction, - exp_data_len + prot_bytes); + data_direction, exp_data_len); ret = 0; err: /* From 3d8c5b714c186d08c8fe8c8cfd1f9b89525ab067 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Fri, 9 Oct 2026 17:09:12 +0800 Subject: [PATCH 26/38] vhost-scsi: validate event queue descriptor direction vhost_get_vq_desc() returns the number of device readable (output) and device writable (input) iovec segments. A guest can submit an eventq descriptor chain that fills vq->iov with output segments, setting out to ARRAY_SIZE(vq->iov) and in to zero. When the host later queues a vhost-scsi event, vhost_scsi_do_evt_work() uses out as the index of the first input segment. This accesses one element beyond vq->iov and triggers an array-index-out-of-bounds report. With panic_on_warn enabled, the report panics the host. The event queue accepts device writable buffers only. Reject chains that contain output segments or lack an input segment before using out as an index. The input check prevents the out-of-bounds access, while the output check validates the eventq descriptor direction. I found the descriptor direction issue myself, though the patch was written with AI assistance. The out-of-bounds read with an output-only chain was separately reported by Jia Jia. Fixes: a6c9af87363c ("tcm_vhost: Add hotplug/hotunplug support") Reported-by: Jia Jia Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng Sun Message-ID: <20261009-fix-arrary-index-out-of-bounds-in-vhost_scsi_complete_events-v2-1-1f7689a54735@gmail.com> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/scsi.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 2e060c76ce76..7a168de61001 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -638,6 +638,16 @@ vhost_scsi_do_evt_work(struct vhost_scsi *vs, struct vhost_scsi_evt *evt) return; } + /* + * Eventq buffers are device-writable only. A nonzero in count + * guarantees that out is within the bounds of vq->iov. + */ + if (unlikely(out || !in)) { + vq_err(vq, "Invalid event buffer: out %u, in %u\n", out, in); + vs->vs_events_missed = true; + return; + } + if ((vq->iov[out].iov_len != sizeof(struct virtio_scsi_event))) { vq_err(vq, "Expecting virtio_scsi_event, got %zu bytes\n", vq->iov[out].iov_len); From e498eb93f961db1a1dfdabd373500f0d4019c9a4 Mon Sep 17 00:00:00 2001 From: sungbyeongchan Date: Thu, 8 Oct 2026 14:04:23 +0900 Subject: [PATCH 27/38] virtio_ring: use shadow flags when detaching split descriptors Split virtqueues save descriptor flags and next indexes in desc_extra before publishing descriptors to the device. The detach path uses the shadow next index, but decides whether to continue by rereading the NEXT flag from the shared descriptor. Use the shadow flags for the continuation decision as well. This keeps all detach metadata in the same driver-owned snapshot and avoids an unnecessary shared-ring read. Fixes: 72b5e8958738 ("virtio-ring: store DMA metadata in desc_extra for split virtqueue") Acked-by: Michael S. Tsirkin Assisted-by: LLM Signed-off-by: sungbyeongchan Signed-off-by: Michael S. Tsirkin Message-ID: <20261008050423.32221-1-tjdqudcks0424@naver.com> --- drivers/virtio/virtio_ring.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index d0dc4645b5b3..bec008123ce8 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -910,7 +910,6 @@ static unsigned detach_buf_split_in_order(struct vring_virtqueue *vq, { struct vring_desc_extra *extra; unsigned int i; - __virtio16 nextflag = cpu_to_virtio16(vq->vq.vdev, VRING_DESC_F_NEXT); /* Clear data ptr. */ vq->split.desc_state[head].data = NULL; @@ -920,7 +919,7 @@ static unsigned detach_buf_split_in_order(struct vring_virtqueue *vq, /* Put back on free list: unmap first-level descriptors and find end */ i = head; - while (vq->split.vring.desc[i].flags & nextflag) { + while (extra[i].flags & VRING_DESC_F_NEXT) { i = vring_unmap_one_split(vq, &extra[i]); vq->vq.num_free++; } From a543346b96a174fcee6812ac2764acc8dc993358 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Sat, 10 Oct 2026 20:22:58 +0800 Subject: [PATCH 28/38] vhost-scsi: reject delayed virtqueue setup vhost_scsi_set_endpoint() allocates command pools only for I/O virtqueues that are already set up, but publishes the backend for every virtqueue. Userspace can complete a skipped queue after attaching the endpoint, then kick it with scsi_tags uninitialized and trigger the alloc_hint warning in sbitmap_get(). Delayed virtqueue setup is not supported. Leave skipped I/O virtqueues inactive and reject changes to the vring size or addresses while an endpoint is attached. Userspace must clear the endpoint before changing the vring configuration. I found this bug myself, though the patch was written with AI assistance. Fixes: 25b98b64e284 ("vhost scsi: alloc cmds per vq instead of session") Assisted-by: OpenAI-Codex:GPT-5.6 Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20261010-fix-warning-in-sbitmap_get-v3-1-33de562d1359@gmail.com> --- drivers/vhost/scsi.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 7a168de61001..3fc7a115e8ab 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -2135,6 +2135,9 @@ vhost_scsi_set_endpoint(struct vhost_scsi *vs, for (i = 0; i < vs->dev.nvqs; i++) { vq = &vs->vqs[i].vq; + if (i >= VHOST_SCSI_VQ_IO && !vs->vqs[i].scsi_cmds) + continue; + mutex_lock(&vq->mutex); vhost_vq_set_backend(vq, vs_tpg); vhost_vq_init_access(vq); @@ -2496,10 +2499,16 @@ vhost_scsi_ioctl(struct file *f, default: mutex_lock(&vs->dev.mutex); r = vhost_dev_ioctl(&vs->dev, ioctl, argp); - if (r == -ENOIOCTLCMD) - r = vhost_vring_ioctl(&vs->dev, ioctl, argp); - else + if (r == -ENOIOCTLCMD) { + if (vs->vs_tpg && + (ioctl == VHOST_SET_VRING_NUM || + ioctl == VHOST_SET_VRING_ADDR)) + r = -EBUSY; + else + r = vhost_vring_ioctl(&vs->dev, ioctl, argp); + } else { vhost_scsi_flush(vs); + } mutex_unlock(&vs->dev.mutex); return r; } From 509f6bd229e94fc39506e01be692c4aa22338b49 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Sun, 11 Oct 2026 15:33:30 +0800 Subject: [PATCH 29/38] vhost: reject backend enable without a kick file Enabling a backend without VHOST_SET_VRING_KICK dereferences a NULL vq->kick in vhost_poll_start(). Reject the request with -EINVAL. Fixes: 264ee5aa81ec ("tools/virtio: Add --reset") Assisted-by: OpenAI-Codex:GPT-5.6 Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20261011-fix-null-pointer-in-vhost_test_set_backend-v1-1-60b2f12d64c6@gmail.com> --- drivers/vhost/test.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/vhost/test.c b/drivers/vhost/test.c index 84ed9ea81219..65dc8c71336d 100644 --- a/drivers/vhost/test.c +++ b/drivers/vhost/test.c @@ -287,6 +287,10 @@ static long vhost_test_set_backend(struct vhost_test *n, unsigned index, int fd) r = -EFAULT; goto err_vq; } + if (enable && !vq->kick) { + r = -EINVAL; + goto err_vq; + } if (!enable) { vhost_poll_stop(&vq->poll); backend = vhost_vq_get_backend(vq); From 4b03d1e7f13613af708441a061556de99891a516 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Sun, 11 Oct 2026 18:28:24 +0800 Subject: [PATCH 30/38] vhost-vdpa: initialize state before publishing cdev Initialize the completion and as hash buckets before cdev_device_add() makes the device accessible. Fixes: 4c8cf31885f6 ("vhost: introduce vDPA-based backend") Assisted-by: OpenAI-Codex:GPT-5.6 Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20261011-fix-null-pointer-in-vhost_vdpa_release-v2-1-66ba929971ce@gmail.com> --- drivers/vhost/vdpa.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index f2ef7b852b2b..c912ac40ca37 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -1699,7 +1699,7 @@ static int vhost_vdpa_probe(struct vdpa_device *vdpa) return minor; } - atomic_set(&v->opened, 0); + atomic_set(&v->opened, 1); spin_lock_init(&v->config_lock); v->minor = minor; v->vdpa = vdpa; @@ -1722,16 +1722,17 @@ static int vhost_vdpa_probe(struct vdpa_device *vdpa) cdev_init(&v->cdev, &vhost_vdpa_fops); v->cdev.owner = THIS_MODULE; + init_completion(&v->completion); + for (i = 0; i < VHOST_VDPA_IOTLB_BUCKETS; i++) + INIT_HLIST_HEAD(&v->as[i]); r = cdev_device_add(&v->cdev, &v->dev); if (r) goto err; - init_completion(&v->completion); vdpa_set_drvdata(vdpa, v); - for (i = 0; i < VHOST_VDPA_IOTLB_BUCKETS; i++) - INIT_HLIST_HEAD(&v->as[i]); + atomic_set_release(&v->opened, 0); return 0; From 7a2609bcb8b223443696e8cddd7ab91831e015eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Thu, 15 Oct 2026 11:40:49 +0200 Subject: [PATCH 31/38] MAINTAINERS: add missing virtio/vdpa/vduse/vhost headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several headers related to virtio, vDPA, VDUSE and vhost were not covered by any specific MAINTAINERS entry and fell through to the catch-all THE REST section: - include/linux/device-id/virtio.h: not matched by the existing "include/linux/virtio*.h" glob, which only matches files directly under include/linux/ and does not recurse into subdirectories. - include/uapi/linux/vdpa.h: the in-kernel include/linux/vdpa.h was listed under VIRTIO CORE but its UAPI companion was missing; the "include/uapi/linux/virtio_*.h" pattern does not match it because the filename lacks the "virtio_" prefix. - include/uapi/linux/vduse.h: there is no dedicated VDUSE section in MAINTAINERS; the driver lives under drivers/vdpa/ which is already covered by VIRTIO CORE, so the UAPI header belongs there too. - include/uapi/linux/vhost_types.h: the VIRTIO HOST (VHOST) section explicitly lists include/uapi/linux/vhost.h but omits its companion types header. Assisted-by: claude-sonnet 4.6 Signed-off-by: Eugenio Pérez Reviewed-by: Stefano Garzarella Signed-off-by: Michael S. Tsirkin Message-ID: <20261015094049.107999-1-eperezma@redhat.com> --- MAINTAINERS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 93abbb315065..d4258dfbc59f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29610,9 +29610,12 @@ F: drivers/block/virtio_blk.c F: drivers/crypto/virtio/ F: drivers/vdpa/ F: drivers/virtio/ +F: include/linux/device-id/virtio.h F: include/linux/vdpa.h F: include/linux/virtio*.h F: include/linux/vringh.h +F: include/uapi/linux/vdpa.h +F: include/uapi/linux/vduse.h F: include/uapi/linux/virtio_*.h F: net/vmw_vsock/virtio* F: tools/virtio/ @@ -29686,6 +29689,7 @@ F: drivers/vhost/ F: include/linux/sched/vhost_task.h F: include/linux/vhost_iotlb.h F: include/uapi/linux/vhost.h +F: include/uapi/linux/vhost_types.h F: kernel/vhost_task.c VIRTIO HOST (VHOST-SCSI) From 161aa708b02cc997a51701c342f3cebade0b5164 Mon Sep 17 00:00:00 2001 From: Mingbing Zhai Date: Fri, 16 Oct 2026 10:54:29 +0800 Subject: [PATCH 32/38] virtio_balloon: initialize wakeup_lock before virtqueues are set up Since c578123e63da ("virtio_balloon: Treat stats requests as wakeup events") the stats_request() virtqueue callback takes vb->wakeup_lock via start_wakeup_event(). init_vqs() registers that callback and even primes and kicks the stats queue, so the lock is reachable from irq context as soon as init_vqs() returns, long before the driver sets DRIVER_OK: vring_interrupt() dispatches to the callback without any DRIVER_OK check unless CONFIG_VIRTIO_HARDEN_NOTIFICATION is set, and that option depends on BROKEN. syzbot hit it from a threaded irq: INFO: trying to register non-static key in stats_request ... assign_lock_key register_lock_class lock_acquire rt_spin_lock start_wakeup_event drivers/virtio/virtio_balloon.c:150 [inline] stats_request drivers/virtio/virtio_balloon.c:435 The lock is only initialized near the end of virtballoon_probe(), after init_vqs() and several steps that can still fail and unwind through del_vqs(), where a racing interrupt still finds the callback registered. Until then it is whatever kzalloc() left in vb: lockdep complains about the unregistered lock class, and wakeup_signal_mask and processing_wakeup_event get updated from irq context while probe is still running. The rt_spin_lock() in the report is just spin_lock() on PREEMPT_RT; any PROVE_LOCKING build takes the same path. stop_update_lock(), which the same callback also acquires, is already initialized before init_vqs(); do the same for wakeup_lock. Fixes: c578123e63da ("virtio_balloon: Treat stats requests as wakeup events") Reported-by: syzbot+13df72e0dc4be5c62215@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=13df72e0dc4be5c62215 Signed-off-by: Mingbing Zhai Signed-off-by: Michael S. Tsirkin Message-ID: <20261016025429.901-1-zhaimingbing@cmss.chinamobile.com> --- drivers/virtio/virtio_balloon.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index ab3e3d887e00..8c1b47ec17a2 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c @@ -942,6 +942,7 @@ static int virtballoon_probe(struct virtio_device *vdev) INIT_WORK(&vb->update_balloon_stats_work, update_balloon_stats_func); INIT_WORK(&vb->update_balloon_size_work, update_balloon_size_func); spin_lock_init(&vb->stop_update_lock); + spin_lock_init(&vb->wakeup_lock); mutex_init(&vb->balloon_lock); init_waitqueue_head(&vb->acked); vb->vdev = vdev; @@ -1049,8 +1050,6 @@ static int virtballoon_probe(struct virtio_device *vdev) goto out_unregister_oom; } - spin_lock_init(&vb->wakeup_lock); - /* * The virtio balloon itself can't wake up the device, but it is * responsible for processing wakeup events passed up from the transport From 57c1c0d172b4bab5be7d44166c405d54db70a200 Mon Sep 17 00:00:00 2001 From: Wei-Shuo Chen Date: Fri, 16 Oct 2026 15:23:18 +0800 Subject: [PATCH 33/38] virtio: use consistent virtqueue terminology Use virtqueue consistently in the notify function comments across the MMIO and PCI transports. Assisted-by: LLM Signed-off-by: Wei-Shuo Chen Signed-off-by: Michael S. Tsirkin Message-ID: <20261016072318.24015-1-tavhadia@gmail.com> --- drivers/virtio/virtio_mmio.c | 2 +- drivers/virtio/virtio_pci_common.c | 2 +- drivers/virtio/virtio_pci_common.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c index faae58e3401a..e2ec0ec01d67 100644 --- a/drivers/virtio/virtio_mmio.c +++ b/drivers/virtio/virtio_mmio.c @@ -270,7 +270,7 @@ static void vm_reset(struct virtio_device *vdev) /* Transport interface */ -/* the notify function used when creating a virt queue */ +/* the notify function used when creating a virtqueue */ static bool vm_notify(struct virtqueue *vq) { struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vq->vdev); diff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c index 24af84b0c6ad..284daff7ece6 100644 --- a/drivers/virtio/virtio_pci_common.c +++ b/drivers/virtio/virtio_pci_common.c @@ -47,7 +47,7 @@ void vp_synchronize_vectors(struct virtio_device *vdev) synchronize_irq(pci_irq_vector(vp_dev->pci_dev, i)); } -/* the notify function used when creating a virt queue */ +/* the notify function used when creating a virtqueue */ bool vp_notify(struct virtqueue *vq) { /* we write the queue's selector into the notification register to diff --git a/drivers/virtio/virtio_pci_common.h b/drivers/virtio/virtio_pci_common.h index 8cd01de27baf..18b6dbdfd1e5 100644 --- a/drivers/virtio/virtio_pci_common.h +++ b/drivers/virtio/virtio_pci_common.h @@ -127,7 +127,7 @@ static struct virtio_pci_device *to_vp_device(struct virtio_device *vdev) /* wait for pending irq handlers */ void vp_synchronize_vectors(struct virtio_device *vdev); -/* the notify function used when creating a virt queue */ +/* the notify function used when creating a virtqueue */ bool vp_notify(struct virtqueue *vq); /* the config->del_vqs() implementation */ void vp_del_vqs(struct virtio_device *vdev); From 0f7bf47f5a0cc26320aa4b14f4504d03a07f4b74 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Fri, 16 Oct 2026 12:36:40 +0200 Subject: [PATCH 34/38] hwrng: virtio - prevent use-after-free after a failed restore remove_common() frees the driver data during freeze, but does not clear vdev->priv, which points to freed memory. If probe_common() then fails during restore, a later remove or freeze can dereference the dangling pointer. Clear vdev->priv on the probe_common() error cleanup path and before freeing the driver data in remove_common(). Also reset the device and return early from remove_common() if vdev->priv is already NULL for a safe teardown after a failed restore. Fixes: 08e53fbdb85c ("virtio-rng: support multiple virtio-rng devices") Cc: stable@vger.kernel.org Signed-off-by: Thorsten Blum Reviewed-by: PrasannaKumar Muralidharan . Signed-off-by: Michael S. Tsirkin Message-ID: <20261016103640.4989-2-blum@kernel.org> --- drivers/char/hw_random/virtio-rng.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/char/hw_random/virtio-rng.c b/drivers/char/hw_random/virtio-rng.c index f9982217247d..45d87c0614a5 100644 --- a/drivers/char/hw_random/virtio-rng.c +++ b/drivers/char/hw_random/virtio-rng.c @@ -190,6 +190,7 @@ static int probe_common(struct virtio_device *vdev) err_find: ida_free(&rng_index_ida, index); err_ida: + vdev->priv = NULL; kfree(vi); return err; } @@ -198,6 +199,11 @@ static void remove_common(struct virtio_device *vdev) { struct virtrng_info *vi = vdev->priv; + if (!vi) { + virtio_reset_device(vdev); + return; + } + vi->hwrng_removed = true; vi->data_avail = 0; vi->data_idx = 0; @@ -207,6 +213,7 @@ static void remove_common(struct virtio_device *vdev) virtio_reset_device(vdev); vdev->config->del_vqs(vdev); ida_free(&rng_index_ida, vi->index); + vdev->priv = NULL; kfree(vi); } From bb2e6f166547838fd7c320b01c90918b736f76e2 Mon Sep 17 00:00:00 2001 From: "chenglong.ma" Date: Mon, 2 Nov 2026 12:08:55 -0500 Subject: [PATCH 35/38] vduse: fix VA/PA confusion in the coherent page fault path vduse_domain_get_coherent_page() assumes that iotlb entries above bounce_size were installed by vduse_domain_alloc_coherent(), so it treats map->addr as a physical address. But vduse registers with use_va = true, and when the device is bound to vhost_vdpa, vhost_vdpa_va_map() stores a userspace virtual address in map->addr, which vduse_domain_set_map() then copies into the same iotlb. A process holding the domain file descriptor across a rebind can therefore fault in an arbitrary physical page with read/write access, ignoring the entry permissions. Additionally, since the domain file mmap lacks VM_IO, the domain file itself can be mapped and passed to vhost_vdpa_va_map(), producing self-referential entries that bypass the coherent path check. To fix, only translate entries whose backing file is the domain file itself in vduse_domain_get_coherent_page(), reject such entries in vduse_domain_set_map() since only external mappings should be imported, and set VM_IO on the domain file mmap so vhost_vdpa_va_map() refuses to map it in the first place. Fixes: 8c773d53fb7b ("vduse: Implement an MMU-based software IOTLB") Cc: Xie Yongji Cc: stable@vger.kernel.org Reported-by: chenglong.ma Signed-off-by: chenglong.ma Signed-off-by: Michael S. Tsirkin Co-Authored-By: Claude Code --- drivers/vdpa/vdpa_user/iova_domain.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/vdpa_user/iova_domain.c b/drivers/vdpa/vdpa_user/iova_domain.c index b6c958224b7c..28a977248a0d 100644 --- a/drivers/vdpa/vdpa_user/iova_domain.c +++ b/drivers/vdpa/vdpa_user/iova_domain.c @@ -76,6 +76,10 @@ int vduse_domain_set_map(struct vduse_iova_domain *domain, for (map = vhost_iotlb_itree_first(iotlb, start, last); map; map = vhost_iotlb_itree_next(map, start, last)) { map_file = (struct vdpa_map_file *)map->opaque; + if (!map_file || map_file->file == domain->file) { + ret = -EINVAL; + goto err; + } ret = vduse_iotlb_add_range(domain, map->start, map->last, map->addr, map->perm, map_file->file, @@ -226,6 +230,7 @@ vduse_domain_get_coherent_page(struct vduse_iova_domain *domain, u64 iova) { u64 start = iova & PAGE_MASK; u64 last = start + PAGE_SIZE - 1; + struct vdpa_map_file *map_file; struct vhost_iotlb_map *map; struct page *page = NULL; @@ -234,6 +239,10 @@ vduse_domain_get_coherent_page(struct vduse_iova_domain *domain, u64 iova) if (!map) goto out; + map_file = (struct vdpa_map_file *)map->opaque; + if (!map_file || map_file->file != domain->file) + goto out; + page = pfn_to_page((map->addr + iova - map->start) >> PAGE_SHIFT); get_page(page); out: @@ -579,7 +588,7 @@ static int vduse_domain_mmap(struct file *file, struct vm_area_struct *vma) { struct vduse_iova_domain *domain = file->private_data; - vm_flags_set(vma, VM_DONTDUMP | VM_DONTEXPAND); + vm_flags_set(vma, VM_IO | VM_DONTDUMP | VM_DONTEXPAND); vma->vm_private_data = domain; vma->vm_ops = &vduse_domain_mmap_ops; From 058fc59326af3350c6e830858e56d2aeecabe0a2 Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Fri, 23 Oct 2026 18:40:35 +0800 Subject: [PATCH 36/38] vhost-scsi: fix page reference leak in misaligned I/O fallback iov_iter_get_pages2() returns a byte count that excludes the offset into the first page, but also returns a reference to that page in pages[]. On the misaligned-I/O fallback, vhost_scsi_revert_map_iov_to_sgl() puts pages already added to the SG list. The cleanup loop starts at pages[npages], but accounts for its first page as PAGE_SIZE bytes rather than PAGE_SIZE - offset. If the remaining data starts at a non-zero offset, it can finish one page early and leave a page reference behind. Use PAGE_SIZE - offset for the first remaining page and clear offset for later pages, as in the mapping loop. Without this, each affected request leaks one page reference. Fixes: bca939d5bcd0 ("vhost-scsi: Dynamically allocate scatterlists") Reviewed-by: Mike Christie Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20261023104035.6686-1-physicalmtea@gmail.com> --- drivers/vhost/scsi.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 3fc7a115e8ab..e44044449cef 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -900,11 +900,16 @@ vhost_scsi_map_to_sgl(struct vhost_scsi_cmd *cmd, vhost_scsi_revert_map_iov_to_sgl(iter, *sgl, sg); iov_iter_revert(iter, bytes); + /* + * pages[npages] is the first page not added to the SG list. + * The remaining data starts at offset in that page. + */ while (bytes) { - n = min_t(unsigned int, PAGE_SIZE, bytes); + n = min_t(unsigned int, PAGE_SIZE - offset, bytes); put_page(pages[npages++]); bytes -= n; + offset = 0; } return -EINVAL; From 92368f4fb8ff40b87faa4acf1775720ffaf5749d Mon Sep 17 00:00:00 2001 From: zhang jiao Date: Thu, 29 Oct 2026 15:25:10 +0800 Subject: [PATCH 37/38] virtio_ring: Initialize id field in packed indirect descriptors The virtqueue_add_indirect_packed() function fills an indirect descriptor table with addr, len, and flags for each entry, but does not initialize the id field of struct vring_packed_desc. The table is allocated via kmalloc() (not kzalloc) and then DMA-mapped with DMA_TO_DEVICE, making the uninitialized 2-byte id field of each descriptor readable by the device. This leaks kernel heap memory to the device for every packed indirect descriptor entry. A malicious or compromised device could extract heap contents by reading the id fields from the DMA-mapped indirect descriptor tables. This affects all virtio devices that use packed virtqueues with indirect descriptors, which is the common configuration for modern virtio devices. Fix this by explicitly initializing desc[i].id to zero in the descriptor filling loop. The id field is unused in indirect descriptor table entries per the virtio specification (only the head descriptor in the main ring uses id for buffer identification), so setting it to zero is semantically correct. This matches the split ring path where all four fields of struct vring_desc (addr, len, flags, next) are explicitly written in virtqueue_add_desc_split(). Signed-off-by: zhang jiao Signed-off-by: Michael S. Tsirkin Message-ID: <20261029072511.851-1-zhangjiao2@cmss.chinamobile.com> --- drivers/virtio/virtio_ring.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index bec008123ce8..f683a2d6a200 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -1575,6 +1575,11 @@ static int virtqueue_add_indirect_packed(struct vring_virtqueue *vq, 0 : VRING_DESC_F_WRITE); desc[i].addr = cpu_to_le64(addr); desc[i].len = cpu_to_le32(len); + /* The id field is unused in indirect descriptors but + * must be initialized to prevent leaking kernel heap + * memory to the device through the DMA-mapped table. + */ + desc[i].id = 0; if (unlikely(vq->use_map_api)) { extra[i].addr = premapped ? DMA_MAPPING_ERROR : addr; From 1a577f9402c1780cc1e663f48d5dd54c02051b09 Mon Sep 17 00:00:00 2001 From: "Zenghui Yu (Huawei)" Date: Thu, 29 Oct 2026 20:16:48 +0800 Subject: [PATCH 38/38] virtio_pci: don't take the MSI mutex under cpu_hotplug_lock Lockdep reports the following circular locking dependency when a virtio-net device is probed with lockdep enabled on a system whose devices sit behind an IOMMU: ====================================================== WARNING: possible circular locking dependency detected 7.3.0-rc1+ #84 Not tainted ------------------------------------------------------ kworker/u34:0/57 is trying to acquire lock: ffff800104f83ee8 (&md->mutex){+.+.}-{4:4}, at: msi_domain_get_virq+0xf0/0x118 but task is already holding lock: ffffc00081edd930 (cpu_hotplug_lock){++++}-{0:0}, at: cpus_read_lock+0x10/0x1c which lock already depends on the new lock. Chain exists of: &md->mutex --> &group->mutex --> cpu_hotplug_lock The cycle is composed of three dependencies: * cpu_hotplug_lock -> &md->mutex (which triggers the splat). virtio_net's init_vqs() calls cpus_read_lock() around virtnet_set_affinity(), which reaches vp_set_vq_affinity(). That calls pci_irq_vector(), which takes the per-device MSI mutex (&dev->msi.data->mutex) via msi_domain_get_virq(). * &md->mutex -> &domain->mutex -> &group->mutex. MSI-X allocation of any device behind the IOMMU holds the MSI mutex while allocating interrupts through the IRQ domain hierarchy. The MSI parent domain (GICv2m, or ITS likewise) calls iommu_dma_prepare_msi() from its .alloc() to map the MSI doorbell, which takes the IOMMU group mutex. * &group->mutex -> cpu_hotplug_lock. bus_iommu_probe() calls iommu_setup_dma_ops() under the group mutex; initializing the IOVA rcaches registers a cpuhp instance, which takes cpu_hotplug_lock. The cycle only became complete since commit 288683c92b1a ("iommu: Make iommu_dma_prepare_msi() into a generic operation"), where iommu_dma_prepare_msi() switched to use the group mutex. pci_irq_vector() is a pure lookup of a vector that is bound to the queue for the whole lifetime of the queue, so there is no reason to take the MSI mutex for it from cpu hotplug locked contexts. Cache the IRQ number in virtio_pci_vq_info {} at vector binding time and make vp_set_vq_affinity() use the cached value. This also covers the CPU hotplug callbacks (virtnet_cpu_online/virtnet_cpu_dead), which call virtnet_set_affinity() with cpu_hotplug_lock already held. Signed-off-by: Zenghui Yu (Huawei) Signed-off-by: Michael S. Tsirkin Message-ID: <20261029121648.22155-1-zenghui.yu@linux.dev> --- drivers/virtio/virtio_pci_common.c | 8 +++++++- drivers/virtio/virtio_pci_common.h | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c index 284daff7ece6..79d3395f3705 100644 --- a/drivers/virtio/virtio_pci_common.c +++ b/drivers/virtio/virtio_pci_common.c @@ -224,6 +224,12 @@ static struct virtqueue *vp_setup_vq(struct virtio_device *vdev, unsigned int in goto out_info; info->vq = vq; + + if (msix_vec != VIRTIO_MSI_NO_VECTOR) + info->virq = pci_irq_vector(vp_dev->pci_dev, msix_vec); + else + info->virq = 0; + if (callback) { spin_lock_irqsave(&vp_dev->lock, flags); if (!vp_is_slow_path_vector(msix_vec)) @@ -571,7 +577,7 @@ int vp_set_vq_affinity(struct virtqueue *vq, const struct cpumask *cpu_mask) if (vp_dev->msix_enabled) { mask = vp_dev->msix_affinity_masks[info->msix_vector]; - irq = pci_irq_vector(vp_dev->pci_dev, info->msix_vector); + irq = info->virq; if (!cpu_mask) irq_update_affinity_hint(irq, NULL); else { diff --git a/drivers/virtio/virtio_pci_common.h b/drivers/virtio/virtio_pci_common.h index 18b6dbdfd1e5..4ce903e7d3a9 100644 --- a/drivers/virtio/virtio_pci_common.h +++ b/drivers/virtio/virtio_pci_common.h @@ -40,6 +40,9 @@ struct virtio_pci_vq_info { /* MSI-X vector (or none) */ unsigned int msix_vector; + + /* Linux IRQ number of msix_vector, or 0 if not mapped */ + unsigned int virq; }; struct virtio_pci_admin_vq {