* [PATCH] virtio: validate split queue head count before popping
@ 2026-07-09 10:05 Jia Jia
2026-07-09 12:43 ` Michael S. Tsirkin
0 siblings, 1 reply; 5+ messages in thread
From: Jia Jia @ 2026-07-09 10:05 UTC (permalink / raw)
To: qemu-devel; +Cc: mst, Jia Jia
virtqueue_split_pop() reads the next split avail ring entry after
virtio_queue_empty_rcu() reports work. Call virtqueue_num_heads() before
consuming that entry, so an avail index distance larger than the queue
size is rejected.
virtqueue_num_heads() also keeps the read barrier needed before the avail
ring entry is read.
This prevents an invalid split queue state from being expanded into
repeated device command processing.
Link: https://gitlab.com/qemu-project/qemu/-/issues/3930
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
Tested with qemu-system-x86_64 11.0.50, built from origin/master
f893c46c3931 plus this patch, configured with:
--target-list=x86_64-softmmu --enable-kvm --disable-tcg
The original virtio-iommu live-vring qtest reproducer used for the
report completed successfully on the patched build without host OOM,
confirming that this reproducer is fixed by the patch.
hw/virtio/virtio.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
index f4d86a3655..252bff8641 100644
--- a/hw/virtio/virtio.c
+++ b/hw/virtio/virtio.c
@@ -1751,9 +1751,11 @@ static void *virtqueue_split_pop(VirtQueue *vq, size_t sz)
if (virtio_queue_empty_rcu(vq)) {
goto done;
}
- /* Needed after virtio_queue_empty(), see comment in
- * virtqueue_num_heads(). */
- smp_rmb();
+
+ rc = virtqueue_num_heads(vq, vq->last_avail_idx);
+ if (rc <= 0) {
+ goto done;
+ }
/* When we start there are none of either input nor output. */
out_num = in_num = elem_entries = 0;
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [PATCH] virtio: validate split queue head count before popping
2026-07-09 10:05 [PATCH] virtio: validate split queue head count before popping Jia Jia
@ 2026-07-09 12:43 ` Michael S. Tsirkin
2026-07-10 1:34 ` m'te'a physical
0 siblings, 1 reply; 5+ messages in thread
From: Michael S. Tsirkin @ 2026-07-09 12:43 UTC (permalink / raw)
To: Jia Jia; +Cc: qemu-devel
On Thu, Jul 09, 2026 at 06:05:29PM +0800, Jia Jia wrote:
> virtqueue_split_pop() reads the next split avail ring entry after
> virtio_queue_empty_rcu() reports work. Call virtqueue_num_heads() before
> consuming that entry, so an avail index distance larger than the queue
> size is rejected.
>
> virtqueue_num_heads() also keeps the read barrier needed before the avail
> ring entry is read.
>
> This prevents an invalid split queue state from being expanded into
> repeated device command processing.
>
> Link: https://gitlab.com/qemu-project/qemu/-/issues/3930
> Signed-off-by: Jia Jia <physicalmtea@gmail.com>
> ---
> Tested with qemu-system-x86_64 11.0.50, built from origin/master
> f893c46c3931 plus this patch, configured with:
> --target-list=x86_64-softmmu --enable-kvm --disable-tcg
>
> The original virtio-iommu live-vring qtest reproducer used for the
> report completed successfully on the patched build without host OOM,
> confirming that this reproducer is fixed by the patch.
>
> hw/virtio/virtio.c | 8 +++++---
> 1 file changed, 5 insertions(+), 3 deletions(-)
>
> diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
> index f4d86a3655..252bff8641 100644
> --- a/hw/virtio/virtio.c
> +++ b/hw/virtio/virtio.c
> @@ -1751,9 +1751,11 @@ static void *virtqueue_split_pop(VirtQueue *vq, size_t sz)
> if (virtio_queue_empty_rcu(vq)) {
> goto done;
> }
> - /* Needed after virtio_queue_empty(), see comment in
> - * virtqueue_num_heads(). */
> - smp_rmb();
> +
> + rc = virtqueue_num_heads(vq, vq->last_avail_idx);
> + if (rc <= 0) {
> + goto done;
> + }
Do we still need virtio_queue_empty_rcu then? Why?
> /* When we start there are none of either input nor output. */
> out_num = in_num = elem_entries = 0;
> --
> 2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH] virtio: validate split queue head count before popping
2026-07-09 12:43 ` Michael S. Tsirkin
@ 2026-07-10 1:34 ` m'te'a physical
2026-07-10 6:21 ` Michael S. Tsirkin
0 siblings, 1 reply; 5+ messages in thread
From: m'te'a physical @ 2026-07-10 1:34 UTC (permalink / raw)
To: Michael S. Tsirkin; +Cc: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 3396 bytes --]
I think virtio_queue_empty_rcu() is no longer needed after this change.
For this caller, the pieces it covered are either already handled elsewhere
or
kept explicitly in the split pop path.
The disabled-device check is already done by virtqueue_pop() before it calls
virtqueue_split_pop().
The empty-queue check is covered by virtqueue_num_heads(): rc == 0 is the
normal empty case, and rc < 0 is the invalid avail index distance case that
this
patch wants to reject.
The read barrier is also covered by virtqueue_num_heads(), since it does
smp_rmb() when it returns a positive head count, before the caller reads the
avail ring entry with virtqueue_get_head().
The only old check that I think should stay explicit in
virtqueue_split_pop() is
!vq->vring.avail. virtqueue_num_heads() does not check that, and the old
behavior for an unconfigured split queue was to treat it as empty. So v2
keeps
that guard and replaces the rest of virtio_queue_empty_rcu() with
virtqueue_num_heads():
RCU_READ_LOCK_GUARD();
if (unlikely(!vq->vring.avail)) {
goto done;
}
rc = virtqueue_num_heads(vq, vq->last_avail_idx);
if (rc <= 0) {
goto done;
}
With that, virtio_queue_empty_rcu() has no remaining users and can be
removed.
If this looks right, I can send a v2 patch with this change.
Michael S. Tsirkin <mst@redhat.com> 于2026年7月9日周四 20:43写道:
> On Thu, Jul 09, 2026 at 06:05:29PM +0800, Jia Jia wrote:
> > virtqueue_split_pop() reads the next split avail ring entry after
> > virtio_queue_empty_rcu() reports work. Call virtqueue_num_heads() before
> > consuming that entry, so an avail index distance larger than the queue
> > size is rejected.
> >
> > virtqueue_num_heads() also keeps the read barrier needed before the avail
> > ring entry is read.
> >
> > This prevents an invalid split queue state from being expanded into
> > repeated device command processing.
> >
> > Link: https://gitlab.com/qemu-project/qemu/-/issues/3930
> > Signed-off-by: Jia Jia <physicalmtea@gmail.com>
> > ---
> > Tested with qemu-system-x86_64 11.0.50, built from origin/master
> > f893c46c3931 plus this patch, configured with:
> > --target-list=x86_64-softmmu --enable-kvm --disable-tcg
> >
> > The original virtio-iommu live-vring qtest reproducer used for the
> > report completed successfully on the patched build without host OOM,
> > confirming that this reproducer is fixed by the patch.
> >
> > hw/virtio/virtio.c | 8 +++++---
> > 1 file changed, 5 insertions(+), 3 deletions(-)
> >
> > diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
> > index f4d86a3655..252bff8641 100644
> > --- a/hw/virtio/virtio.c
> > +++ b/hw/virtio/virtio.c
> > @@ -1751,9 +1751,11 @@ static void *virtqueue_split_pop(VirtQueue *vq,
> size_t sz)
> > if (virtio_queue_empty_rcu(vq)) {
> > goto done;
> > }
> > - /* Needed after virtio_queue_empty(), see comment in
> > - * virtqueue_num_heads(). */
> > - smp_rmb();
> > +
> > + rc = virtqueue_num_heads(vq, vq->last_avail_idx);
> > + if (rc <= 0) {
> > + goto done;
> > + }
>
>
> Do we still need virtio_queue_empty_rcu then? Why?
>
> > /* When we start there are none of either input nor output. */
> > out_num = in_num = elem_entries = 0;
> > --
> > 2.34.1
>
>
[-- Attachment #2: Type: text/html, Size: 4236 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH] virtio: validate split queue head count before popping
2026-07-10 1:34 ` m'te'a physical
@ 2026-07-10 6:21 ` Michael S. Tsirkin
2026-07-10 7:35 ` [PATCH v2] " Jia Jia
0 siblings, 1 reply; 5+ messages in thread
From: Michael S. Tsirkin @ 2026-07-10 6:21 UTC (permalink / raw)
To: m'te'a physical; +Cc: qemu-devel
On Fri, Jul 10, 2026 at 09:34:54AM +0800, m'te'a physical wrote:
> I think virtio_queue_empty_rcu() is no longer needed after this change.
>
> For this caller, the pieces it covered are either already handled elsewhere or
> kept explicitly in the split pop path.
>
> The disabled-device check is already done by virtqueue_pop() before it calls
> virtqueue_split_pop().
>
> The empty-queue check is covered by virtqueue_num_heads(): rc == 0 is the
> normal empty case, and rc < 0 is the invalid avail index distance case that
> this
> patch wants to reject.
>
> The read barrier is also covered by virtqueue_num_heads(), since it does
> smp_rmb() when it returns a positive head count, before the caller reads the
> avail ring entry with virtqueue_get_head().
>
> The only old check that I think should stay explicit in virtqueue_split_pop()
> is
> !vq->vring.avail. virtqueue_num_heads() does not check that, and the old
> behavior for an unconfigured split queue was to treat it as empty. So v2 keeps
> that guard and replaces the rest of virtio_queue_empty_rcu() with
> virtqueue_num_heads():
Yea this is our hacky way to check the mr cache is valid.
> RCU_READ_LOCK_GUARD();
> if (unlikely(!vq->vring.avail)) {
> goto done;
> }
>
> rc = virtqueue_num_heads(vq, vq->last_avail_idx);
> if (rc <= 0) {
> goto done;
> }
>
> With that, virtio_queue_empty_rcu() has no remaining users and can be removed.
>
> If this looks right, I can send a v2 patch with this change.
>
> Michael S. Tsirkin <mst@redhat.com> 于2026年7月9日周四 20:43写道:
>
> On Thu, Jul 09, 2026 at 06:05:29PM +0800, Jia Jia wrote:
> > virtqueue_split_pop() reads the next split avail ring entry after
> > virtio_queue_empty_rcu() reports work. Call virtqueue_num_heads() before
> > consuming that entry, so an avail index distance larger than the queue
> > size is rejected.
> >
> > virtqueue_num_heads() also keeps the read barrier needed before the avail
> > ring entry is read.
> >
> > This prevents an invalid split queue state from being expanded into
> > repeated device command processing.
> >
> > Link: https://gitlab.com/qemu-project/qemu/-/issues/3930
> > Signed-off-by: Jia Jia <physicalmtea@gmail.com>
> > ---
> > Tested with qemu-system-x86_64 11.0.50, built from origin/master
> > f893c46c3931 plus this patch, configured with:
> > --target-list=x86_64-softmmu --enable-kvm --disable-tcg
> >
> > The original virtio-iommu live-vring qtest reproducer used for the
> > report completed successfully on the patched build without host OOM,
> > confirming that this reproducer is fixed by the patch.
> >
> > hw/virtio/virtio.c | 8 +++++---
> > 1 file changed, 5 insertions(+), 3 deletions(-)
> >
> > diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
> > index f4d86a3655..252bff8641 100644
> > --- a/hw/virtio/virtio.c
> > +++ b/hw/virtio/virtio.c
> > @@ -1751,9 +1751,11 @@ static void *virtqueue_split_pop(VirtQueue *vq,
> size_t sz)
> > if (virtio_queue_empty_rcu(vq)) {
> > goto done;
> > }
> > - /* Needed after virtio_queue_empty(), see comment in
> > - * virtqueue_num_heads(). */
> > - smp_rmb();
> > +
> > + rc = virtqueue_num_heads(vq, vq->last_avail_idx);
> > + if (rc <= 0) {
> > + goto done;
> > + }
>
>
> Do we still need virtio_queue_empty_rcu then? Why?
>
> > /* When we start there are none of either input nor output. */
> > out_num = in_num = elem_entries = 0;
> > --
> > 2.34.1
>
>
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v2] virtio: validate split queue head count before popping
2026-07-10 6:21 ` Michael S. Tsirkin
@ 2026-07-10 7:35 ` Jia Jia
0 siblings, 0 replies; 5+ messages in thread
From: Jia Jia @ 2026-07-10 7:35 UTC (permalink / raw)
To: qemu-devel; +Cc: mst
virtqueue_split_pop() reads the next split avail ring entry after
checking that the ring is configured. Call virtqueue_num_heads() before
consuming that entry, so an avail index distance larger than the queue
size is rejected.
virtqueue_num_heads() also reports an empty queue and keeps the read
barrier needed before the avail ring entry is read. Drop the now unused
virtio_queue_empty_rcu() helper.
This prevents an invalid split queue state from being expanded into
repeated device command processing.
Link: https://gitlab.com/qemu-project/qemu/-/issues/3930
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
v2:
- Drop virtio_queue_empty_rcu() from the split pop path.
- Keep only the existing !vq->vring.avail early exit before
virtqueue_num_heads().
- Remove the now unused virtio_queue_empty_rcu() helper.
Tested with qemu-system-x86_64 11.0.50, built from origin/master
f893c46c3931 plus this patch, configured with:
--target-list=x86_64-softmmu --enable-kvm --disable-tcg
The original virtio-iommu live-vring qtest reproducer used for the
report completed successfully on the patched build without host OOM,
confirming that this reproducer is fixed by the patch.
hw/virtio/virtio.c | 30 ++++++------------------------
1 file changed, 6 insertions(+), 24 deletions(-)
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
index f4d86a3655..68ec3f0751 100644
--- a/hw/virtio/virtio.c
+++ b/hw/virtio/virtio.c
@@ -716,26 +716,6 @@ static inline bool is_desc_avail(uint16_t flags, bool wrap_counter)
return (avail != used) && (avail == wrap_counter);
}
-/* Fetch avail_idx from VQ memory only when we really need to know if
- * guest has added some buffers.
- * Called within rcu_read_lock(). */
-static int virtio_queue_empty_rcu(VirtQueue *vq)
-{
- if (virtio_device_disabled(vq->vdev)) {
- return 1;
- }
-
- if (unlikely(!vq->vring.avail)) {
- return 1;
- }
-
- if (vq->shadow_avail_idx != vq->last_avail_idx) {
- return 0;
- }
-
- return vring_avail_idx(vq) == vq->last_avail_idx;
-}
-
static int virtio_queue_split_empty(VirtQueue *vq)
{
bool empty;
@@ -1748,12 +1728,14 @@ static void *virtqueue_split_pop(VirtQueue *vq, size_t sz)
address_space_cache_init_empty(&indirect_desc_cache);
RCU_READ_LOCK_GUARD();
- if (virtio_queue_empty_rcu(vq)) {
+ if (unlikely(!vq->vring.avail)) {
+ goto done;
+ }
+
+ rc = virtqueue_num_heads(vq, vq->last_avail_idx);
+ if (rc <= 0) {
goto done;
}
- /* Needed after virtio_queue_empty(), see comment in
- * virtqueue_num_heads(). */
- smp_rmb();
/* When we start there are none of either input nor output. */
out_num = in_num = elem_entries = 0;
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-10 7:36 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-09 10:05 [PATCH] virtio: validate split queue head count before popping Jia Jia
2026-07-09 12:43 ` Michael S. Tsirkin
2026-07-10 1:34 ` m'te'a physical
2026-07-10 6:21 ` Michael S. Tsirkin
2026-07-10 7:35 ` [PATCH v2] " Jia Jia
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.