From 8517ed4429a2eefd002dce00ea46e23e6714350b Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Fri, 11 Sep 2026 17:00:58 +0800 Subject: 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); } } -- cgit v1.2.3