When the VDUSE handler processes VDUSE_UPDATE_IOTLB, it removes the IOTLB cache entry but fails to invalidate vring pointers that were derived from that mapping. Since VDUSE also unmaps the memory via munmap(), the vring pointers (vq->desc, vq->avail, vq->used) become dangling while vq->access_ok remains true, causing use-after-free.
Add the missing vring invalidation logic to the VDUSE_UPDATE_IOTLB handler, mirroring what the vhost-user path already does correctly in VHOST_IOTLB_INVALIDATE handling. Move is_vring_iotlb() to vhost.c so it can be shared by both vhost_user.c and vduse.c. CVE-2026-86567 Bugzilla ID: 2029 Cc: [email protected] Reported-by: Ivar Al-Safawi <[email protected]> Acked-by: Kevin Traynor <[email protected]> Signed-off-by: Maxime Coquelin <[email protected]> --- lib/vhost/vduse.c | 24 ++++++++++++- lib/vhost/vhost.c | 76 ++++++++++++++++++++++++++++++++++++++++++ lib/vhost/vhost.h | 2 ++ lib/vhost/vhost_user.c | 75 ----------------------------------------- 4 files changed, 101 insertions(+), 76 deletions(-) diff --git a/lib/vhost/vduse.c b/lib/vhost/vduse.c index f8a4a8edcb..58c8f4090b 100644 --- a/lib/vhost/vduse.c +++ b/lib/vhost/vduse.c @@ -442,13 +442,35 @@ vduse_events_handler(int fd, void *arg, int *close __rte_unused) dev->reconnect_log->status = dev->status; resp.result = VDUSE_REQ_RESULT_OK; break; - case VDUSE_UPDATE_IOTLB: + case VDUSE_UPDATE_IOTLB: { + struct vhost_iotlb_msg imsg; + uint32_t i; + VHOST_CONFIG_LOG(dev->ifname, INFO, "\tIOVA range: %" PRIx64 " - %" PRIx64, (uint64_t)req.iova.start, (uint64_t)req.iova.last); vhost_user_iotlb_cache_remove(dev, req.iova.start, req.iova.last - req.iova.start + 1); + + /* Invalidate vring translations if affected by this range */ + imsg.iova = req.iova.start; + imsg.size = req.iova.last - req.iova.start + 1; + + for (i = 0; i < dev->nr_vring; i++) { + vq = dev->virtqueue[i]; + + if (!vq) + continue; + + if (is_vring_iotlb(dev, vq, &imsg)) { + rte_rwlock_write_lock(&vq->access_lock); + vring_invalidate(dev, vq); + rte_rwlock_write_unlock(&vq->access_lock); + } + } + resp.result = VDUSE_REQ_RESULT_OK; break; + } default: resp.result = VDUSE_REQ_RESULT_FAILED; break; diff --git a/lib/vhost/vhost.c b/lib/vhost/vhost.c index fde8acb00c..2d694d586e 100644 --- a/lib/vhost/vhost.c +++ b/lib/vhost/vhost.c @@ -566,6 +566,82 @@ vring_translate(struct virtio_net *dev, struct vhost_virtqueue *vq) return 0; } +static int +is_vring_iotlb_split(struct vhost_virtqueue *vq, struct vhost_iotlb_msg *imsg) +{ + struct vhost_vring_addr *ra; + uint64_t start, end, len; + + start = imsg->iova; + end = start + imsg->size; + + ra = &vq->ring_addrs; + len = sizeof(struct vring_desc) * vq->size; + if (ra->desc_user_addr < end && (ra->desc_user_addr + len) > start) + return 1; + + len = sizeof(struct vring_avail) + sizeof(uint16_t) * vq->size; + if (ra->avail_user_addr < end && (ra->avail_user_addr + len) > start) + return 1; + + len = sizeof(struct vring_used) + + sizeof(struct vring_used_elem) * vq->size; + if (ra->used_user_addr < end && (ra->used_user_addr + len) > start) + return 1; + + if (ra->flags & (1 << VHOST_VRING_F_LOG)) { + len = sizeof(uint64_t); + if (ra->log_guest_addr < end && + (ra->log_guest_addr + len) > start) + return 1; + } + + return 0; +} + +static int +is_vring_iotlb_packed(struct vhost_virtqueue *vq, struct vhost_iotlb_msg *imsg) +{ + struct vhost_vring_addr *ra; + uint64_t start, end, len; + + start = imsg->iova; + end = start + imsg->size; + + ra = &vq->ring_addrs; + len = sizeof(struct vring_packed_desc) * vq->size; + if (ra->desc_user_addr < end && (ra->desc_user_addr + len) > start) + return 1; + + len = sizeof(struct vring_packed_desc_event); + if (ra->avail_user_addr < end && (ra->avail_user_addr + len) > start) + return 1; + + len = sizeof(struct vring_packed_desc_event); + if (ra->used_user_addr < end && (ra->used_user_addr + len) > start) + return 1; + + if (ra->flags & (1 << VHOST_VRING_F_LOG)) { + len = sizeof(uint64_t); + if (ra->log_guest_addr < end && + (ra->log_guest_addr + len) > start) + return 1; + } + + return 0; +} + +int +is_vring_iotlb(struct virtio_net *dev, + struct vhost_virtqueue *vq, + struct vhost_iotlb_msg *imsg) +{ + if (vq_is_packed(dev)) + return is_vring_iotlb_packed(vq, imsg); + else + return is_vring_iotlb_split(vq, imsg); +} + void vring_invalidate(struct virtio_net *dev __rte_unused, struct vhost_virtqueue *vq) { diff --git a/lib/vhost/vhost.h b/lib/vhost/vhost.h index bb4708aed5..6bed0b6717 100644 --- a/lib/vhost/vhost.h +++ b/lib/vhost/vhost.h @@ -909,6 +909,8 @@ uint64_t translate_log_addr(struct virtio_net *dev, struct vhost_virtqueue *vq, __rte_requires_shared_capability(&vq->iotlb_lock); void vring_invalidate(struct virtio_net *dev, struct vhost_virtqueue *vq) __rte_requires_capability(&vq->access_lock); +int is_vring_iotlb(struct virtio_net *dev, struct vhost_virtqueue *vq, + struct vhost_iotlb_msg *imsg); static __rte_always_inline uint64_t vhost_iova_to_vva(struct virtio_net *dev, struct vhost_virtqueue *vq, diff --git a/lib/vhost/vhost_user.c b/lib/vhost/vhost_user.c index 018adfe4b2..15f13ef443 100644 --- a/lib/vhost/vhost_user.c +++ b/lib/vhost/vhost_user.c @@ -2878,81 +2878,6 @@ vhost_user_set_req_fd(struct virtio_net **pdev, return RTE_VHOST_MSG_RESULT_OK; } -static int -is_vring_iotlb_split(struct vhost_virtqueue *vq, struct vhost_iotlb_msg *imsg) -{ - struct vhost_vring_addr *ra; - uint64_t start, end, len; - - start = imsg->iova; - end = start + imsg->size; - - ra = &vq->ring_addrs; - len = sizeof(struct vring_desc) * vq->size; - if (ra->desc_user_addr < end && (ra->desc_user_addr + len) > start) - return 1; - - len = sizeof(struct vring_avail) + sizeof(uint16_t) * vq->size; - if (ra->avail_user_addr < end && (ra->avail_user_addr + len) > start) - return 1; - - len = sizeof(struct vring_used) + - sizeof(struct vring_used_elem) * vq->size; - if (ra->used_user_addr < end && (ra->used_user_addr + len) > start) - return 1; - - if (ra->flags & (1 << VHOST_VRING_F_LOG)) { - len = sizeof(uint64_t); - if (ra->log_guest_addr < end && - (ra->log_guest_addr + len) > start) - return 1; - } - - return 0; -} - -static int -is_vring_iotlb_packed(struct vhost_virtqueue *vq, struct vhost_iotlb_msg *imsg) -{ - struct vhost_vring_addr *ra; - uint64_t start, end, len; - - start = imsg->iova; - end = start + imsg->size; - - ra = &vq->ring_addrs; - len = sizeof(struct vring_packed_desc) * vq->size; - if (ra->desc_user_addr < end && (ra->desc_user_addr + len) > start) - return 1; - - len = sizeof(struct vring_packed_desc_event); - if (ra->avail_user_addr < end && (ra->avail_user_addr + len) > start) - return 1; - - len = sizeof(struct vring_packed_desc_event); - if (ra->used_user_addr < end && (ra->used_user_addr + len) > start) - return 1; - - if (ra->flags & (1 << VHOST_VRING_F_LOG)) { - len = sizeof(uint64_t); - if (ra->log_guest_addr < end && - (ra->log_guest_addr + len) > start) - return 1; - } - - return 0; -} - -static int is_vring_iotlb(struct virtio_net *dev, - struct vhost_virtqueue *vq, - struct vhost_iotlb_msg *imsg) -{ - if (vq_is_packed(dev)) - return is_vring_iotlb_packed(vq, imsg); - else - return is_vring_iotlb_split(vq, imsg); -} - static int vhost_user_get_config(struct virtio_net **pdev, struct vhu_msg_context *ctx, -- 2.56.0

