@samitouri / QOSamiQemu / commits / d6384ad1c1

virtio: stop migrating num_default, validate vring.num on load

num_default tracks the allocation size of used_elems, set by virtio_add_queue(). Migrating it via the ringsize subsection is wrong: a migration stream (malicious or simply from a different configuration) can inflate num_default so that virtio_queue_set_num() accepts oversized values, leading to OOB access on the used_elems array. It is not even migrated consistently: a configuration with a smaller num_default could thinkably migrate and work but in the common case of num == num_default the value is not actually sent. Stop migrating num_default: make virtio_ringsize_needed() return false so the subsection is never sent, and use VMSTATE_UNUSED to consume the field from old streams without applying it. The destination keeps its local num_default from virtio_add_queue(), which matches the actual allocation. Also validate vring.num against num_default when loading the core virtio state, rejecting streams that supply a queue size larger than the locally allocated maximum. Fixes: 46c5d0823d ("virtio: ring sizes vs. reset") Fixes: 50e5ae4dc3 ("migration/virtio: Remove simple .get/.put use") Cc: Cornelia Huck <cohuck@redhat.com> Cc: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-ID: <3e6a7c403f93acc37af6a6332fdc65049ae218fb.1784894327.git.mst@redhat.com>

Michael S. Tsirkin committed Jul 20, 2026 at 01:26 UTC d6384ad1c15962092c2281efedc118152de6a794
1 file changed +7 -9
hw/virtio/virtio.c
+7 -9
@@ -2805,14 +2805,6 @@ static bool virtio_packed_virtqueue_needed(void *opaque)
2805
2806 static bool virtio_ringsize_needed(void *opaque)
2807 {
2808 - VirtIODevice *vdev = opaque;
2809 - int i;
2810 -
2811 - for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
2812 - if (vdev->vq[i].vring.num != vdev->vq[i].vring.num_default) {
2813 - return true;
2814 - }
2815 - }
2808 return false;
2809 }
2810
@@ -2901,7 +2893,7 @@ static const VMStateDescription vmstate_ringsize = {
2893 .version_id = 1,
2894 .minimum_version_id = 1,
2895 .fields = (const VMStateField[]) {
2904 - VMSTATE_UINT32(vring.num_default, struct VirtQueue),
2896 + VMSTATE_UNUSED(sizeof(uint32_t)),
2897 VMSTATE_END_OF_LIST()
2898 }
2899 };
@@ -3581,6 +3573,12 @@ virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
3573
3574 for (i = 0; i < num; i++) {
3575 vdev->vq[i].vring.num = qemu_get_be32(f);
3576 + if (vdev->vq[i].vring.num > vdev->vq[i].vring.num_default) {
3577 + error_report("VQ %d vring.num %u exceeds allocated max %u",
3578 + i, vdev->vq[i].vring.num,
3579 + vdev->vq[i].vring.num_default);
3580 + return -1;
3581 + }
3582 if (k->has_variable_vring_alignment) {
3583 vdev->vq[i].vring.align = qemu_get_be32(f);
3584 }