[PATCH] vhost: fix used ring index wrap in vhost_add_used_n_in_order

From: Fang Xieyan

Date: Tue Oct 20 2026 - 19:30:30 EST


vhost_add_used_n_in_order() advances start by nheads[i] but wraps it
with a single subtraction. If start + nheads[i] >= 2 * vq->num, start
can remain outside the ring.

With used-ring logging enabled, start > vq->num makes (vq->num - start)
underflow. log_write() then sets bits beyond the dirty-log bitmap,
potentially corrupting adjacent userspace memory. This is reachable on
the vhost-net TX path with VIRTIO_F_IN_ORDER: done_idx can reach
VHOST_NET_BATCH (64), triggering the issue with a 32-entry ring.

Wrap start with a mask to keep it within the ring. VHOST_SET_VRING_NUM
enforces a nonzero power-of-two ring size, allowing start to be wrapped
using & (vq->num - 1).

Fixes: 67a873df0c41 ("vhost: basic in order support")
Cc: stable@xxxxxxxxxxxxxxx
Reviewed-by: Weibin Liu <liuwb@xxxxxxxxxxxx>
Signed-off-by: Fang Xieyan <fangxy@xxxxxxxxxxxx>
---
Reproducer (verified on a kernel containing commit 67a873df0c41 with a
temporary WARN_ON_ONCE probe in log_used(); the probe is not part of
this patch):

With VIRTIO_F_IN_ORDER, num=32, and used-ring dirty logging enabled, the
TX batch reaches done_idx=64. In the observed run, the initial index is
3, so the single subtraction leaves start=35. The resulting length
calculation wraps to 34359738344 bytes, as confirmed by the log_used()
probe.

A 64-byte canary immediately after the 512-byte log bitmap changes from
0xA5 to 0xFF, confirming the out-of-bounds write. The used-ring canary
remains intact because vhost_put_used() runs before start is advanced;
the demonstrated corruption is in the dirty-log bitmap.

drivers/vhost/vhost.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c
index 44cac11b68d2..0226c4c59709 100644
--- a/drivers/vhost/vhost.c
+++ b/drivers/vhost/vhost.c
@@ -3172,8 +3172,7 @@ static int vhost_add_used_n_in_order(struct vhost_virtqueue *vq,
}
start += nheads[i];
new += nheads[i];
- if (start >= vq->num)
- start -= vq->num;
+ start &= vq->num - 1;
}

if (unlikely(vq->log_used)) {
--
2.50.1