mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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: 4c8cf31885 ("vhost: introduce vDPA-based backend")
Assisted-by: OpenAI-Codex:GPT-5
Signed-off-by: Linfeng Sun <linfeng.sun.dev@gmail.com>
Message-ID: <20260911-fix-vdpa-iotlb-lifetime-v3-2-8766d371748e@gmail.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
This commit is contained in:
parent
8517ed4429
commit
acbf36deb5
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user