From 1ed35ac7f3fe2b4396bdd29ac3a7f0ebc0829e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linfeng=20Sun=C2=A0?= Date: Sat, 20 Jun 2026 21:00:05 +0800 Subject: vhost_iotlb: bound map allocation in add_range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vhost_iotlb_add_range_ctx() only retires an old entry when the table has a non-zero limit, has exactly reached that limit and has VHOST_IOTLB_FLAG_RETIRE set. Non-retiring tables can keep allocating entries after reaching their configured limit. Existing vhost devices allocate their IOTLB with max_iotlb_entries from vhost.c, which defaults to 2048 and is tunable by module parameter. Use the caller-provided limit at the allocation point instead of adding a separate default in the common IOTLB helper, and reject non-positive values in vhost paths that can report an error. Other vhost IOTLB users should not create zero-limit tables when entries can be populated from userspace or guest-controlled requests. Add caller-side max_iotlb_entries parameters for mlx5 vDPA, VDUSE and vhost-vDPA. Reject non-positive VDUSE and vhost-vDPA values, and require at least two entries for vdpa_sim and mlx5 vDPA paths that install full-range mappings, since those mappings are split into two IOTLB entries. Handle full-range mappings in the common helper by checking that the IOTLB can hold both split entries before inserting the first half. This avoids returning an error after leaving a half mapping behind. When the table is full, keep the existing retire behavior for retiring tables and return -ENOSPC for non-retiring tables. Reuse the retired map node instead of freeing it and allocating a replacement, so a stream of IOTLB updates cannot keep forcing GFP_ATOMIC allocations after the table has reached its limit. If a zero-limit IOTLB still reaches the common helper, treat it as a configuration error and return -EINVAL. I found this bug myself, though the patch was written with AI assistance. Fixes: 0bbe30668d89 ("vhost: factor out IOTLB") Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng SunĀ  Message-ID: Signed-off-by: Michael S. Tsirkin --- drivers/vdpa/mlx5/core/mlx5_vdpa.h | 2 ++ drivers/vdpa/mlx5/core/mr.c | 5 +++- drivers/vdpa/mlx5/core/resources.c | 11 ++++++++- drivers/vdpa/vdpa_sim/vdpa_sim.c | 10 +++++--- drivers/vdpa/vdpa_user/iova_domain.c | 11 ++++++++- drivers/vhost/iotlb.c | 47 +++++++++++++++++++++++++----------- drivers/vhost/vdpa.c | 9 ++++++- drivers/vhost/vhost.c | 8 ++++++ 8 files changed, 82 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/vdpa/mlx5/core/mlx5_vdpa.h b/drivers/vdpa/mlx5/core/mlx5_vdpa.h index 2cedf7e2dbc4..42f2f44b383c 100644 --- a/drivers/vdpa/mlx5/core/mlx5_vdpa.h +++ b/drivers/vdpa/mlx5/core/mlx5_vdpa.h @@ -11,6 +11,8 @@ #define MLX5V_ETH_HARD_MTU (ETH_HLEN + VLAN_HLEN + ETH_FCS_LEN) +extern int mlx5_vdpa_max_iotlb_entries; + struct mlx5_vdpa_direct_mr { u64 start; u64 end; diff --git a/drivers/vdpa/mlx5/core/mr.c b/drivers/vdpa/mlx5/core/mr.c index 6d02ccf9eb91..7d681961a5cc 100644 --- a/drivers/vdpa/mlx5/core/mr.c +++ b/drivers/vdpa/mlx5/core/mr.c @@ -776,6 +776,9 @@ static int _mlx5_vdpa_create_mr(struct mlx5_vdpa_dev *mvdev, { int err; + if (mlx5_vdpa_max_iotlb_entries < 2) + return -EINVAL; + if (iotlb) err = create_user_mr(mvdev, mr, iotlb); else @@ -784,7 +787,7 @@ static int _mlx5_vdpa_create_mr(struct mlx5_vdpa_dev *mvdev, if (err) return err; - mr->iotlb = vhost_iotlb_alloc(0, 0); + mr->iotlb = vhost_iotlb_alloc(mlx5_vdpa_max_iotlb_entries, 0); if (!mr->iotlb) { err = -ENOMEM; goto err_mr; diff --git a/drivers/vdpa/mlx5/core/resources.c b/drivers/vdpa/mlx5/core/resources.c index aeae31d0cefa..28a4d7a35bf4 100644 --- a/drivers/vdpa/mlx5/core/resources.c +++ b/drivers/vdpa/mlx5/core/resources.c @@ -3,8 +3,14 @@ #include #include +#include #include "mlx5_vdpa.h" +int mlx5_vdpa_max_iotlb_entries = 2048; +module_param_named(max_iotlb_entries, mlx5_vdpa_max_iotlb_entries, int, 0444); +MODULE_PARM_DESC(max_iotlb_entries, + "Maximum number of iotlb entries. (default: 2048)"); + static int alloc_pd(struct mlx5_vdpa_dev *dev, u32 *pdn, u16 uid) { struct mlx5_core_dev *mdev = dev->mdev; @@ -229,7 +235,10 @@ int mlx5_vdpa_destroy_mkey(struct mlx5_vdpa_dev *mvdev, u32 mkey) static int init_ctrl_vq(struct mlx5_vdpa_dev *mvdev) { - mvdev->cvq.iotlb = vhost_iotlb_alloc(0, 0); + if (mlx5_vdpa_max_iotlb_entries < 2) + return -EINVAL; + + mvdev->cvq.iotlb = vhost_iotlb_alloc(mlx5_vdpa_max_iotlb_entries, 0); if (!mvdev->cvq.iotlb) return -ENOMEM; diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim.c b/drivers/vdpa/vdpa_sim/vdpa_sim.c index 8cb1cc2ea139..4d116644851d 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim.c @@ -34,7 +34,7 @@ MODULE_PARM_DESC(batch_mapping, "Batched mapping 1 -Enable; 0 - Disable"); static int max_iotlb_entries = 2048; module_param(max_iotlb_entries, int, 0444); MODULE_PARM_DESC(max_iotlb_entries, - "Maximum number of iotlb entries for each address space. 0 means unlimited. (default: 2048)"); + "Maximum number of iotlb entries for each address space. (default: 2048)"); static bool use_va = true; module_param(use_va, bool, 0444); @@ -201,6 +201,8 @@ struct vdpasim *vdpasim_create(struct vdpasim_dev_attr *dev_attr, if (!dev_attr->alloc_size) return ERR_PTR(-EINVAL); + if (max_iotlb_entries < 2) + return ERR_PTR(-EINVAL); if (config->mask & BIT_ULL(VDPA_ATTR_DEV_FEATURES)) { if (config->device_features & @@ -261,8 +263,10 @@ struct vdpasim *vdpasim_create(struct vdpasim_dev_attr *dev_attr, for (i = 0; i < vdpasim->dev_attr.nas; i++) { vhost_iotlb_init(&vdpasim->iommu[i], max_iotlb_entries, 0); - vhost_iotlb_add_range(&vdpasim->iommu[i], 0, ULONG_MAX, 0, - VHOST_MAP_RW); + ret = vhost_iotlb_add_range(&vdpasim->iommu[i], 0, ULONG_MAX, + 0, VHOST_MAP_RW); + if (ret) + goto err_iommu; vdpasim->iommu_pt[i] = true; } diff --git a/drivers/vdpa/vdpa_user/iova_domain.c b/drivers/vdpa/vdpa_user/iova_domain.c index 4dc76c0d0d13..b6c958224b7c 100644 --- a/drivers/vdpa/vdpa_user/iova_domain.c +++ b/drivers/vdpa/vdpa_user/iova_domain.c @@ -12,11 +12,17 @@ #include #include #include +#include #include #include #include "iova_domain.h" +static int max_iotlb_entries = 2048; +module_param(max_iotlb_entries, int, 0444); +MODULE_PARM_DESC(max_iotlb_entries, + "Maximum number of iotlb entries. (default: 2048)"); + static int vduse_iotlb_add_range(struct vduse_iova_domain *domain, u64 start, u64 last, u64 addr, unsigned int perm, @@ -622,11 +628,14 @@ vduse_domain_create(unsigned long iova_limit, size_t bounce_size) if (iova_limit <= bounce_size) return NULL; + if (max_iotlb_entries <= 0) + return NULL; + domain = kzalloc_obj(*domain); if (!domain) return NULL; - domain->iotlb = vhost_iotlb_alloc(0, 0); + domain->iotlb = vhost_iotlb_alloc(max_iotlb_entries, 0); if (!domain->iotlb) goto err_iotlb; diff --git a/drivers/vhost/iotlb.c b/drivers/vhost/iotlb.c index e1414c774c34..a1d4376a5b87 100644 --- a/drivers/vhost/iotlb.c +++ b/drivers/vhost/iotlb.c @@ -20,6 +20,14 @@ INTERVAL_TREE_DEFINE(struct vhost_iotlb_map, rb, __u64, __subtree_last, START, LAST, static inline, vhost_iotlb_itree); +static void vhost_iotlb_map_unlink(struct vhost_iotlb *iotlb, + struct vhost_iotlb_map *map) +{ + vhost_iotlb_itree_remove(map, &iotlb->root); + list_del(&map->link); + iotlb->nmaps--; +} + /** * vhost_iotlb_map_free - remove a map node and free it * @iotlb: the IOTLB @@ -28,10 +36,8 @@ INTERVAL_TREE_DEFINE(struct vhost_iotlb_map, void vhost_iotlb_map_free(struct vhost_iotlb *iotlb, struct vhost_iotlb_map *map) { - vhost_iotlb_itree_remove(map, &iotlb->root); - list_del(&map->link); + vhost_iotlb_map_unlink(iotlb, map); kfree(map); - iotlb->nmaps--; } EXPORT_SYMBOL_GPL(vhost_iotlb_map_free); @@ -57,14 +63,25 @@ int vhost_iotlb_add_range_ctx(struct vhost_iotlb *iotlb, if (last < start) return -EFAULT; + if (!iotlb->limit) + return -EINVAL; + /* If the range being mapped is [0, ULONG_MAX], split it into two entries * otherwise its size would overflow u64. */ if (start == 0 && last == ULONG_MAX) { u64 mid = last / 2; - int err = vhost_iotlb_add_range_ctx(iotlb, start, mid, addr, - perm, opaque); + int err; + + if (iotlb->limit < 2) + return -ENOSPC; + if (!(iotlb->flags & VHOST_IOTLB_FLAG_RETIRE) && + iotlb->nmaps > iotlb->limit - 2) + return -ENOSPC; + + err = vhost_iotlb_add_range_ctx(iotlb, start, mid, addr, + perm, opaque); if (err) return err; @@ -72,17 +89,19 @@ int vhost_iotlb_add_range_ctx(struct vhost_iotlb *iotlb, start = mid + 1; } - if (iotlb->limit && - iotlb->nmaps == iotlb->limit && - iotlb->flags & VHOST_IOTLB_FLAG_RETIRE) { - map = list_first_entry(&iotlb->list, typeof(*map), link); - vhost_iotlb_map_free(iotlb, map); + if (iotlb->nmaps >= iotlb->limit) { + if (iotlb->flags & VHOST_IOTLB_FLAG_RETIRE) { + map = list_first_entry(&iotlb->list, typeof(*map), link); + vhost_iotlb_map_unlink(iotlb, map); + } else { + return -ENOSPC; + } + } else { + map = kmalloc_obj(*map, GFP_ATOMIC); + if (!map) + return -ENOMEM; } - map = kmalloc_obj(*map, GFP_ATOMIC); - if (!map) - return -ENOMEM; - map->start = start; map->size = last - start + 1; map->last = last; diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index ac55275fa0d0..ef642bc9f97e 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -34,6 +34,11 @@ enum { #define VHOST_VDPA_DEV_MAX (1U << MINORBITS) +static int max_iotlb_entries = 2048; +module_param(max_iotlb_entries, int, 0444); +MODULE_PARM_DESC(max_iotlb_entries, + "Maximum number of iotlb entries. (default: 2048)"); + #define VHOST_VDPA_IOTLB_BUCKETS 16 struct vhost_vdpa_as { @@ -109,12 +114,14 @@ static struct vhost_vdpa_as *vhost_vdpa_alloc_as(struct vhost_vdpa *v, u32 asid) if (asid >= v->vdpa->nas) return NULL; + if (max_iotlb_entries <= 0) + return NULL; as = kmalloc_obj(*as); if (!as) return NULL; - vhost_iotlb_init(&as->iotlb, 0, 0); + vhost_iotlb_init(&as->iotlb, max_iotlb_entries, 0); as->id = asid; hlist_add_head(&as->hash_link, head); diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 4c525b3e16ea..ae29a7ef7baa 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -1137,6 +1137,9 @@ EXPORT_SYMBOL_GPL(vhost_dev_set_owner); static struct vhost_iotlb *iotlb_alloc(void) { + if (max_iotlb_entries <= 0) + return NULL; + return vhost_iotlb_alloc(max_iotlb_entries, VHOST_IOTLB_FLAG_RETIRE); } @@ -1981,6 +1984,8 @@ static long vhost_set_memory(struct vhost_dev *d, struct vhost_memory __user *m) return -EOPNOTSUPP; if (mem.nregions > max_mem_regions) return -E2BIG; + if (max_iotlb_entries <= 0) + return -EINVAL; newmem = kvzalloc_flex(*newmem, regions, mem.nregions); if (!newmem) return -ENOMEM; @@ -2275,6 +2280,9 @@ int vhost_init_device_iotlb(struct vhost_dev *d) struct vhost_iotlb *niotlb, *oiotlb; int i; + if (max_iotlb_entries <= 0) + return -EINVAL; + niotlb = iotlb_alloc(); if (!niotlb) return -ENOMEM; -- cgit v1.2.3 From 0619aaa34c0c2a2dcb07f0e9c8a34e7efb8c4cdf Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Wed, 24 Jun 2026 15:02:02 -0700 Subject: vhost/vdpa: reject overflowing PA map page counts on 32-bit vhost_vdpa_pa_map() adds the IOVA page offset to the user-controlled map size before computing the number of pages to pin. On 32-bit systems, where unsigned long is narrower than u64, that addition can overflow and the code can pin and map fewer pages than the requested IOTLB range. Reject sizes that overflow the unsigned long page-count calculation. Fixes: 22af48cf91aa ("vdpa: factor out vhost_vdpa_pa_map() and vhost_vdpa_pa_unmap()") Acked-by: Michael S. Tsirkin Signed-off-by: Yousef Alhouseen Signed-off-by: Michael S. Tsirkin Message-ID: --- drivers/vhost/vdpa.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index ef642bc9f97e..c3d913bd7cac 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -1109,6 +1109,7 @@ static int vhost_vdpa_pa_map(struct vhost_vdpa *v, 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 page_offset; u64 start = iova; long pinned; int ret = 0; @@ -1121,7 +1122,13 @@ static int vhost_vdpa_pa_map(struct vhost_vdpa *v, if (perm & VHOST_ACCESS_WO) gup_flags |= FOLL_WRITE; - npages = PFN_UP(size + (iova & ~PAGE_MASK)); + page_offset = iova & ~PAGE_MASK; + if (size > ULONG_MAX - page_offset) { + ret = -EINVAL; + goto free; + } + + npages = PFN_UP(size + page_offset); if (!npages) { ret = -EINVAL; goto free; -- cgit v1.2.3 From 727e1f569855df83579edbd73dcb4a0723543a12 Mon Sep 17 00:00:00 2001 From: Christian Borntraeger Date: Mon, 6 Jul 2026 16:15:37 +0200 Subject: vdpa/mlx5: Fix buffer length in create_direct_keys() We have seen in our CI the following KASAN message: BUG: KASAN: slab-out-of-bounds in cmd_exec+0x550/0xca0 [mlx5_core] Read of size 272 at addr 0000000176795020 by task qemu-system-s39/82764 [...] [<000011388ab3a7a0>] cmd_exec+0x550/0xca0 [mlx5_core] [<000011388ab3b61c>] mlx5_cmd_exec_cb+0x25c/0x4f0 [mlx5_core] [<000011388b21e82e>] mlx5_vdpa_exec_async_cmds+0x22e/0x5e0 [mlx5_vdpa] [<000011388b21fd44>] create_direct_keys+0x954/0xef0 [mlx5_vdpa] [...] The buggy address is located 4128 bytes inside of allocated 4384-byte region [0000000176794000, 0000000176795120) So in essence we read 16 bytes beyond 4384-byte allocation. create_direct_keys calculates the pointer and length for in and out buffers. The size calculation for in includes the entire structure size (out + in + mtt[]) but the pointer passed to cmd_exec points only to the 'in' field, skipping the 'out' field. This causes mlx5_copy_to_msg() to read beyond the allocated buffer by sizeof(out) bytes when copying command data. Properly calculate the input size to match the pointer and allocation size. Fixes: 0071b138d44a ("vdpa/mlx5: Create direct MKEYs in parallel") Signed-off-by: Christian Borntraeger Tested-by: Dragos Tatulea Reviewed-by: Dragos Tatulea Signed-off-by: Michael S. Tsirkin Message-ID: <20260706141537.3510294-1-borntraeger@linux.ibm.com> --- drivers/vdpa/mlx5/core/mr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/vdpa/mlx5/core/mr.c b/drivers/vdpa/mlx5/core/mr.c index 7d681961a5cc..77a479aeaa85 100644 --- a/drivers/vdpa/mlx5/core/mr.c +++ b/drivers/vdpa/mlx5/core/mr.c @@ -233,7 +233,8 @@ static int create_direct_keys(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_mr * cmds[i].out = cmd_mem->out; cmds[i].outlen = sizeof(cmd_mem->out); cmds[i].in = cmd_mem->in; - cmds[i].inlen = struct_size(cmd_mem, mtt, mttcount); + cmds[i].inlen = struct_size(cmd_mem, mtt, mttcount) - + offsetof(struct mlx5_create_mkey_mem, in); fill_create_direct_mr(mvdev, dmr, cmd_mem); -- cgit v1.2.3 From d876c493fc4b811941bfeb4c80beb2dfc4bf025e Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Mon, 27 Jul 2026 16:18:41 +0800 Subject: vhost-scsi: Validate T10 PI scatterlist counts When T10 PI is negotiated, vhost-scsi splits protection bytes from the data iterator before mapping the request scatterlists. A malformed request can claim protection bytes that cover or exceed the full payload length. The former leaves no data bytes to map, while the latter underflows exp_data_len before advancing the iterator. Both cases can let a zero data SGL count reach sg_alloc_table_chained(), which triggers BUG_ON(!nents). Reject protection lengths that cover or exceed the payload before subtracting prot_bytes and advancing the iterator. Also propagate negative errors from the protection SGL calculation before calling the allocator, matching the data SGL path. Fixes: bca939d5bcd0 ("vhost-scsi: Dynamically allocate scatterlists") Suggested-by: Jia Jia Signed-off-by: Jia Jia Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng Sun Message-ID: <20260727081841.923151-1-slf@hdu.edu.cn> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/scsi.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 9a1253b9d8c5..c79197edb163 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -972,6 +972,9 @@ vhost_scsi_mapal(struct vhost_scsi *vs, struct vhost_scsi_cmd *cmd, if (prot_bytes) { sgl_count = vhost_scsi_calc_sgls(prot_iter, prot_bytes, VHOST_SCSI_PREALLOC_PROT_SGLS); + if (sgl_count < 0) + return sgl_count; + cmd->prot_table.sgl = cmd->prot_sgl; ret = sg_alloc_table_chained(&cmd->prot_table, sgl_count, cmd->prot_table.sgl, @@ -1416,6 +1419,11 @@ vhost_scsi_handle_vq(struct vhost_scsi *vs, struct vhost_virtqueue *vq) * actual data payload length. */ if (prot_bytes) { + if (prot_bytes >= exp_data_len) { + vq_err(vq, "Protection data exceeds payload length\n"); + goto err; + } + exp_data_len -= prot_bytes; prot_iter = data_iter; iov_iter_truncate(&prot_iter, prot_bytes); -- cgit v1.2.3 From de845981da67a6b049080c87e605130b0c30adc5 Mon Sep 17 00:00:00 2001 From: Jun Yang Date: Mon, 3 Aug 2026 09:45:14 +0800 Subject: vhost: reset the vring metadata cache on vring reconfiguration vq->meta_iotlb[] caches the vhost_iotlb_map that backs each vring metadata region, and iotlb_access_ok() returns early on a cache hit, taking the hit as proof that the region has already been validated: if (vhost_vq_meta_fetch(vq, addr, len, type)) return true; The cache is reset on VHOST_IOTLB_UPDATE and VHOST_IOTLB_INVALIDATE, on device IOTLB (re)initialisation and on vq reset, but not when VHOST_SET_VRING_ADDR replaces vq->desc, vq->avail and vq->used, nor when VHOST_SET_VRING_NUM changes the region sizes. With a device IOTLB attached both ioctls are accepted while the vq is live, and neither validates the addresses at ioctl time: vq_access_ok() and vq_log_used_access_ok() return true early because the addresses are GIOVAs, deferring validation to prefetch time. Once the cache has been populated that deferred validation no longer runs -- vq_meta_prefetch() hits the stale entry and returns true -- and vhost_vq_meta_fetch() keeps translating through the old mapping as map->addr + addr - map->start for an address the mapping no longer covers. vhost_copy_to_user() and vhost_copy_from_user() consume the result with __copy_to_user() and __copy_from_user(), which do not check it either, so a subsequent used ring update or descriptor fetch accesses memory outside the region the IOTLB actually maps. Reset the metadata cache whenever the vring is reconfigured, so the new addresses are pushed back through iotlb_access_ok()'s slow path. Fixes: f88949138058 ("vhost: introduce O(1) vq metadata cache") Cc: stable@vger.kernel.org Assisted-by: tencentos-corvus-ai:kimi-k3 Signed-off-by: Jun Yang Message-ID: <20260803014823.68623-1-juny24602@gmail.com> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/vhost.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index ae29a7ef7baa..269efad90369 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -2131,6 +2131,14 @@ static long vhost_vring_set_num_addr(struct vhost_dev *d, BUG(); } + /* + * The metadata cache holds the IOTLB mapping that backed the previous + * desc/avail/used addresses and vring size, both of which are being + * replaced here. iotlb_access_ok() takes a cache hit as proof that the + * region was validated, so the stale entries have to go. + */ + __vhost_vq_meta_reset(vq); + mutex_unlock(&vq->mutex); return r; -- cgit v1.2.3 From 22598f55a4c2b510b3df5e69e563387a963222ae Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Fri, 24 Jul 2026 14:09:19 +0800 Subject: vhost-scsi: flush backend after device ioctls vhost-scsi translates guest response descriptors into userspace iovecs when commands are submitted. Target-core completes those commands asynchronously, so VHOST_SET_MEM_TABLE can replace the memory table while an in-flight command still retains response iovecs translated through the old table. If the old mapping is reused after VHOST_SET_MEM_TABLE returns, command completion can write the response to an unrelated userspace object. Flush the vhost-scsi backend after vhost_dev_ioctl() handles a device ioctl. This waits for in-flight commands that can still use the old response iovecs before the ioctl returns. Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260724060919.1569170-1-physicalmtea@gmail.com> --- drivers/vhost/scsi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index c79197edb163..aae1164e1ca9 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -2434,9 +2434,10 @@ vhost_scsi_ioctl(struct file *f, default: mutex_lock(&vs->dev.mutex); r = vhost_dev_ioctl(&vs->dev, ioctl, argp); - /* TODO: flush backend after dev ioctl. */ if (r == -ENOIOCTLCMD) r = vhost_vring_ioctl(&vs->dev, ioctl, argp); + else + vhost_scsi_flush(vs); mutex_unlock(&vs->dev.mutex); return r; } -- cgit v1.2.3 From 42bc45df5905e2b7dccb72adaf7730f66cfbe03f Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Sun, 26 Jul 2026 22:43:14 +0800 Subject: vhost-scsi: reject feature changes after endpoint vhost_scsi_setup_vq_cmds() runs from VHOST_SCSI_SET_ENDPOINT and allocates each command's protection scatterlist array (prot_sgl) according to the acknowledged VIRTIO_SCSI_F_T10_PI bit. The command pools are not rebuilt when VHOST_SET_FEATURES changes that bit later. Although virtio feature bits must not change after feature negotiation, vhost_scsi_set_features() currently accepts such a request after the endpoint is active and updates acked_features. Enabling T10-PI after endpoint setup therefore leaves prot_sgl NULL while the I/O path follows the new feature bit. For a 129-page protection payload, vhost_scsi_mapal() passes the missing first chunk to sg_alloc_table_chained(): sg_alloc_table_chained(table, 129, first_chunk=NULL, nents_first_chunk=inline_sg_cnt) sg_pool_index() then hits: BUG_ON(nents > SG_CHUNK_SIZE); /* 129 > 128 */ The kernel reported the following call trace and register state: Call Trace: ? __sg_alloc_table+0x1d8/0x250 ? __pfx_vhost_run_work_list+0x10/0x10 [vhost] sg_alloc_table_chained+0x59/0xf0 ? __pfx_sg_pool_alloc+0x10/0x10 ? vhost_scsi_calc_sgls.constprop.0+0x43/0x60 [vhost_scsi] vhost_scsi_handle_vq+0xf02/0x1700 [vhost_scsi] ? __pfx_vhost_scsi_handle_vq+0x10/0x10 [vhost_scsi] vhost_scsi_handle_kick+0x37/0x50 [vhost_scsi] vhost_run_work_list+0x8e/0xd0 [vhost] vhost_task_fn+0xe1/0x210 ret_from_fork+0x348/0x540 RIP: 0010:0x4 CR2 = 0x4 RSP: 0018:ffffc90000dbf940 EFLAGS: 00010202 RAX: ffffffff82396810 RBX: ffff88811dc28b80 RCX: 0000000000000000 RDX: 0000000000000000 RSI: 0000000000000820 RDI: 0000000000000081 VHOST_F_LOG_ALL is a vhost-specific runtime feature and remains the only exception. Reject changes to any feature other than VHOST_F_LOG_ALL while the endpoint is active. This preserves the existing runtime log toggle while preventing feature-dependent command resources and data-path state from becoming inconsistent. Userspace must clear the endpoint before changing any other negotiated feature and set the endpoint up again afterward. Fixes: bf2d650391be ("vhost-scsi: Allocate T10 PI structs only when enabled") Signed-off-by: Jia Jia Reviewed-by: Stefan Hajnoczi Signed-off-by: Michael S. Tsirkin Message-ID: <20260726144314.1652934-1-physicalmtea@gmail.com> --- drivers/vhost/scsi.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index aae1164e1ca9..7a1f39a327da 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -2227,6 +2227,7 @@ static int vhost_scsi_set_features(struct vhost_scsi *vs, u64 features) { struct vhost_virtqueue *vq; bool is_log, was_log; + u64 old_features; int i; if (features & ~VHOST_SCSI_FEATURES) @@ -2242,6 +2243,14 @@ static int vhost_scsi_set_features(struct vhost_scsi *vs, u64 features) if (!vs->dev.nvqs) goto out; + old_features = vs->vqs[0].vq.acked_features; + if (vs->vs_tpg && + ((features ^ old_features) & + ~(1ULL << VHOST_F_LOG_ALL))) { + mutex_unlock(&vs->dev.mutex); + return -EBUSY; + } + is_log = features & (1 << VHOST_F_LOG_ALL); /* * All VQs should have same feature. -- cgit v1.2.3