[PATCH 13/14] vhost: fix indirect descriptor table double fetch
Maxime Coquelin
maxime.coquelin at redhat.com
Wed Oct 14 17:00:48 CEST 2026
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 <naseebdangi100 at gmail.com>
Acked-by: Kevin Traynor <ktraynor at redhat.com>
Signed-off-by: Maxime Coquelin <maxime.coquelin at redhat.com>
---
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
More information about the dev
mailing list