The virtio descriptor length field was read multiple times from
guest-controlled memory, creating a TOCTOU (time-of-check-time-of-use)
vulnerability. A malicious guest could exploit this by modifying
desc->len between reads, causing the host to allocate a buffer of
one size but iterate over a different number of descriptors, leading
to an out-of-bounds read.

Affected functions:
- fill_vec_buf_packed_indirect(): desc->len read for allocation at
  one point, then re-read for loop bound calculation
- fill_vec_buf_split(): vq->desc[idx].len re-read for allocation
  after being captured in a local variable
- vdpa.c indirect descriptor handling: same pattern

Fix by capturing the descriptor length once in a local variable
and using that for all subsequent operations.

Also fix a memory leak in fill_vec_buf_packed_indirect() where
free_ind_table() was not called on the map_one_desc() error path.

CVE: CVE-2026-93683
Bugzilla ID: 2046

Fixes: d0c24508e1f ("vhost: add support for non-contiguous indirect descs 
tables")
Fixes: 2f3225a7d69 ("vhost: add vector filling support for packed ring")

Reported-by: Naseeb Dangi <[email protected]>
Acked-by: Kevin Traynor <[email protected]>
Signed-off-by: Maxime Coquelin <[email protected]>
---
 lib/vhost/vdpa.c       |  8 +++++---
 lib/vhost/virtio_net.c | 22 +++++++++++++---------
 2 files changed, 18 insertions(+), 12 deletions(-)

diff --git a/lib/vhost/vdpa.c b/lib/vhost/vdpa.c
index bc2dd8d2e1..ff3b4379aa 100644
--- a/lib/vhost/vdpa.c
+++ b/lib/vhost/vdpa.c
@@ -195,7 +195,9 @@ rte_vdpa_relay_vring_used(int vid, uint16_t qid, void 
*vring_m)
                        return -1;
 
                if (vq->desc[desc_id].flags & VRING_DESC_F_INDIRECT) {
-                       dlen = vq->desc[desc_id].len;
+                       uint64_t desc_len = vq->desc[desc_id].len;
+
+                       dlen = desc_len;
                        nr_descs = dlen / sizeof(struct vring_desc);
                        if (unlikely(nr_descs > vq->size))
                                return -1;
@@ -209,11 +211,11 @@ rte_vdpa_relay_vring_used(int vid, uint16_t qid, void 
*vring_m)
                        if (unlikely(!desc_ring))
                                return -1;
 
-                       if (unlikely(dlen < vq->desc[desc_id].len)) {
+                       if (unlikely(dlen < desc_len)) {
                                vhost_user_iotlb_rd_lock(vq);
                                idesc = vhost_alloc_copy_ind_table(dev, vq,
                                                vq->desc[desc_id].addr,
-                                               vq->desc[desc_id].len);
+                                               desc_len);
                                vhost_user_iotlb_rd_unlock(vq);
                                if (unlikely(!idesc))
                                        return -1;
diff --git a/lib/vhost/virtio_net.c b/lib/vhost/virtio_net.c
index ff4613df68..8bdddedccf 100644
--- a/lib/vhost/virtio_net.c
+++ b/lib/vhost/virtio_net.c
@@ -775,7 +775,9 @@ fill_vec_buf_split(struct virtio_net *dev, struct 
vhost_virtqueue *vq,
        *desc_chain_head = idx;
 
        if (vq->desc[idx].flags & VRING_DESC_F_INDIRECT) {
-               dlen = vq->desc[idx].len;
+               uint64_t desc_len = vq->desc[idx].len;
+
+               dlen = desc_len;
                nr_descs = dlen / sizeof(struct vring_desc);
                if (unlikely(nr_descs > vq->size))
                        return -1;
@@ -787,13 +789,13 @@ fill_vec_buf_split(struct virtio_net *dev, struct 
vhost_virtqueue *vq,
                if (unlikely(!descs))
                        return -1;
 
-               if (unlikely(dlen < vq->desc[idx].len)) {
+               if (unlikely(dlen < desc_len)) {
                        /*
                         * The indirect desc table is not contiguous
                         * in process VA space, we have to copy it.
                         */
                        idesc = vhost_alloc_copy_ind_table(dev, vq,
-                                       vq->desc[idx].addr, vq->desc[idx].len);
+                                       vq->desc[idx].addr, desc_len);
                        if (unlikely(!idesc))
                                return -1;
 
@@ -898,29 +900,29 @@ fill_vec_buf_packed_indirect(struct virtio_net *dev,
        uint16_t i;
        uint32_t nr_descs;
        uint16_t vec_id = *vec_idx;
-       uint64_t dlen;
+       uint64_t desc_len = desc->len;
+       uint64_t dlen = desc_len;
        struct vring_packed_desc *descs, *idescs = NULL;
 
-       dlen = desc->len;
        descs = (struct vring_packed_desc *)(uintptr_t)
                vhost_iova_to_vva(dev, vq, desc->addr, &dlen, VHOST_ACCESS_RO);
        if (unlikely(!descs))
                return -1;
 
-       if (unlikely(dlen < desc->len)) {
+       if (unlikely(dlen < desc_len)) {
                /*
                 * The indirect desc table is not contiguous
                 * in process VA space, we have to copy it.
                 */
                idescs = vhost_alloc_copy_ind_table(dev,
-                               vq, desc->addr, desc->len);
+                               vq, desc->addr, desc_len);
                if (unlikely(!idescs))
                        return -1;
 
                descs = idescs;
        }
 
-       nr_descs =  desc->len / sizeof(struct vring_packed_desc);
+       nr_descs = desc_len / sizeof(struct vring_packed_desc);
        if (unlikely(nr_descs >= vq->size)) {
                free_ind_table(idescs);
                return -1;
@@ -936,8 +938,10 @@ fill_vec_buf_packed_indirect(struct virtio_net *dev,
                *len += dlen;
                if (unlikely(map_one_desc(dev, vq, buf_vec, &vec_id,
                                                descs[i].addr, dlen,
-                                               perm)))
+                                               perm))) {
+                       free_ind_table(idescs);
                        return -1;
+               }
        }
        *vec_idx = vec_id;
 
-- 
2.56.0

Reply via email to