* [PATCH 0/2] vringh: fix infinite loop on cyclic top-level indirect descriptor @ 2026-09-22 12:29 Fang Xieyan 2026-09-22 12:29 ` [PATCH 1/2] vringh: bound top-level re-entry into indirect tables Fang Xieyan 2026-09-22 12:29 ` [PATCH 2/2] vringh: add regression test for cyclic indirect descriptor Fang Xieyan 0 siblings, 2 replies; 6+ messages in thread From: Fang Xieyan @ 2026-09-22 12:29 UTC (permalink / raw) To: Michael S . Tsirkin, Jason Wang, Eugenio Pérez Cc: Xie Yongji, Xuan Zhuo, stable, virtualization, kvm, netdev, linux-kernel __vringh_iov()'s F_INDIRECT branch does `continue` before the descriptor accounting block, so a top-level indirect descriptor is never charged to `count`. When such a descriptor also carries NEXT pointing to itself, returning from the indirect table resumes at the same uncharged descriptor; `count` stays flat and `indirect_count` is reset to 0 on every return, so neither bound in the loop check can ever trip. A guest can keep the host vringh worker spinning inside __vringh_iov(), causing a host DoS. Patch 1/2 moves the accounting block above the INDIRECT switch so each top-level descriptor, including INDIRECT ones, is charged one traversal step before the walk descends. Legitimate chains and indirect descriptors stay within vring.num; a cyclic indirect descriptor is now rejected with -ELOOP. Patch 1 carries the Fixes tag and is Cc'd to stable. Patch 2/2 appends a regression test to tools/virtio/vringh_test.c. It builds the offending ring: top-level desc[1].flags = VRING_DESC_F_INDIRECT | VRING_DESC_F_NEXT and desc[1].next = 1, with a single-entry indirect table, and asserts that vringh_getdesc_user() returns -ELOOP. Verified against v7.3-rc3: both patches pass git apply --check. With the fix, vringh_test --indirect observes -ELOOP and exits 0. With the fix reverted, the walk re-enters the same descriptor without making forward progress and never returns -ELOOP, so the assertion does not hold and the regression is caught either way. Fang Xieyan (2): vringh: bound top-level re-entry into indirect tables vringh: add regression test for cyclic indirect descriptor drivers/vhost/vringh.c | 22 ++++++++++---------- tools/virtio/vringh_test.c | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 11 deletions(-) -- 2.50.1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/2] vringh: bound top-level re-entry into indirect tables 2026-09-22 12:29 [PATCH 0/2] vringh: fix infinite loop on cyclic top-level indirect descriptor Fang Xieyan @ 2026-09-22 12:29 ` Fang Xieyan 2026-09-22 12:39 ` sashiko-bot 2026-09-22 12:29 ` [PATCH 2/2] vringh: add regression test for cyclic indirect descriptor Fang Xieyan 1 sibling, 1 reply; 6+ messages in thread From: Fang Xieyan @ 2026-09-22 12:29 UTC (permalink / raw) To: Michael S . Tsirkin, Jason Wang, Eugenio Pérez Cc: Xie Yongji, Xuan Zhuo, stable, virtualization, kvm, netdev, linux-kernel In __vringh_iov(), the F_INDIRECT branch descends into the indirect table and continues before the descriptor accounting runs. A top-level indirect descriptor is therefore never charged to count. If such a descriptor sets NEXT to point back at itself, returning from the indirect table resumes at the same top-level descriptor, which is again not counted, so the walk never makes forward progress. Because count stays flat and indirect_count is reset to 0 on every return to the top-level table, neither bound in the loop check trips. A guest can spin the vringh worker at 100% CPU inside __vringh_iov(), an uninterruptible host DoS. Move the descriptor accounting above the indirect switch so that a top-level indirect descriptor is charged one top-level traversal step before the walk descends into its table, bringing this re-entry under the existing vrh->vring.num bound. Each top-level descriptor is still charged at most one step and indirect_count still bounds a single table, so legitimate chains (including the multiple-indirect case in tools/virtio/vringh_test.c) stay within vring.num, while a cyclic indirect descriptor is now rejected with -ELOOP. Fixes: dbd29e075228 ("vringh: Fix loop descriptors check in the indirect cases") Cc: Xie Yongji <xieyongji@bytedance.com> Cc: stable@vger.kernel.org Assisted-by: Hawkeye:GLM-5.3-flash Assisted-by: Qoder:Qwen3.8-Max Signed-off-by: Fang Xieyan <fangxy@xiaopeng.com> --- drivers/vhost/vringh.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/vhost/vringh.c b/drivers/vhost/vringh.c index 9066f9f..f672e11 100644 --- a/drivers/vhost/vringh.c +++ b/drivers/vhost/vringh.c @@ -333,6 +333,17 @@ __vringh_iov(struct vringh *vrh, u16 i, if (unlikely(err)) goto fail; + if (up_next == -1) + count++; + else + indirect_count++; + + if (count > vrh->vring.num || indirect_count > desc_max) { + vringh_bad("Descriptor loop in %p", descs); + err = -ELOOP; + goto fail; + } + if (unlikely(desc.flags & cpu_to_vringh16(vrh, VRING_DESC_F_INDIRECT))) { u64 a = vringh64_to_cpu(vrh, desc.addr); @@ -358,17 +369,6 @@ __vringh_iov(struct vringh *vrh, u16 i, continue; } - if (up_next == -1) - count++; - else - indirect_count++; - - if (count > vrh->vring.num || indirect_count > desc_max) { - vringh_bad("Descriptor loop in %p", descs); - err = -ELOOP; - goto fail; - } - if (desc.flags & cpu_to_vringh16(vrh, VRING_DESC_F_WRITE)) iov = wiov; else { -- 2.50.1 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] vringh: bound top-level re-entry into indirect tables 2026-09-22 12:29 ` [PATCH 1/2] vringh: bound top-level re-entry into indirect tables Fang Xieyan @ 2026-09-22 12:39 ` sashiko-bot 0 siblings, 0 replies; 6+ messages in thread From: sashiko-bot @ 2026-09-22 12:39 UTC (permalink / raw) To: Fang Xieyan; +Cc: kvm, virtualization, Eugenio Perez, Michael S. Tsirkin > In __vringh_iov(), the F_INDIRECT branch descends into the indirect > table and continues before the descriptor accounting runs. A top-level > indirect descriptor is therefore never charged to count. If such a > descriptor sets NEXT to point back at itself, returning from the > indirect table resumes at the same top-level descriptor, which is again > not counted, so the walk never makes forward progress. > > Because count stays flat and indirect_count is reset to 0 on every > return to the top-level table, neither bound in the loop check trips. > A guest can spin the vringh worker at 100% CPU inside __vringh_iov(), > an uninterruptible host DoS. > > Move the descriptor accounting above the indirect switch so that a > top-level indirect descriptor is charged one top-level traversal step > before the walk descends into its table, bringing this re-entry under > the existing vrh->vring.num bound. Each top-level descriptor is still > charged at most one step and indirect_count still bounds a single > table, so legitimate chains (including the multiple-indirect case in > tools/virtio/vringh_test.c) stay within vring.num, while a cyclic > indirect descriptor is now rejected with -ELOOP. > > Fixes: dbd29e075228 ("vringh: Fix loop descriptors check in the indirect cases") > Cc: Xie Yongji <xieyongji@bytedance.com> > Cc: stable@vger.kernel.org > Assisted-by: Hawkeye:GLM-5.3-flash > Assisted-by: Qoder:Qwen3.8-Max > Signed-off-by: Fang Xieyan <fangxy@xiaopeng.com> Sashiko has reviewed this patch and found no issues. It looks great! -- Sashiko AI review · https://sashiko.dev/#/patchset/20260922122955.69433-1-fangxy@xiaopeng.com?part=1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/2] vringh: add regression test for cyclic indirect descriptor 2026-09-22 12:29 [PATCH 0/2] vringh: fix infinite loop on cyclic top-level indirect descriptor Fang Xieyan 2026-09-22 12:29 ` [PATCH 1/2] vringh: bound top-level re-entry into indirect tables Fang Xieyan @ 2026-09-22 12:29 ` Fang Xieyan 2026-09-22 12:39 ` sashiko-bot 2026-09-26 6:43 ` Jason Wang 1 sibling, 2 replies; 6+ messages in thread From: Fang Xieyan @ 2026-09-22 12:29 UTC (permalink / raw) To: Michael S . Tsirkin, Jason Wang, Eugenio Pérez Cc: Xie Yongji, Xuan Zhuo, stable, virtualization, kvm, netdev, linux-kernel Add a case to tools/virtio/vringh_test.c that builds a top-level indirect descriptor whose NEXT points back at itself and checks that vringh_getdesc_user() rejects it with -ELOOP. Without the preceding fix, the walk re-enters the same top-level descriptor without making forward progress: count stays flat, so the traversal limit is never reached and -ELOOP is never returned. With the fix, the top-level count advances on each re-entry and vringh_getdesc_user() returns -ELOOP once the traversal limit is reached. Use index 1 rather than 0 for the self-cycle, since returning from an indirect table is only performed for a positive up_next value. A self-cycle at index 0 would instead terminate the walk and would not reproduce the bug. Assisted-by: Hawkeye:GLM-5.3-flash Assisted-by: Qoder:Qwen3.8-Max Signed-off-by: Fang Xieyan <fangxy@xiaopeng.com> --- tools/virtio/vringh_test.c | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tools/virtio/vringh_test.c b/tools/virtio/vringh_test.c index 84961b9..2a5d7f7 100644 --- a/tools/virtio/vringh_test.c +++ b/tools/virtio/vringh_test.c @@ -458,6 +458,8 @@ int main(int argc, char *argv[]) int err; unsigned i; void *ret; + struct vring_desc *ind; + char *data; bool (*getrange)(struct vringh *vrh, u64 addr, struct vringh_range *r); bool fast_vringh = false, parallel = false; @@ -755,6 +757,45 @@ int main(int argc, char *argv[]) vringh_iov_cleanup(&riov); } + /* + * Regression test: a top-level indirect descriptor whose NEXT + * points back to itself must be rejected with -ELOOP instead of + * looping forever. Use index 1 rather than 0 so that returning + * from the indirect table re-enters the same top-level descriptor. + */ + ind = __user_addr_max - USER_MEM/2; + data = __user_addr_max - USER_MEM/4; + + /* Fresh ring and host state; resets last_avail_idx to 0. */ + vring_init(&vrh.vring, RINGSIZE, __user_addr_min, ALIGN); + vringh_init_user(&vrh, vdev.features, RINGSIZE, true, + vrh.vring.desc, vrh.vring.avail, vrh.vring.used); + + /* Single-entry indirect table pointing at valid data. */ + ind[0].addr = (unsigned long)data; + ind[0].len = 1; + ind[0].flags = 0; + + /* Top-level desc[1]: INDIRECT, and NEXT loops back to itself. */ + vrh.vring.desc[1].addr = (unsigned long)ind; + vrh.vring.desc[1].len = sizeof(*ind); + vrh.vring.desc[1].flags = VRING_DESC_F_INDIRECT | VRING_DESC_F_NEXT; + vrh.vring.desc[1].next = 1; + + /* Publish head 1 on the avail ring. */ + vrh.vring.avail->ring[0] = 1; + vrh.vring.avail->idx = 1; + + vringh_iov_init(&riov, host_riov, ARRAY_SIZE(host_riov)); + vringh_iov_init(&wiov, host_wiov, ARRAY_SIZE(host_wiov)); + + err = vringh_getdesc_user(&vrh, &riov, &wiov, getrange, &head); + if (err != -ELOOP) + errx(1, "self-referential indirect: %i not -ELOOP", err); + + vringh_iov_cleanup(&riov); + vringh_iov_cleanup(&wiov); + /* Don't leak memory... */ vring_del_virtqueue(vq); free(__user_addr_min); -- 2.50.1 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] vringh: add regression test for cyclic indirect descriptor 2026-09-22 12:29 ` [PATCH 2/2] vringh: add regression test for cyclic indirect descriptor Fang Xieyan @ 2026-09-22 12:39 ` sashiko-bot 2026-09-26 6:43 ` Jason Wang 1 sibling, 0 replies; 6+ messages in thread From: sashiko-bot @ 2026-09-22 12:39 UTC (permalink / raw) To: Fang Xieyan; +Cc: kvm, Michael S. Tsirkin, Eugenio Perez, virtualization > Add a case to tools/virtio/vringh_test.c that builds a top-level > indirect descriptor whose NEXT points back at itself and checks that > vringh_getdesc_user() rejects it with -ELOOP. > > Without the preceding fix, the walk re-enters the same top-level > descriptor without making forward progress: count stays flat, so the > traversal limit is never reached and -ELOOP is never returned. With the > fix, the top-level count advances on each re-entry and > vringh_getdesc_user() returns -ELOOP once the traversal limit is reached. > > Use index 1 rather than 0 for the self-cycle, since returning from an > indirect table is only performed for a positive up_next value. A > self-cycle at index 0 would instead terminate the walk and would not > reproduce the bug. > > Assisted-by: Hawkeye:GLM-5.3-flash > Assisted-by: Qoder:Qwen3.8-Max > Signed-off-by: Fang Xieyan <fangxy@xiaopeng.com> Sashiko has reviewed this patch and found no issues. It looks great! -- Sashiko AI review · https://sashiko.dev/#/patchset/20260922122955.69433-1-fangxy@xiaopeng.com?part=2 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] vringh: add regression test for cyclic indirect descriptor 2026-09-22 12:29 ` [PATCH 2/2] vringh: add regression test for cyclic indirect descriptor Fang Xieyan 2026-09-22 12:39 ` sashiko-bot @ 2026-09-26 6:43 ` Jason Wang 1 sibling, 0 replies; 6+ messages in thread From: Jason Wang @ 2026-09-26 6:43 UTC (permalink / raw) To: Fang Xieyan Cc: Michael S . Tsirkin, Eugenio Pérez, Xie Yongji, Xuan Zhuo, stable, virtualization, kvm, netdev, linux-kernel On Tue, Sep 22, 2026 at 8:30 PM Fang Xieyan <fangxy@xiaopeng.com> wrote: > > Add a case to tools/virtio/vringh_test.c that builds a top-level > indirect descriptor whose NEXT points back at itself and checks that > vringh_getdesc_user() rejects it with -ELOOP. > > Without the preceding fix, the walk re-enters the same top-level > descriptor without making forward progress: count stays flat, so the > traversal limit is never reached and -ELOOP is never returned. With the > fix, the top-level count advances on each re-entry and > vringh_getdesc_user() returns -ELOOP once the traversal limit is reached. > > Use index 1 rather than 0 for the self-cycle, since returning from an > indirect table is only performed for a positive up_next value. A > self-cycle at index 0 would instead terminate the walk and would not > reproduce the bug. > > Assisted-by: Hawkeye:GLM-5.3-flash > Assisted-by: Qoder:Qwen3.8-Max > Signed-off-by: Fang Xieyan <fangxy@xiaopeng.com> > --- > tools/virtio/vringh_test.c | 41 ++++++++++++++++++++++++++++++++++++++ > 1 file changed, 41 insertions(+) > > diff --git a/tools/virtio/vringh_test.c b/tools/virtio/vringh_test.c > index 84961b9..2a5d7f7 100644 > --- a/tools/virtio/vringh_test.c > +++ b/tools/virtio/vringh_test.c > @@ -458,6 +458,8 @@ int main(int argc, char *argv[]) > int err; > unsigned i; > void *ret; > + struct vring_desc *ind; > + char *data; > bool (*getrange)(struct vringh *vrh, u64 addr, struct vringh_range *r); > bool fast_vringh = false, parallel = false; > > @@ -755,6 +757,45 @@ int main(int argc, char *argv[]) > vringh_iov_cleanup(&riov); > } > > + /* > + * Regression test: a top-level indirect descriptor whose NEXT > + * points back to itself must be rejected with -ELOOP instead of > + * looping forever. Use index 1 rather than 0 so that returning > + * from the indirect table re-enters the same top-level descriptor. > + */ > + ind = __user_addr_max - USER_MEM/2; > + data = __user_addr_max - USER_MEM/4; > + > + /* Fresh ring and host state; resets last_avail_idx to 0. */ > + vring_init(&vrh.vring, RINGSIZE, __user_addr_min, ALIGN); > + vringh_init_user(&vrh, vdev.features, RINGSIZE, true, > + vrh.vring.desc, vrh.vring.avail, vrh.vring.used); > + > + /* Single-entry indirect table pointing at valid data. */ > + ind[0].addr = (unsigned long)data; > + ind[0].len = 1; > + ind[0].flags = 0; > + > + /* Top-level desc[1]: INDIRECT, and NEXT loops back to itself. */ > + vrh.vring.desc[1].addr = (unsigned long)ind; > + vrh.vring.desc[1].len = sizeof(*ind); > + vrh.vring.desc[1].flags = VRING_DESC_F_INDIRECT | VRING_DESC_F_NEXT; > + vrh.vring.desc[1].next = 1; > + > + /* Publish head 1 on the avail ring. */ > + vrh.vring.avail->ring[0] = 1; > + vrh.vring.avail->idx = 1; > + > + vringh_iov_init(&riov, host_riov, ARRAY_SIZE(host_riov)); > + vringh_iov_init(&wiov, host_wiov, ARRAY_SIZE(host_wiov)); > + > + err = vringh_getdesc_user(&vrh, &riov, &wiov, getrange, &head); > + if (err != -ELOOP) > + errx(1, "self-referential indirect: %i not -ELOOP", err); > + > + vringh_iov_cleanup(&riov); > + vringh_iov_cleanup(&wiov); > + > /* Don't leak memory... */ > vring_del_virtqueue(vq); > free(__user_addr_min); > -- > 2.50.1 > Acked-by: Jason Wang <jasowangio@gmail.com> Thanks ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-26 6:43 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-22 12:29 [PATCH 0/2] vringh: fix infinite loop on cyclic top-level indirect descriptor Fang Xieyan 2026-09-22 12:29 ` [PATCH 1/2] vringh: bound top-level re-entry into indirect tables Fang Xieyan 2026-09-22 12:39 ` sashiko-bot 2026-09-22 12:29 ` [PATCH 2/2] vringh: add regression test for cyclic indirect descriptor Fang Xieyan 2026-09-22 12:39 ` sashiko-bot 2026-09-26 6:43 ` Jason Wang
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox