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

Reply via email to