Skip to contentSkip to conversation

pci, vhost, virtio, iommu: bugfixes

GIT PULL Michael S. Tsirkin 4 participants 44 messages Jul 26 – Jul 28

lore
Share & tools
lore
#0 lore raw

The following changes since commit cbd42e2b75b23953a9bdb073c8531518b1bd6163:

Update version for v11.1.0-rc1 release (2026-07-21 13:18:25 -0400)

are available in the Git repository at:

https://git.kernel.org/pub/scm/virt/kvm/mst/qemu.git tags/for_upstream

for you to fetch changes up to 13342b9c4104f3bf93c2e52e7de4b383971d7bd6:

backends/rng: cap request size to avoid oversized allocation (2026-07-26 17:26:41 -0400)

----------------------------------------------------------------
pci, vhost, virtio, iommu: bugfixes

Fixes all over the place, including a bunch of CVE fixes.

----------------------------------------------------------------
Clément MATHIEU--DRIF (1):
intel_iommu: Check address mask before using it in pasid-based iotlb invalidation

GuoHan Zhao (1):
hw/virtio/vdpa-dev: pass set_config buffer to vhost backend

Haotian Jiang (1):
hw/cxl: fix OOB access in cxl_doe_cdat_rsp via entry_handle

Laurent Vivier (4):
hw/virtio: reject zero-length packed indirect descriptor table hw/net/virtio-net: Protect from DMA re-entrancy bugs hw/virtio-rng: Fix host use-after-free (CVE-2026-50624) backends/rng: cap request size to avoid oversized allocation

Manos Pitsidianakis (2):
virtio-snd: check rx buffer descriptor size
virtio-snd: check for overflow before g_malloc0

Michael S. Tsirkin (18):
virtio: use masked features with set_features_ex
virtio-net: fix OOB read in RSC receive path
virtio-net: fix short frame OOB read in receive_filter()
libvhost-user: protect against OOB writes in vu_set_inflight_fd
libvhost-user: protect against OOB vring queue access
vhost: do not crash on ring map failure
virtio-scsi: fix SCSIRequest leak on a bad request
virtio-mmio: fix QUEUE_NUM_MAX
virtio: fix queue size validation against allocated maximum
virtio: stop migrating num_default, validate vring.num on load
virtio: fail early on bad config_len in migration
vhost-user: assert nregions within limit
virtio-pmem: wait for flush requests on unrealize
libvhost-user: validate last_batch_head in vu_check_queue_inflights
libvhost-user: fix heap overflow in vu_check_queue_inflights
vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64
libvduse: validate vq size
virtio-iommu: fix OOM due to unbounded call_rcu

Peter Maydell (3):
hw/pci-host/q35.c: Always initialize smram-region even if SMM disabled hw/pci-host/q35.c: Factor out creation of SMRAM MRs hw/pci-host/q35.c: Avoid early return in mch_write_config()

 include/hw/scsi/scsi.h                    |   1 +
 include/hw/virtio/virtio-bus.h            |   1 +
 include/hw/virtio/virtio-iommu.h          |   1 +
 include/hw/virtio/virtio-mmio.h           |   1 +
 include/hw/virtio/virtio-pmem.h           |   1 +
 include/migration/vmstate.h               |   5 +-
 include/system/rng.h                      |  14 ++++
 backends/rng.c                            |  21 +++++-
 hw/audio/virtio-snd.c                     |  24 +++++--
 hw/core/machine.c                         |   1 +
 hw/i386/intel_iommu.c                     |   9 +++
 hw/mem/cxl_type3.c                        |   3 +
 hw/net/virtio-net.c                       |  25 ++++++-
 hw/pci-bridge/cxl_upstream.c              |   3 +
 hw/pci-host/q35.c                         | 110 ++++++++++++++++--------------
 hw/scsi/scsi-bus.c                        |   7 ++
 hw/scsi/virtio-scsi.c                     |   2 +
 hw/virtio/vdpa-dev.c                      |   2 +-
 hw/virtio/vhost-user.c                    |   3 +
 hw/virtio/vhost.c                         |   5 +-
 hw/virtio/virtio-iommu.c                  |  29 ++++++++
 hw/virtio/virtio-mmio.c                   |   7 +-
 hw/virtio/virtio-pmem.c                   |  17 ++++-
 hw/virtio/virtio-rng.c                    |   2 +
 hw/virtio/virtio.c                        |  43 ++++++++----
 migration/vmstate.c                       |  31 ++++++++-
 subprojects/libvduse/libvduse.c           |   5 ++
 subprojects/libvhost-user/libvhost-user.c |  61 ++++++++++++++++-
 28 files changed, 345 insertions(+), 89 deletions(-)
#1 lore raw

[PULL 08/30] virtio-scsi: fix SCSIRequest leak on a bad request

When virtio_scsi_handle_cmd_vq() cleans up prepared requests after a malformed element in the same batch, it drops only one reference even though virtio_scsi_handle_cmd_req_prepare() leaves each unsubmitted SCSIRequest with two references. This leaks the request and allows repeated bad batches to cause unbounded host memory growth.

Add a second scsi_req_unref() and clear hba_private first.

Fixes CVE-2026-61476
Fixes 661e32fb3c ("virtio-scsi: convert virtio_scsi_bad_req() to use virtio_error()")

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3875

Cc Paolo Bonzini <[email protected]>
Cc Fam Zheng <[email protected]>
Cc Greg Kurz <[email protected]>

Message-ID: <5092cd4716e08d29731bfe85eea82a732b837ff4.1784895264.git.mst@redhat.com>

---
 include/hw/scsi/scsi.h | 1 +
 hw/scsi/scsi-bus.c     | 7 +++++++
 hw/scsi/virtio-scsi.c  | 2 ++
 3 files changed, 10 insertions(+)
include/hw/scsi/scsi.h +1 -0
index 5f83e58d1d..c60c6e8810 100644
@@ -221,6 +221,7 @@ SCSIRequest *scsi_req_new(SCSIDevice *d, uint32_t tag, uint32_t lun,
 int32_t scsi_req_enqueue(SCSIRequest *req);
 SCSIRequest *scsi_req_ref(SCSIRequest *req);
 void scsi_req_unref(SCSIRequest *req);
+void scsi_req_unref_detach_hba(SCSIRequest *req);
 
 int scsi_bus_parse_cdb(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf,
                        size_t buf_len, void *hba_private);
hw/scsi/scsi-bus.c +7 -0
index dccb2f25b2..deb43d5560 100644
@@ -1513,6 +1513,13 @@ void scsi_req_unref(SCSIRequest *req)
     }
 }
 
+void scsi_req_unref_detach_hba(SCSIRequest *req)
+{
+    /* Unref when the HBA frees hba_private separately (e.g. virtio_scsi_free_req) */
+    req->hba_private = NULL;
+    scsi_req_unref(req);
+}
+
 /* Tell the device that we finished processing this chunk of I/O.  It
    will start the next chunk or complete the command.  */
 void scsi_req_continue(SCSIRequest *req)
hw/scsi/virtio-scsi.c +2 -0
index 6c73768011..bf64d1231a 100644
@@ -931,7 +931,9 @@ static void virtio_scsi_handle_cmd_vq(VirtIOSCSI *s, VirtQueue *vq)
                     req = QTAILQ_FIRST(&reqs);
                     QTAILQ_REMOVE(&reqs, req, next);
                     defer_call_end();
+                    /* Drop both the ref from _prepare and the initial ref */
                     scsi_req_unref(req->sreq);
+                    scsi_req_unref_detach_hba(req->sreq);
                     virtqueue_detach_element(req->vq, &req->elem, 0);
                     virtio_scsi_free_req(req);
                 }
Signature · 1 line
MST
#2 lore raw

[PULL 01/30] virtio: use masked features with set_features_ex

In reply to Michael S. Tsirkin

virtio_set_features_nocheck() calls set_features_ex with guest-supplied feature bits, without masking the value with host features (unlike set_features which gets the correct val & host_features).

This does not matter if the driver matches spec, but drivers can be malicious or buggy and set bit outside the host mask.

Devices don't expect this, so unsupported guest feature bits getting set can break the host. In virtio-net, this can enable RSC without vnet header support and cause out-of-bounds reads from short packets.

Pass the masked features to set_features_ex, consistent with set_features.

Fixes CVE-2026-63321
Fixes 64a6a336f4 ("virtio: add support for negotiating extended features")
Cc Jason Wang <[email protected]>
Cc Yuri Benditovich <[email protected]>
Cc Paolo Abeni <[email protected]>

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3623

Message-ID: <dfd27c9b26e442a2076f6ddc9bb3d38363d9b2da.1784891251.git.mst@redhat.com>

---
 hw/virtio/virtio.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
hw/virtio/virtio.c +1 -1
index f4d86a3655..7c19080db5 100644
@@ -3320,7 +3320,7 @@ static int virtio_set_features_nocheck(VirtIODevice *vdev, const uint64_t *val)
     virtio_features_and(tmp, val, vdev->host_features_ex);
 
     if (k->set_features_ex) {
-        k->set_features_ex(vdev, val);
+        k->set_features_ex(vdev, tmp);
     } else if (k->set_features) {
         bad = bad || virtio_features_use_ex(tmp);
         k->set_features(vdev, tmp[0]);
Signature · 1 line
MST
#3 lore raw

[PULL 02/30] virtio-net: fix OOB read in RSC receive path

In reply to Michael S. Tsirkin

The RSC receive path parses incoming frames at guest_hdr_len byte offsets, but the backend buffer contains only host_hdr_len bytes of vnet header. If the lengths differ, RSC would read at the wrong offset and cause an OOB read.

This is no longer possible after the previous patch, but the assumption seem fragile. Along the defense in depth lines, let's validate. To ensure we are not breaking any valid setups by mistake, warn and fall back to the normal receive path when host_hdr_len != guest_hdr_len.

Fixes CVE-2026-63321
Fixes 2974e916df ("virtio-net: support RSC v4/v6 tcp traffic for Windows HCK")
Cc Jason Wang <[email protected]>
Cc Yuri Benditovich <[email protected]>
Cc Wei Xu <[email protected]>

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3623

Message-ID: <f261dcd535edc890f8636d8ae5ac1007bc32b8b4.1784891251.git.mst@redhat.com>

---
 hw/net/virtio-net.c | 7 +++++++
 1 file changed, 7 insertions(+)
hw/net/virtio-net.c +7 -0
index f0e3beb290..2761644dfd 100644
@@ -2674,6 +2674,13 @@ static ssize_t virtio_net_receive(NetClientState *nc, const uint8_t *buf,
 {
     VirtIONet *n = qemu_get_nic_opaque(nc);
     if ((n->rsc4_enabled || n->rsc6_enabled)) {
+        /* this never happens with existing backends, but just in case. */
+        if (n->host_hdr_len != n->guest_hdr_len) {
+            warn_report_once("virtio-net: host_hdr_len %zu != guest_hdr_len %zu, "
+                             "skipping RSC",
+                             n->host_hdr_len, n->guest_hdr_len);
+            return virtio_net_do_receive(nc, buf, size);
+        }
         return virtio_net_rsc_receive(nc, buf, size);
     } else {
         return virtio_net_do_receive(nc, buf, size);
Signature · 1 line
MST
#4 lore raw

[PULL 07/30] vhost: do not crash on ring map failure

In reply to Michael S. Tsirkin

When vhost_commit() rebuilds the memory region table after a flatview change, it revalidates cached host virtual addresses for active vring parts. If a mapping is stale, QEMU abort().

This is not a security problem - only the priviledged guest can control make it invalid - but not nice e.g. for driver debugging.

Let's call virtio_error() instead, marking the device as broken.

Fixes 0ca1fd2d68 ("vhost: Simplify ring verification checks")

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3783

Cc Stefano Garzarella <[email protected]>
Cc Dr. David Alan Gilbert <[email protected]>

Message-ID: <71961a7dc157f552303aeea8c99a75c5e1ce904e.1784898432.git.mst@redhat.com>

---
 hw/virtio/vhost.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
hw/virtio/vhost.c +3 -2
index af41841b52..371dca17dd 100644
@@ -755,8 +755,9 @@ static void vhost_commit(MemoryListener *listener)
                        (void *)(uintptr_t)dev->mem->regions[i].userspace_addr,
                        dev->mem->regions[i].guest_phys_addr,
                        dev->mem->regions[i].memory_size)) {
-            error_report("Verify ring failure on region %d", i);
-            abort();
+            virtio_error(dev->vdev,
+                         "Verify ring failure on region %d", i);
+            goto out;
         }
     }
 
Signature · 1 line
MST
#5 lore raw

[PULL 04/30] libvhost-user: protect against OOB writes in vu_set_inflight_fd

In reply to Michael S. Tsirkin

vu_set_inflight_fd() trusts the num_queues value from the VHOST_USER_SET_INFLIGHT_FD message without checking it against dev->max_queues, so an oversized value causes out-of-bounds writes to dev->vq.

Front end is generally trusted so not a security problem, but OOB isn't a nice way to handle frontend bugs. Let's harden this a bit:
check num_queues and panic if it's invalid.

Fixes 5f9ff1eff3 ("libvhost-user: Support tracking inflight I/O in shared memory")

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3740

Message-ID: <23b3f12388c1035f208550df9de9944c22d8d534.1784899127.git.mst@redhat.com>

---
 subprojects/libvhost-user/libvhost-user.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
subprojects/libvhost-user/libvhost-user.c +20 -0
index 2c35bddd6f..8208265588 100644
@@ -1998,6 +1998,8 @@ vu_get_inflight_fd(VuDev *dev, VhostUserMsg *vmsg)
 
     if (vmsg->size != sizeof(vmsg->payload.inflight)) {
         vu_panic(dev, "Invalid get_inflight_fd message:%d", vmsg->size);
+        vmsg_close_fds(vmsg);
+        vmsg->fd_num = 0;
         vmsg->payload.inflight.mmap_size = 0;
         return true;
     }
@@ -2005,6 +2007,15 @@ vu_get_inflight_fd(VuDev *dev, VhostUserMsg *vmsg)
     num_queues = vmsg->payload.inflight.num_queues;
     queue_size = vmsg->payload.inflight.queue_size;
 
+    if (num_queues > dev->max_queues) {
+        vu_panic(dev, "Invalid get_inflight_fd num_queues: %"PRId16,
+                 num_queues);
+        vmsg_close_fds(vmsg);
+        vmsg->fd_num = 0;
+        vmsg->payload.inflight.mmap_size = 0;
+        return true;
+    }
+
     DPRINT("set_inflight_fd num_queues: %"PRId16"\n", num_queues);
     DPRINT("set_inflight_fd queue_size: %"PRId16"\n", queue_size);
 
@@ -2052,6 +2063,7 @@ vu_set_inflight_fd(VuDev *dev, VhostUserMsg *vmsg)
         vmsg->size != sizeof(vmsg->payload.inflight)) {
         vu_panic(dev, "Invalid set_inflight_fd message size:%d fds:%d",
                  vmsg->size, vmsg->fd_num);
+        vmsg_close_fds(vmsg);
         return false;
     }
 
@@ -2061,6 +2073,13 @@ vu_set_inflight_fd(VuDev *dev, VhostUserMsg *vmsg)
     num_queues = vmsg->payload.inflight.num_queues;
     queue_size = vmsg->payload.inflight.queue_size;
 
+    if (num_queues > dev->max_queues) {
+        vu_panic(dev, "Invalid set_inflight_fd num_queues: %"PRId16,
+                 num_queues);
+        close(fd);
+        return false;
+    }
+
     DPRINT("set_inflight_fd mmap_size: %"PRId64"\n", mmap_size);
     DPRINT("set_inflight_fd mmap_offset: %"PRId64"\n", mmap_offset);
     DPRINT("set_inflight_fd num_queues: %"PRId16"\n", num_queues);
@@ -2071,6 +2090,7 @@ vu_set_inflight_fd(VuDev *dev, VhostUserMsg *vmsg)
 
     if (rc == MAP_FAILED) {
         vu_panic(dev, "set_inflight_fd mmap error: %s", strerror(errno));
+        close(fd);
         return false;
     }
 
Signature · 1 line
MST
#6 lore raw

[PULL 26/30] hw/cxl: fix OOB access in cxl_doe_cdat_rsp via entry_handle

In reply to Michael S. Tsirkin

From: Haotian Jiang <[email protected]>

cxl_doe_cdat_rsp() takes ent = req->entry_handle (uint16_t, fully guest-controlled, 0..0xFFFF) and directly indexes cdat->entry[ent] without checking ent < cdat->entry_len. For a default cxl-type3 with one volatile memory region, entry_len = 1 + CT3_CDAT_NUM_ENTRIES = 7, so any entry_handle >= 7 reads past the CDATEntry array into host heap.

The OOB-read base/length are then used in
memcpy(read_mbox + offset, base, len) at cxl_type3.c:298-299, leaking host heap memory to the guest via PCI_EXP_DOE_RD_DATA_MBOX, and potentially overflowing the 1 MiB read_mbox heap buffer when the OOB length field is large.

The same bug exists in the cxl-upstream implementation.

Existing checks do not bound ent: assert(cdat->entry_len) only ensures the table is loaded; the minimum-length check only guards against a truncated CDATReq; the entry_handle ternary at line 293 only decides the next-handle echo, not the current access; pcie_doe_get_obj_len reads header.length, not entry_handle.

Reproduce: build QEMU with CONFIG_CXL, boot
-M q35,cxl=on -device pxb-cxl,bus_nr=52 ... -device cxl-type3,...
then send a CDATReq with entry_handle=0xFFFF via the DOE mailbox at config offset 0x190. Under ASAN this reports SEGV in cxl_doe_cdat_rsp at cxl_type3.c:281.

Fixes f5ee7413d5 ("hw/mem/cxl-type3: Add CXL CDAT Data Object Exchange")
Fixes 882877fc35 ("hw/pci-bridge/cxl-upstream: Add a CDAT table access DOE")

Message-ID: <[email protected]>

---
 hw/mem/cxl_type3.c           | 3 +++
 hw/pci-bridge/cxl_upstream.c | 3 +++
 2 files changed, 6 insertions(+)
hw/mem/cxl_type3.c +3 -0
index cba05ec57d..28f41fa623 100644
@@ -278,6 +278,9 @@ static bool cxl_doe_cdat_rsp(DOECap *doe_cap)
     }
 
     ent = req->entry_handle;
+    if (ent >= cdat->entry_len) {
+        return false;
+    }
     base = cdat->entry[ent].base;
     len = cdat->entry[ent].length;
 
hw/pci-bridge/cxl_upstream.c +3 -0
index b6281cbd4c..69773ff163 100644
@@ -158,6 +158,9 @@ static bool cxl_doe_cdat_rsp(DOECap *doe_cap)
     }
 
     ent = req->entry_handle;
+    if (ent >= cdat->entry_len) {
+        return false;
+    }
     base = cdat->entry[ent].base;
     len = cdat->entry[ent].length;
 
Signature · 1 line
MST
#7 lore raw

[PULL 09/30] virtio-mmio: fix QUEUE_NUM_MAX

In reply to Michael S. Tsirkin

virtio-mmio reports VIRTQUEUE_MAX_SIZE (1024) as QUEUE_NUM_MAX for every queue, regardless of the size the device passes to virtio_add_queue().

This works by accident because QEMU mostly does not care about the ring size - the guest is the one allocating memory here. But this changes with in-order vqs where qemu is the one allocating resources. Now, specifying a larger vq than allocated causes an OOB memory access.

To fix:
- for new machine types, report the actual max queue size to guest
- for old machine types, use a compat property to allocate 1k sized queues

Fixes 525d82e323 ("virtio: fix queue size validation against allocated maximum")
Fixes CVE-2026-50626

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3882
Cc: Peter Maydell <[email protected]>
Message-ID: <8715acbb9516e67e2a776cda6f9edf105343f788.1784930765.git.mst@redhat.com>

---
 include/hw/virtio/virtio-bus.h  |  1 +
 include/hw/virtio/virtio-mmio.h |  1 +
 hw/core/machine.c               |  1 +
 hw/virtio/virtio-mmio.c         |  7 +++----
 hw/virtio/virtio.c              | 11 +++++++++++
 5 files changed, 17 insertions(+), 4 deletions(-)
include/hw/virtio/virtio-bus.h +1 -0
index 1a2d396156..f80fd71424 100644
@@ -30,6 +30,7 @@
 #include "qom/object.h"
 
 #define TYPE_VIRTIO_BUS "virtio-bus"
+#define VIRTIO_QUEUE_SIZE_OVERRIDE "x-override-queue-size"
 typedef struct VirtioBusClass VirtioBusClass;
 typedef struct VirtioBusState VirtioBusState;
 DECLARE_OBJ_CHECKERS(VirtioBusState, VirtioBusClass,
include/hw/virtio/virtio-mmio.h +1 -0
index 1644d09810..0a9069868c 100644
@@ -69,6 +69,7 @@ struct VirtIOMMIOProxy {
     /* Fields only used for non-legacy (v2) devices */
     uint32_t guest_features[2];
     VirtIOMMIOQueue vqs[VIRTIO_QUEUE_MAX];
+    uint16_t override_queue_size;
 };
 
 #endif
hw/core/machine.c +1 -0
index 805148678d..73b4d82b4a 100644
@@ -41,6 +41,7 @@
 #include "hw/arm/smmuv3.h"
 
 GlobalProperty hw_compat_11_0[] = {
+    { "virtio-mmio", VIRTIO_QUEUE_SIZE_OVERRIDE, "1024" },
     { "chardev-vc", "encoding", "cp437" },
     { "tpm-crb", "cap-chunk", "off" },
     { "tpm-crb", "x-allow-chunk-migration", "off" },
hw/virtio/virtio-mmio.c +3 -4
index 58c6d46aab..55ceaeef5f 100644
@@ -172,10 +172,7 @@ static uint64_t virtio_mmio_read(void *opaque, hwaddr offset, unsigned size)
                 >> (32 * proxy->host_features_sel);
         }
     case VIRTIO_MMIO_QUEUE_NUM_MAX:
-        if (!virtio_queue_get_num(vdev, vdev->queue_sel)) {
-            return 0;
-        }
-        return VIRTQUEUE_MAX_SIZE;
+        return virtio_queue_get_max_num(vdev, vdev->queue_sel);
     case VIRTIO_MMIO_QUEUE_PFN:
         if (!proxy->legacy) {
             qemu_log_mask(LOG_GUEST_ERROR,
@@ -738,6 +735,8 @@ static const Property virtio_mmio_properties[] = {
     DEFINE_PROP_BOOL("force-legacy", VirtIOMMIOProxy, legacy, true),
     DEFINE_PROP_BIT("ioeventfd", VirtIOMMIOProxy, flags,
                     VIRTIO_IOMMIO_FLAG_USE_IOEVENTFD_BIT, true),
+    DEFINE_PROP_UINT16(VIRTIO_QUEUE_SIZE_OVERRIDE, VirtIOMMIOProxy,
+                       override_queue_size, 0),
 };
 
 static void virtio_mmio_realizefn(DeviceState *d, Error **errp)
hw/virtio/virtio.c +11 -0
index 2d33622daf..cdc8ba76f1 100644
@@ -2568,6 +2568,17 @@ VirtQueue *virtio_add_queue(VirtIODevice *vdev, int queue_size,
     if (i == VIRTIO_QUEUE_MAX || queue_size > VIRTQUEUE_MAX_SIZE)
         abort();
 
+    BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
+    if (qbus && qbus->parent &&
+        object_property_find(OBJECT(qbus->parent), VIRTIO_QUEUE_SIZE_OVERRIDE)) {
+        int override = object_property_get_int(OBJECT(qbus->parent),
+                                               VIRTIO_QUEUE_SIZE_OVERRIDE,
+                                               &error_abort);
+        if (override) {
+            queue_size = override;
+        }
+    }
+
     vdev->vq[i].vring.num = queue_size;
     vdev->vq[i].vring.num_default = queue_size;
     vdev->vq[i].vring.align = VIRTIO_PCI_VRING_ALIGN;
Signature · 1 line
MST
#8 lore raw

[PULL 18/30] libvduse: validate vq size

In reply to Michael S. Tsirkin

libvduse assumes that vq size (aka vq num) is below VIRTQUEUE_MAX_SIZE and maps logs large enough based on this assumption.

However, vduse_queue_enable() accepts the vq size returned through VDUSE_VQ_GET_INFO without validation, so a value above VIRTQUEUE_MAX_SIZE (1024) overruns the inflight log and causes out-of-bounds writes in vduse_queue_inflight_get().

According to the virtio spec, vq size can only be reduced, not increased, so vq size must not exceed the previously configured max_size, but the kernel vduse module does not validate this for us, and we should not trust another process to follow the spec.

Validate and reject vq size values above VIRTQUEUE_MAX_SIZE.

Fixes: CVE-2026-61402
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3652

Message-ID: <bf7e71b3139875e5e00fd53970c772d6c90dc2a1.1784888961.git.mst@redhat.com>

---
 subprojects/libvduse/libvduse.c | 5 +++++
 1 file changed, 5 insertions(+)
subprojects/libvduse/libvduse.c +5 -0
index df9ca5e56f..712b97b4c3 100644
@@ -902,6 +902,11 @@ static void vduse_queue_enable(VduseVirtq *vq)
         return;
     }
 
+    if (vq_info.num > VIRTQUEUE_MAX_SIZE) {
+        fprintf(stderr, "vq[%d] vring num %u exceeds max %u\n",
+                vq->index, vq_info.num, VIRTQUEUE_MAX_SIZE);
+        return;
+    }
     vq->vring.num = vq_info.num;
     vq->vring.desc_addr = vq_info.desc_addr;
     vq->vring.avail_addr = vq_info.driver_addr;
Signature · 1 line
MST
#9 lore raw

[PULL 22/30] hw/pci-host/q35.c: Always initialize smram-region even if SMM disabled

In reply to Michael S. Tsirkin

From: Peter Maydell <[email protected]>

The MCHPCIState::smram_region looks like it ought to be SMM-specific, but it isn't, because its behaviour is "alias the PCI address space into system memory at the SMRAM_C_BASE offset", and it must be enabled for "hide SMRAM", and disabled for "show SMRAM". If the SMRAM regions are disabled, we want "hide SMRAM", so we need to initialize and place this MR. Do this in the minimal way, by moving the "bail out of realize if has_smm_ranges is false" check down below the initialization code.

This fixes a bug where disabling SMM causes the VGA screen to be blank during seabios output, until the OS graphics driver is initialized. This is most obvious for accelerators which have no SMM support (e.g. NVMM, HVF, WHPX) as there smm=off is the default, but you can also see it on KVM and TCG if you explicitly pass smm=off:
qemu-system-x86_64 -machine q35,accel=kvm,smm=off

The early return is bug-prone, so we can refactor the code to clean it up, but this is the minimal bug fix for backports, and is what Debian used to work around this:
https://salsa.debian.org/qemu-team/qemu/-/commit/6e0766f0f897dc2b75ab87dd59da0d4639bb37ee

Another proposed fix for this:
https://patchew.org/QEMU/[email protected]/
also moves an early return in mch_update-smram() and tweaks mch_update_smram() accordingly. This shouldn't be necessary, because in the no-SMM case smram_region should always be enabled and we don't want to allow the guest to make it disabled.

NetBSD bug: https://gnats.NetBSD.org/59721

Fixes b07bf7b7 ("q35: Introduce smm_ranges property for q35-pci-host")

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/2608

Message-ID: <[email protected]>

---
 hw/pci-host/q35.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)
hw/pci-host/q35.c +12 -5
index 4784b8f59b..47e726c41d 100644
@@ -596,11 +596,14 @@ static void mch_realize(PCIDevice *d, Error **errp)
                  PAM_EXPAN_BASE + i * PAM_EXPAN_SIZE, PAM_EXPAN_SIZE);
     }
 
-    if (!mch->has_smm_ranges) {
-        return;
-    }
-
-    /* if *disabled* show SMRAM to all CPUs */
+    /*
+     * This memory region looks like it's SMM specific, but it is not.
+     * It's an alias that makes the pci_address_space appear in system
+     * memory at the SMRAM_C_BASE address. The alias is enabled when the
+     * CPU should not see SMRAM, and *disabled* when the low SMRAM should be
+     * visible. So for non-SMM configs we need to create the alias, and
+     * leave it permanently enabled.
+     */
     memory_region_init_alias(&mch->smram_region, OBJECT(mch), "smram-region",
                              mch->pci_address_space, MCH_HOST_BRIDGE_SMRAM_C_BASE,
                              MCH_HOST_BRIDGE_SMRAM_C_SIZE);
@@ -608,6 +611,10 @@ static void mch_realize(PCIDevice *d, Error **errp)
                                         &mch->smram_region, 1);
     memory_region_set_enabled(&mch->smram_region, true);
 
+    if (!mch->has_smm_ranges) {
+        return;
+    }
+
     memory_region_init_alias(&mch->open_high_smram, OBJECT(mch), "smram-open-high",
                              mch->ram_memory, MCH_HOST_BRIDGE_SMRAM_C_BASE,
                              MCH_HOST_BRIDGE_SMRAM_C_SIZE);
Signature · 1 line
MST
#10 lore raw

[PULL 06/30] hw/virtio: reject zero-length packed indirect descriptor table

In reply to Michael S. Tsirkin

From: Laurent Vivier <[email protected]>

The split-ring path already rejects a zero-length indirect descriptor table since commit 7423192912af ("virtio: add checks for the size of the indirect table"). The packed-ring path is missing the same check, allowing a guest to trigger an assertion in address_space_cache_init() with a packed indirect descriptor that has len=0.

Add the same !desc.len check to the packed-ring indirect validation in both virtqueue_packed_get_avail_bytes() and virtqueue_packed_pop().

Fixes 86044b24e865 ("virtio: basic packed virtqueue support")

Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3984

Message-ID: <[email protected]>

---
 hw/virtio/virtio.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
hw/virtio/virtio.c +2 -2
index 7c19080db5..2d33622daf 100644
@@ -1475,7 +1475,7 @@ static void virtqueue_packed_get_avail_bytes(VirtQueue *vq,
         }
 
         if (desc.flags & VRING_DESC_F_INDIRECT) {
-            if (desc.len % sizeof(VRingPackedDesc)) {
+            if (!desc.len || (desc.len % sizeof(VRingPackedDesc))) {
                 virtio_error(vdev, "Invalid size for indirect buffer table");
                 goto err;
             }
@@ -1927,7 +1927,7 @@ static void *virtqueue_packed_pop(VirtQueue *vq, size_t sz)
     vring_packed_desc_read(vdev, &desc, desc_cache, i, true);
     id = desc.id;
     if (desc.flags & VRING_DESC_F_INDIRECT) {
-        if (desc.len % sizeof(VRingPackedDesc)) {
+        if (!desc.len || (desc.len % sizeof(VRingPackedDesc))) {
             virtio_error(vdev, "Invalid size for indirect buffer table");
             goto done;
         }
Signature · 1 line
MST
#11 lore raw

[PULL 03/30] virtio-net: fix short frame OOB read in receive_filter()

In reply to Michael S. Tsirkin

Within virtio-net, receive_filter() reads Ethernet header fields without any length checks.

But virtio-net sets do_not_pad in NetClientState, so backends such as socket forward frames at the size supplied by the peer without padding to the Ethernet minimum. A short frame thus causes an out-of-bounds read.

Add size checks in receive_filter() and drop the truncated frames.

Fixes CVE-2026-63320
Fixes 3831ab2094 ("qemu:virtio-net: Enable filtering based on MAC, promisc, broadcast and allmulti (Alex Williamson)")
Cc Jason Wang <[email protected]>
Cc Alex Williamson <[email protected]>

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3626

Message-ID: <ee5c77b96ab66b2dd518f146def8727216a5c495.1784895727.git.mst@redhat.com>

---
 hw/net/virtio-net.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)
hw/net/virtio-net.c +12 -1
index 2761644dfd..7e0c984ec6 100644
@@ -1744,10 +1744,21 @@ static int receive_filter(VirtIONet *n, const uint8_t *buf, int size)
     if (n->promisc)
         return 1;
 
+    if (size < n->host_hdr_len + 14) {
+        /* Truncated ethernet packet */
+        return 0;
+    }
+
     ptr += n->host_hdr_len;
 
     if (!memcmp(&ptr[12], vlan, sizeof(vlan))) {
-        int vid = lduw_be_p(ptr + 14) & 0xfff;
+        int vid;
+
+        /* Truncated vlan packet */
+        if (size < n->host_hdr_len + 16) {
+            return 0;
+        }
+        vid = lduw_be_p(ptr + 14) & 0xfff;
         if (!(n->vlans[vid >> 5] & (1U << (vid & 0x1f))))
             return 0;
     }
Signature · 1 line
MST
#12 lore raw

[PULL 27/30] intel_iommu: Check address mask before using it in pasid-based iotlb invalidation

In reply to Michael S. Tsirkin

From: Clément MATHIEU--DRIF <[email protected]>

Prevent a buggy driver to execute malformed invalidation operations.

Add the same assert as in vtd_iotlb_page_invalidate.

Message-ID: <[email protected]>

---
 hw/i386/intel_iommu.c | 9 +++++++++
 1 file changed, 9 insertions(+)
hw/i386/intel_iommu.c +9 -0
index d1af7a3135..82c3c3b2c3 100644
@@ -3021,6 +3021,8 @@ static void vtd_piotlb_page_invalidate(IntelIOMMUState *s, uint16_t domain_id,
 {
     VTDIOTLBPageInvInfo info;
 
+    assert(am <= VTD_MAMV);
+
     info.domain_id = domain_id;
     info.pasid = pasid;
     info.addr = addr;
@@ -3060,6 +3062,13 @@ static bool vtd_process_piotlb_desc(IntelIOMMUState *s,
 
     case VTD_INV_DESC_PIOTLB_PSI_IN_PASID:
         am = VTD_INV_DESC_PIOTLB_AM(inv_desc->val[1]);
+        if (am > VTD_MAMV) {
+            error_report_once("%s: invalid piotlb inv desc: hi=0x%"PRIx64
+                              ", lo=0x%"PRIx64" (am=%u > VTD_MAMV=%llu)",
+                              __func__, inv_desc->val[1], inv_desc->val[0],
+                              am, VTD_MAMV);
+            return false;
+        }
         addr = (hwaddr) VTD_INV_DESC_PIOTLB_ADDR(inv_desc->val[1]);
         vtd_piotlb_page_invalidate(s, domain_id, pasid, addr, am,
                                    VTD_INV_DESC_PIOTLB_IH(inv_desc));
Signature · 1 line
MST
#13 lore raw

[PULL 23/30] hw/pci-host/q35.c: Factor out creation of SMRAM MRs

In reply to Michael S. Tsirkin

From: Peter Maydell <[email protected]>

mch_realize has a large section that deals with initializing the SMRAM-specific MemoryRegions. Currently we do an early return from the realize function if mch->has_smm_ranges is false, but this has the potential for bugs if somebody adds new code at the end of the function that isn't SMM-specific. Pull the MR init code out into its own function, so we can do the smm-ranges specific handling in the realize function in a more obvious way.

This commit shouldn't change behaviour at all.

Message-ID: <[email protected]>

---
 hw/pci-host/q35.c | 91 ++++++++++++++++++++++++-----------------------
 1 file changed, 47 insertions(+), 44 deletions(-)
hw/pci-host/q35.c +47 -44
index 47e726c41d..e42b84b978 100644
@@ -572,49 +572,9 @@ static void mch_reset(DeviceState *qdev)
     mch_update(mch);
 }
 
-static void mch_realize(PCIDevice *d, Error **errp)
+static void mch_init_smram_regions(MCHPCIState *mch)
 {
-    int i;
-    MCHPCIState *mch = MCH_PCI_DEVICE(d);
-
-    if (mch->ext_tseg_mbytes > MCH_HOST_BRIDGE_EXT_TSEG_MBYTES_MAX) {
-        error_setg(errp, "invalid extended-tseg-mbytes value: %" PRIu16,
-                   mch->ext_tseg_mbytes);
-        return;
-    }
-
-    /* setup pci memory mapping */
-    pc_pci_as_mapping_init(mch->system_memory, mch->pci_address_space);
-
-    /* PAM */
-    init_pam(&mch->pam_regions[0], OBJECT(mch), mch->ram_memory,
-             mch->system_memory, mch->pci_address_space,
-             PAM_BIOS_BASE, PAM_BIOS_SIZE);
-    for (i = 0; i < ARRAY_SIZE(mch->pam_regions) - 1; ++i) {
-        init_pam(&mch->pam_regions[i + 1], OBJECT(mch), mch->ram_memory,
-                 mch->system_memory, mch->pci_address_space,
-                 PAM_EXPAN_BASE + i * PAM_EXPAN_SIZE, PAM_EXPAN_SIZE);
-    }
-
-    /*
-     * This memory region looks like it's SMM specific, but it is not.
-     * It's an alias that makes the pci_address_space appear in system
-     * memory at the SMRAM_C_BASE address. The alias is enabled when the
-     * CPU should not see SMRAM, and *disabled* when the low SMRAM should be
-     * visible. So for non-SMM configs we need to create the alias, and
-     * leave it permanently enabled.
-     */
-    memory_region_init_alias(&mch->smram_region, OBJECT(mch), "smram-region",
-                             mch->pci_address_space, MCH_HOST_BRIDGE_SMRAM_C_BASE,
-                             MCH_HOST_BRIDGE_SMRAM_C_SIZE);
-    memory_region_add_subregion_overlap(mch->system_memory, MCH_HOST_BRIDGE_SMRAM_C_BASE,
-                                        &mch->smram_region, 1);
-    memory_region_set_enabled(&mch->smram_region, true);
-
-    if (!mch->has_smm_ranges) {
-        return;
-    }
-
+    /* Initialize all the SMRAM specific MemoryRegions */
     memory_region_init_alias(&mch->open_high_smram, OBJECT(mch), "smram-open-high",
                              mch->ram_memory, MCH_HOST_BRIDGE_SMRAM_C_BASE,
                              MCH_HOST_BRIDGE_SMRAM_C_SIZE);
@@ -670,9 +630,52 @@ static void mch_realize(PCIDevice *d, Error **errp)
     memory_region_set_enabled(&mch->smbase_window, false);
     memory_region_add_subregion(&mch->smram, MCH_HOST_BRIDGE_SMBASE_ADDR,
                                 &mch->smbase_window);
+}
 
-    object_property_add_const_link(qdev_get_machine(), "smram",
-                                   OBJECT(&mch->smram));
+static void mch_realize(PCIDevice *d, Error **errp)
+{
+    int i;
+    MCHPCIState *mch = MCH_PCI_DEVICE(d);
+
+    if (mch->ext_tseg_mbytes > MCH_HOST_BRIDGE_EXT_TSEG_MBYTES_MAX) {
+        error_setg(errp, "invalid extended-tseg-mbytes value: %" PRIu16,
+                   mch->ext_tseg_mbytes);
+        return;
+    }
+
+    /* setup pci memory mapping */
+    pc_pci_as_mapping_init(mch->system_memory, mch->pci_address_space);
+
+    /* PAM */
+    init_pam(&mch->pam_regions[0], OBJECT(mch), mch->ram_memory,
+             mch->system_memory, mch->pci_address_space,
+             PAM_BIOS_BASE, PAM_BIOS_SIZE);
+    for (i = 0; i < ARRAY_SIZE(mch->pam_regions) - 1; ++i) {
+        init_pam(&mch->pam_regions[i + 1], OBJECT(mch), mch->ram_memory,
+                 mch->system_memory, mch->pci_address_space,
+                 PAM_EXPAN_BASE + i * PAM_EXPAN_SIZE, PAM_EXPAN_SIZE);
+    }
+
+    /*
+     * This memory region looks like it's SMM specific, but it is not.
+     * It's an alias that makes the pci_address_space appear in system
+     * memory at the SMRAM_C_BASE address. The alias is enabled when the
+     * CPU should not see SMRAM, and *disabled* when the low SMRAM should be
+     * visible. So for non-SMM configs we need to create the alias, and
+     * leave it permanently enabled.
+     */
+    memory_region_init_alias(&mch->smram_region, OBJECT(mch), "smram-region",
+                             mch->pci_address_space, MCH_HOST_BRIDGE_SMRAM_C_BASE,
+                             MCH_HOST_BRIDGE_SMRAM_C_SIZE);
+    memory_region_add_subregion_overlap(mch->system_memory, MCH_HOST_BRIDGE_SMRAM_C_BASE,
+                                        &mch->smram_region, 1);
+    memory_region_set_enabled(&mch->smram_region, true);
+
+    if (mch->has_smm_ranges) {
+        mch_init_smram_regions(mch);
+        object_property_add_const_link(qdev_get_machine(), "smram",
+                                       OBJECT(&mch->smram));
+    }
 }
 
 static const Property mch_props[] = {
Signature · 1 line
MST
#14 lore raw

[PULL 24/30] hw/pci-host/q35.c: Avoid early return in mch_write_config()

In reply to Michael S. Tsirkin

From: Peter Maydell <[email protected]>

In mch_write_config() we return early if has_smm_ranges is false. This is slightly bug-prone because it leaves the door open to somebody later adding non-SMM-specific code at the bottom of the function.

This case isn't as bad as the one in realize, because the function is a lot shorter. But putting the handling of the three SMM specific ranges into an if() rather than having an early return seems better.

Message-ID: <[email protected]>

---
 hw/pci-host/q35.c | 26 ++++++++++++--------------
 1 file changed, 12 insertions(+), 14 deletions(-)
hw/pci-host/q35.c +12 -14
index e42b84b978..f4556ad03a 100644
@@ -485,22 +485,20 @@ static void mch_write_config(PCIDevice *d,
         mch_update_pciexbar(mch);
     }
 
-    if (!mch->has_smm_ranges) {
-        return;
-    }
+    if (mch->has_smm_ranges) {
+        if (ranges_overlap(address, len, MCH_HOST_BRIDGE_SMRAM,
+                           MCH_HOST_BRIDGE_SMRAM_SIZE)) {
+            mch_update_smram(mch);
+        }
 
-    if (ranges_overlap(address, len, MCH_HOST_BRIDGE_SMRAM,
-                       MCH_HOST_BRIDGE_SMRAM_SIZE)) {
-        mch_update_smram(mch);
-    }
+        if (ranges_overlap(address, len, MCH_HOST_BRIDGE_EXT_TSEG_MBYTES,
+                           MCH_HOST_BRIDGE_EXT_TSEG_MBYTES_SIZE)) {
+            mch_update_ext_tseg_mbytes(mch);
+        }
 
-    if (ranges_overlap(address, len, MCH_HOST_BRIDGE_EXT_TSEG_MBYTES,
-                       MCH_HOST_BRIDGE_EXT_TSEG_MBYTES_SIZE)) {
-        mch_update_ext_tseg_mbytes(mch);
-    }
-
-    if (ranges_overlap(address, len, MCH_HOST_BRIDGE_F_SMBASE, 1)) {
-        mch_update_smbase_smram(mch);
+        if (ranges_overlap(address, len, MCH_HOST_BRIDGE_F_SMBASE, 1)) {
+            mch_update_smbase_smram(mch);
+        }
     }
 }
 
Signature · 1 line
MST
#15 lore raw

[PULL 29/30] hw/virtio-rng: Fix host use-after-free (CVE-2026-50624)

In reply to Michael S. Tsirkin

From: Laurent Vivier <[email protected]>

Fix a heap-use-after-free in the virtio-rng frontend when a delayed rng-random backend completion arrives after the virtio-rng device has been hot-unplugged.

Fixes: CVE-2026-50624
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3917

Message-ID: <[email protected]>

---
 include/system/rng.h   | 14 ++++++++++++++
 backends/rng.c         | 16 ++++++++++++++++
 hw/virtio/virtio-rng.c |  2 ++
 3 files changed, 32 insertions(+)
include/system/rng.h +14 -0
index e383f87d20..a2667d75ef 100644
@@ -86,4 +86,18 @@ void rng_backend_request_entropy(RngBackend *s, size_t size,
  * deleted.
  */
 void rng_backend_finalize_request(RngBackend *s, RngRequest *req);
+
+/**
+ * rng_backend_cancel_requests:
+ * @s: the backend that created the request
+ * @receive_entropy: the function invoked when entropy is available
+ * @opaque: data passed to @receive_entropy
+ *
+ * This function is used by the front-end to cancel all requests to a
+ * given backend. Requests to cancel are identified by the receive_entropy
+ * function and the data passed to the function.
+ */
+void rng_backend_cancel_requests(RngBackend *s,
+                                 EntropyReceiveFunc *receive_entropy,
+                                 const void *opaque);
 #endif
backends/rng.c +16 -0
index ab94dfea85..7bed62616a 100644
@@ -68,6 +68,22 @@ static void rng_backend_free_request(RngRequest *req)
     g_free(req);
 }
 
+void rng_backend_cancel_requests(RngBackend *s,
+                                 EntropyReceiveFunc *receive_entropy,
+                                 const void *opaque)
+{
+    RngRequest *req, *next;
+
+    QSIMPLEQ_FOREACH_SAFE(req, &s->requests, next, next) {
+        if (req->receive_entropy != receive_entropy ||
+            req->opaque != opaque) {
+            continue;
+        }
+        QSIMPLEQ_REMOVE(&s->requests, req, RngRequest, next);
+        rng_backend_free_request(req);
+    }
+}
+
 static void rng_backend_free_requests(RngBackend *s)
 {
     RngRequest *req, *next;
hw/virtio/virtio-rng.c +2 -0
index 66690a34dc..d68d901195 100644
@@ -234,6 +234,8 @@ static void virtio_rng_device_unrealize(DeviceState *dev)
     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
     VirtIORNG *vrng = VIRTIO_RNG(dev);
 
+    rng_backend_cancel_requests(vrng->rng, chr_read, vrng);
+
     qemu_del_vm_change_state_handler(vrng->vmstate);
     timer_free(vrng->rate_limit_timer);
     virtio_del_queue(vdev, 0);
Signature · 1 line
MST
#16 lore raw

[PULL 20/30] virtio-snd: check rx buffer descriptor size

In reply to Michael S. Tsirkin

From: Manos Pitsidianakis <[email protected]>

It must be at least sizeof(virtio_snd_pcm_status).

I haven't verified if it's possible to get an underflow, but coverity points it out in CID 1547527 so add a check.

Message-ID: <[email protected]>

---
 hw/audio/virtio-snd.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)
hw/audio/virtio-snd.c +5 -3
index fb5cff3866..93fbcfb43f 100644
@@ -970,12 +970,14 @@ static void virtio_snd_handle_rx_xfer(VirtIODevice *vdev, VirtQueue *vq)
         }
 
         stream = vsnd->pcm.streams[stream_id];
-        if (stream == NULL || stream->info.direction != VIRTIO_SND_D_INPUT) {
+        size = iov_size(elem->in_sg, elem->in_num);
+        if (stream == NULL
+            || stream->info.direction != VIRTIO_SND_D_INPUT
+            || size < sizeof(virtio_snd_pcm_status)) {
             goto rx_err;
         }
+        size -= sizeof(virtio_snd_pcm_status);
         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
-            size = iov_size(elem->in_sg, elem->in_num) -
-                sizeof(virtio_snd_pcm_status);
             buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size);
             buffer->elem = elem;
             buffer->vq = vq;
Signature · 1 line
MST
#17 lore raw

[PULL 16/30] libvhost-user: fix heap overflow in vu_check_queue_inflights

In reply to Michael S. Tsirkin

vu_check_queue_inflights counts inflight descriptors using inflight == 1 but copies entries using inflight != 0. If the inflight field contains an unexpected non-0/1 value, the function copies more entries than it allocates and overflows the heap buffer.

Stop the copy pass once resubmit_num reaches the counted inuse value.
Note: the value is not guest-accessible so not a security vulnerability.

Fixes CVE-2026-63110
Fixes 5f9ff1eff3 ("libvhost-user: Support tracking inflight I/O in shared memory")
Cc Xie Yongji <[email protected]>
Cc Stefano Garzarella <[email protected]>

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3974

Message-Id: <e2315efc526c0ee918485df4be69e2b26e8b7a73.1784892981.git.mst@redhat.com>

---
 subprojects/libvhost-user/libvhost-user.c | 7 +++++++
 1 file changed, 7 insertions(+)
subprojects/libvhost-user/libvhost-user.c +7 -0
index c1c13dbc90..a74d814bb4 100644
@@ -1408,6 +1408,13 @@ vu_check_queue_inflights(VuDev *dev, VuVirtq *vq)
 
         for (i = 0; i < vq->inflight->desc_num; i++) {
             if (vq->inflight->desc[i].inflight) {
+                /*
+                 * We earlier counted exactly vq->inuse in flight -
+                 * what is going on?
+                 */
+                if (vq->resubmit_num >= vq->inuse) {
+                    return -1;
+                }
                 vq->resubmit_list[vq->resubmit_num].index = i;
                 vq->resubmit_list[vq->resubmit_num].counter =
                                         vq->inflight->desc[i].counter;
Signature · 1 line
MST
#18 lore raw

[PULL 14/30] virtio-pmem: wait for flush requests on unrealize

In reply to Michael S. Tsirkin

virtio_pmem_flush submits fsync requests to the thread pool and stores a VirtIOPMEM pointer in each request. If device is deleted e.g. by hot-unplug, once these complete, done_cb can run after virtio_pmem_unrealize frees the device, causing a use-after-free.

Track in-flight requests and wait in virtio_pmem_unrealize until their completions finish before tearing the device down.

Fixes CVE-2026-63323
Fixes 5f503cd9f3 ("virtio-pmem: add virtio device")
Cc David Hildenbrand <[email protected]>
Cc Pankaj Gupta <[email protected]>

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3938

Message-ID: <417b6685f37ce818c660ca3c84945992f5c30dcf.1784894206.git.mst@redhat.com>

---
 include/hw/virtio/virtio-pmem.h |  1 +
 hw/virtio/virtio-pmem.c         | 17 +++++++++++++++--
 2 files changed, 16 insertions(+), 2 deletions(-)
include/hw/virtio/virtio-pmem.h +1 -0
index 9cce600d0b..bb959d10cf 100644
@@ -32,6 +32,7 @@ struct VirtIOPMEM {
     VirtQueue *rq_vq;
     uint64_t start;
     HostMemoryBackend *memdev;
+    unsigned int inflight;
 };
 
 struct VirtIOPMEMClass {
hw/virtio/virtio-pmem.c +15 -2
index c3b3299c9c..6f7271c140 100644
@@ -23,6 +23,7 @@
 #include "standard-headers/linux/virtio_pmem.h"
 #include "system/hostmem.h"
 #include "block/thread-pool.h"
+#include "qemu/aio-wait.h"
 #include "trace.h"
 
 typedef struct VirtIODeviceRequest {
@@ -54,14 +55,20 @@ static int worker_cb(void *opaque)
 static void done_cb(void *opaque, int ret)
 {
     VirtIODeviceRequest *req_data = opaque;
+    VirtIOPMEM *pmem = req_data->pmem;
     int len = iov_from_buf(req_data->elem.in_sg, req_data->elem.in_num, 0,
                               &req_data->resp, sizeof(struct virtio_pmem_resp));
 
     /* Callbacks are serialized, so no need to use atomic ops. */
-    virtqueue_push(req_data->pmem->rq_vq, &req_data->elem, len);
-    virtio_notify((VirtIODevice *)req_data->pmem, req_data->pmem->rq_vq);
+    virtqueue_push(pmem->rq_vq, &req_data->elem, len);
+    virtio_notify((VirtIODevice *)pmem, pmem->rq_vq);
     trace_virtio_pmem_response();
     g_free(req_data);
+
+    pmem->inflight--;
+    if (!pmem->inflight) {
+        aio_wait_kick();
+    }
 }
 
 static void virtio_pmem_flush(VirtIODevice *vdev, VirtQueue *vq)
@@ -85,6 +92,7 @@ static void virtio_pmem_flush(VirtIODevice *vdev, VirtQueue *vq)
     req_data->fd   = memory_region_get_fd(&backend->mr);
     req_data->pmem = pmem;
     req_data->vdev = vdev;
+    pmem->inflight++;
     thread_pool_submit_aio(worker_cb, req_data, done_cb, req_data);
 }
 
@@ -122,6 +130,7 @@ static void virtio_pmem_realize(DeviceState *dev, Error **errp)
     host_memory_backend_set_mapped(pmem->memdev, true);
     virtio_init(vdev, VIRTIO_ID_PMEM, sizeof(struct virtio_pmem_config));
     pmem->rq_vq = virtio_add_queue(vdev, 128, virtio_pmem_flush);
+    pmem->inflight = 1;
 }
 
 static void virtio_pmem_unrealize(DeviceState *dev)
@@ -129,6 +138,10 @@ static void virtio_pmem_unrealize(DeviceState *dev)
     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
     VirtIOPMEM *pmem = VIRTIO_PMEM(dev);
 
+    /* Release the device's own reference and wait for in-flight flushes */
+    pmem->inflight--;
+    AIO_WAIT_WHILE(NULL, pmem->inflight > 0);
+
     host_memory_backend_set_mapped(pmem->memdev, false);
     virtio_delete_queue(pmem->rq_vq);
     virtio_cleanup(vdev);
Signature · 1 line
MST
#19 lore raw

[PULL 30/30] backends/rng: cap request size to avoid oversized allocation

In reply to Michael S. Tsirkin

From: Laurent Vivier <[email protected]>

rng_backend_request_entropy() uses the requested size to allocate a buffer with g_malloc(). With virtio-rng, this size comes from guest-supplied descriptor lengths. A malicious guest can set a very large descriptor length, causing QEMU to attempt a multi-gigabyte allocation and abort.

Cap the allocation to 64 KiB. The virtio-rng queue size is hardcoded to 8 entries, the EGD backend protocol limits requests to 255 bytes, the Linux kernel hwrng framework requests at most SMP_CACHE_BYTES per call (64 bytes on x86_64), and the Windows viorng driver uses a 4 KiB buffer. The worst legitimate case is 8 x 4 KiB = 32 KiB, so 64 KiB is well above any legitimate use.

Fixes 14417039653d ("virtio-rng: use virtqueue_get_avail_bytes, fix migration")

Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3983

Message-ID: <[email protected]>

---
 backends/rng.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
backends/rng.c +4 -1
index 7bed62616a..bc9c7a5ab3 100644
@@ -11,11 +11,14 @@
  */
 
 #include "qemu/osdep.h"
+#include "qemu/units.h"
 #include "system/rng.h"
 #include "qapi/error.h"
 #include "qemu/module.h"
 #include "qom/object_interfaces.h"
 
+#define RNG_MAX_REQUEST_SIZE (64 * KiB)
+
 void rng_backend_request_entropy(RngBackend *s, size_t size,
                                  EntropyReceiveFunc *receive_entropy,
                                  void *opaque)
@@ -27,7 +30,7 @@ void rng_backend_request_entropy(RngBackend *s, size_t size,
         req = g_malloc(sizeof(*req));
 
         req->offset = 0;
-        req->size = size;
+        req->size = MIN(size, RNG_MAX_REQUEST_SIZE);
         req->receive_entropy = receive_entropy;
         req->opaque = opaque;
         req->data = g_malloc(req->size);
Signature · 1 line
MST
#20 lore raw

[PULL 25/30] hw/virtio/vdpa-dev: pass set_config buffer to vhost backend

In reply to Michael S. Tsirkin

From: GuoHan Zhao <[email protected]>

vhost_vdpa_device_set_config() receives the updated config buffer, but forwards s->config to the vhost backend. Since s->config is refreshed by get_config(), it may contain stale backend state.

Pass the supplied config buffer to vhost_dev_set_config() instead.

Fixes b430a2bd2303 ("vdpa: add vdpa-dev support")

Message-ID: <[email protected]>

---
 hw/virtio/vdpa-dev.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
hw/virtio/vdpa-dev.c +1 -1
index 089a77f4d0..6dc684ab09 100644
@@ -212,7 +212,7 @@ vhost_vdpa_device_set_config(VirtIODevice *vdev, const uint8_t *config)
     VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev);
     int ret;
 
-    ret = vhost_dev_set_config(&s->dev, s->config, 0, s->config_size,
+    ret = vhost_dev_set_config(&s->dev, config, 0, s->config_size,
                                VHOST_SET_CONFIG_TYPE_FRONTEND);
     if (ret) {
         error_report("set device config space failed");
Signature · 1 line
MST
#21 lore raw

[PULL 10/30] virtio: fix queue size validation against allocated maximum

In reply to Michael S. Tsirkin

virtio_add_queue() allocates used_elems for num_default entries, but virtio_queue_set_num() accepts larger guest-supplied queue sizes up to VIRTQUEUE_MAX_SIZE. With VIRTIO_F_IN_ORDER, this lets the guest drive used_elems accesses past the allocation and cause out-of-bounds reads and writes.

Reject queue sizes larger than num_default in virtio_queue_set_num() and mark the device broken.

Fixes e63c0ba1bc ("virtio: Add support for guest setting of queue size")
Fixes CVE-2026-50626
Cc Peter Maydell <[email protected]>

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3882
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3921
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3923
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3613

Message-ID: <eb7cc3672a20db392f577edbece2300aa6754dd3.1784898967.git.mst@redhat.com>

---
 hw/virtio/virtio.c | 5 +++++
 1 file changed, 5 insertions(+)
hw/virtio/virtio.c +5 -0
index cdc8ba76f1..4a6430c31c 100644
@@ -2418,6 +2418,11 @@ void virtio_queue_set_num(VirtIODevice *vdev, int n, int num)
         num < 0) {
         return;
     }
+    if (num > vdev->vq[n].vring.num_default) {
+        virtio_error(vdev, "virtio: queue %d size %d exceeds max size %u",
+                     n, num, vdev->vq[n].vring.num_default);
+        return;
+    }
     vdev->vq[n].vring.num = num;
 }
 
Signature · 1 line
MST
#22 lore raw

[PULL 05/30] libvhost-user: protect against OOB vring queue access

In reply to Michael S. Tsirkin

SET_VRING_NUM, SET_VRING_ADDR, SET_VRING_BASE, and GET_VRING_BASE handlers all use the queue index from the message to access dev->vq[] without checking that it is below dev->max_queues, so a malformed message causes an out-of-bounds heap access.

Frontend is trusted so not a security problem, but an OOB access is not a nice way to handle errors. Check, and panic.

Fixes 7b2e5c65f4 ("contrib: add libvhost-user")

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3741

Message-ID: <dbba777b25f86587c8d131891a2b4b2be97e5c5b.1784899069.git.mst@redhat.com>

---
 subprojects/libvhost-user/libvhost-user.c | 28 ++++++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)
subprojects/libvhost-user/libvhost-user.c +27 -1
index 8208265588..42a5e2f1f5 100644
@@ -1200,6 +1200,12 @@ vu_set_vring_num_exec(VuDev *dev, VhostUserMsg *vmsg)
 
     DPRINT("State.index: %u\n", index);
     DPRINT("State.num:   %u\n", num);
+
+    if (index >= dev->max_queues) {
+        vu_panic(dev, "Invalid vring_num index: %u", index);
+        return false;
+    }
+
     dev->vq[index].vring.num = num;
 
     return false;
@@ -1210,7 +1216,7 @@ vu_set_vring_addr_exec(VuDev *dev, VhostUserMsg *vmsg)
 {
     struct vhost_vring_addr addr = vmsg->payload.addr, *vra = &addr;
     unsigned int index = vra->index;
-    VuVirtq *vq = &dev->vq[index];
+    VuVirtq *vq;
 
     DPRINT("vhost_vring_addr:\n");
     DPRINT("    index:  %d\n", vra->index);
@@ -1220,6 +1226,12 @@ vu_set_vring_addr_exec(VuDev *dev, VhostUserMsg *vmsg)
     DPRINT("    avail_user_addr:  0x%016" PRIx64 "\n", (uint64_t)vra->avail_user_addr);
     DPRINT("    log_guest_addr:   0x%016" PRIx64 "\n", (uint64_t)vra->log_guest_addr);
 
+    if (index >= dev->max_queues) {
+        vu_panic(dev, "Invalid vring_addr index: %u", index);
+        return false;
+    }
+
+    vq = &dev->vq[index];
     vq->vra = *vra;
     vq->vring.flags = vra->flags;
     vq->vring.log_guest_addr = vra->log_guest_addr;
@@ -1256,6 +1268,12 @@ vu_set_vring_base_exec(VuDev *dev, VhostUserMsg *vmsg)
 
     DPRINT("State.index: %u\n", index);
     DPRINT("State.num:   %u\n", num);
+
+    if (index >= dev->max_queues) {
+        vu_panic(dev, "Invalid vring_base index: %u", index);
+        return false;
+    }
+
     dev->vq[index].shadow_avail_idx = dev->vq[index].last_avail_idx = num;
 
     return false;
@@ -1267,6 +1285,14 @@ vu_get_vring_base_exec(VuDev *dev, VhostUserMsg *vmsg)
     unsigned int index = vmsg->payload.state.index;
 
     DPRINT("State.index: %u\n", index);
+
+    if (index >= dev->max_queues) {
+        vu_panic(dev, "Invalid vring_base index: %u", index);
+        vmsg->payload.state.num = 0;
+        vmsg->size = sizeof(vmsg->payload.state);
+        return true;
+    }
+
     vmsg->payload.state.num = dev->vq[index].last_avail_idx;
     vmsg->size = sizeof(vmsg->payload.state);
 
Signature · 1 line
MST
#23 lore raw

[PULL 11/30] virtio: stop migrating num_default, validate vring.num on load

In reply to Michael S. Tsirkin

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 <[email protected]>
Cc Peter Maydell <[email protected]>

Message-ID: <3e6a7c403f93acc37af6a6332fdc65049ae218fb.1784894327.git.mst@redhat.com>

---
 hw/virtio/virtio.c | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)
hw/virtio/virtio.c +7 -9
index 4a6430c31c..2766217ddd 100644
@@ -2805,14 +2805,6 @@ static bool virtio_packed_virtqueue_needed(void *opaque)
 
 static bool virtio_ringsize_needed(void *opaque)
 {
-    VirtIODevice *vdev = opaque;
-    int i;
-
-    for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
-        if (vdev->vq[i].vring.num != vdev->vq[i].vring.num_default) {
-            return true;
-        }
-    }
     return false;
 }
 
@@ -2901,7 +2893,7 @@ static const VMStateDescription vmstate_ringsize = {
     .version_id = 1,
     .minimum_version_id = 1,
     .fields = (const VMStateField[]) {
-        VMSTATE_UINT32(vring.num_default, struct VirtQueue),
+        VMSTATE_UNUSED(sizeof(uint32_t)),
         VMSTATE_END_OF_LIST()
     }
 };
@@ -3581,6 +3573,12 @@ virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
 
     for (i = 0; i < num; i++) {
         vdev->vq[i].vring.num = qemu_get_be32(f);
+        if (vdev->vq[i].vring.num > vdev->vq[i].vring.num_default) {
+            error_report("VQ %d vring.num %u exceeds allocated max %u",
+                         i, vdev->vq[i].vring.num,
+                         vdev->vq[i].vring.num_default);
+            return -1;
+        }
         if (k->has_variable_vring_alignment) {
             vdev->vq[i].vring.align = qemu_get_be32(f);
         }
Signature · 1 line
MST
#24 lore raw

[PULL 21/30] virtio-snd: check for overflow before g_malloc0

In reply to Michael S. Tsirkin

From: Manos Pitsidianakis <[email protected]>

Coverity points out one g_malloc0 overflow, but it seems to be a false positive. Add a check to it regardless to fortify the code, and also add checks for every other g_malloc0 use.

Resolves: Coverity CID 1547527

Message-ID: <[email protected]>

---
 hw/audio/virtio-snd.c | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)
hw/audio/virtio-snd.c +12 -4
index 93fbcfb43f..694bcebb60 100644
@@ -850,7 +850,7 @@ static void virtio_snd_handle_tx_xfer(VirtIODevice *vdev, VirtQueue *vq)
     VirtIOSound *vsnd = VIRTIO_SND(vdev);
     VirtIOSoundPCMBuffer *buffer;
     VirtQueueElement *elem;
-    size_t msg_sz, size;
+    size_t msg_sz, size, tmp;
     virtio_snd_pcm_xfer hdr;
     uint32_t stream_id;
     /*
@@ -880,6 +880,8 @@ static void virtio_snd_handle_tx_xfer(VirtIODevice *vdev, VirtQueue *vq)
         if (msg_sz != sizeof(virtio_snd_pcm_xfer)) {
             goto tx_err;
         }
+        assert(iov_size(elem->out_sg, elem->out_num) >= msg_sz);
+        size = iov_size(elem->out_sg, elem->out_num) - msg_sz;
         stream_id = le32_to_cpu(hdr.stream_id);
 
         if (stream_id >= vsnd->snd_conf.streams
@@ -892,9 +894,11 @@ static void virtio_snd_handle_tx_xfer(VirtIODevice *vdev, VirtQueue *vq)
             goto tx_err;
         }
 
+        /* Check for g_malloc0 overflow. */
+        if (!g_size_checked_add(&tmp, sizeof(VirtIOSoundPCMBuffer), size)) {
+            goto tx_err;
+        }
         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
-            size = iov_size(elem->out_sg, elem->out_num) - msg_sz;
-
             buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size);
             buffer->elem = elem;
             buffer->populated = false;
@@ -932,7 +936,7 @@ static void virtio_snd_handle_rx_xfer(VirtIODevice *vdev, VirtQueue *vq)
     VirtIOSound *vsnd = VIRTIO_SND(vdev);
     VirtIOSoundPCMBuffer *buffer;
     VirtQueueElement *elem;
-    size_t msg_sz, size;
+    size_t msg_sz, size, tmp;
     virtio_snd_pcm_xfer hdr;
     uint32_t stream_id;
     /*
@@ -977,6 +981,10 @@ static void virtio_snd_handle_rx_xfer(VirtIODevice *vdev, VirtQueue *vq)
             goto rx_err;
         }
         size -= sizeof(virtio_snd_pcm_status);
+        /* Check for g_malloc0 overflow. */
+        if (!g_size_checked_add(&tmp, sizeof(VirtIOSoundPCMBuffer), size)) {
+            goto rx_err;
+        }
         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
             buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size);
             buffer->elem = elem;
Signature · 1 line
MST
#25 lore raw

[PULL 19/30] virtio-iommu: fix OOM due to unbounded call_rcu

In reply to Michael S. Tsirkin

Currently, within virtio-iommu, handle_command processes the command vq without any limits on the number of entries processed.
This can easily and repeatedly enable/disable multiple memory regions. Within the memory code, this causes an accumulation of an unbounded number of RCU-deferred FlatViews - each of these is supposed to be freed with call_rcu, but that never happens because the main thread never returns to the main loop.

Given FlatView is big, it's easy to have this balloon out to multiple Gigabytes of memory.

Limit the loop defer any remaining work to a timer.

Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3930

Message-ID: <eb46ab360dbe28c29cfa78812a7440dcb7444d59.1784807826.git.mst@redhat.com>

---
 include/hw/virtio/virtio-iommu.h |  1 +
 hw/virtio/virtio-iommu.c         | 29 +++++++++++++++++++++++++++++
 2 files changed, 30 insertions(+)
include/hw/virtio/virtio-iommu.h +1 -0
index 3b86050f2c..1f265540ad 100644
@@ -65,6 +65,7 @@ struct VirtIOIOMMU {
     GTree *domains;
     QemuRecMutex mutex;
     GTree *endpoints;
+    QEMUTimer *cmd_timer;
     bool boot_bypass;
     Notifier machine_done;
     bool granule_frozen;
hw/virtio/virtio-iommu.c +29 -0
index 08f7e8b783..533bd5073f 100644
@@ -993,6 +993,18 @@ static int virtio_iommu_handle_probe(VirtIOIOMMU *s,
     return ret ? ret : virtio_iommu_probe(s, &req, buf);
 }
 
+static void virtio_iommu_handle_command(VirtIODevice *vdev, VirtQueue *vq);
+
+static void virtio_iommu_handle_command_timer(void *opaque)
+{
+    VirtIOIOMMU *s = opaque;
+    VirtIODevice *vdev = VIRTIO_DEVICE(s);
+
+    if (virtio_device_started(vdev, vdev->status) && !vdev->broken) {
+        virtio_iommu_handle_command(vdev, s->req_vq);
+    }
+}
+
 static void virtio_iommu_handle_command(VirtIODevice *vdev, VirtQueue *vq)
 {
     VirtIOIOMMU *s = VIRTIO_IOMMU(vdev);
@@ -1003,10 +1015,17 @@ static void virtio_iommu_handle_command(VirtIODevice *vdev, VirtQueue *vq)
     struct iovec *iov;
     void *buf = NULL;
     size_t sz;
+    unsigned int batch = 0;
 
     for (;;) {
         size_t output_size = sizeof(tail);
 
+        if (++batch > virtio_queue_get_num(vdev, virtio_get_queue_index(vq))) {
+            timer_mod(s->cmd_timer,
+                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL_RT) + 1);
+            break;
+        }
+
         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
         if (!elem) {
             return;
@@ -1416,6 +1435,8 @@ static void virtio_iommu_device_realize(DeviceState *dev, Error **errp)
     s->req_vq = virtio_add_queue(vdev, VIOMMU_DEFAULT_QUEUE_SIZE,
                              virtio_iommu_handle_command);
     s->event_vq = virtio_add_queue(vdev, VIOMMU_DEFAULT_QUEUE_SIZE, NULL);
+    s->cmd_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL_RT,
+                                virtio_iommu_handle_command_timer, s);
 
     /*
      * config.bypass is needed to get initial address space early, such as
@@ -1498,6 +1519,7 @@ static void virtio_iommu_device_unrealize(DeviceState *dev)
 
     qemu_rec_mutex_destroy(&s->mutex);
 
+    timer_free(s->cmd_timer);
     virtio_delete_queue(s->req_vq);
     virtio_delete_queue(s->event_vq);
     virtio_cleanup(vdev);
@@ -1509,6 +1531,8 @@ static void virtio_iommu_device_reset_exit(Object *obj, ResetType type)
 
     trace_virtio_iommu_device_reset_exit();
 
+    timer_del(s->cmd_timer);
+
     if (s->domains) {
         g_tree_destroy(s->domains);
     }
@@ -1628,6 +1652,11 @@ static int iommu_post_load(void *opaque, int version_id)
      * still correct.
      */
     virtio_iommu_switch_address_space_all(s);
+
+    if (virtio_device_started(VIRTIO_DEVICE(s), VIRTIO_DEVICE(s)->status)) {
+        timer_mod(s->cmd_timer,
+                  qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL_RT) + 1);
+    }
     return 0;
 }
 
Signature · 1 line
MST
#26 lore raw

[PULL 28/30] hw/net/virtio-net: Protect from DMA re-entrancy bugs

In reply to Michael S. Tsirkin

From: Laurent Vivier <[email protected]>

Replace qemu_bh_new_guarded() by virtio_bh_new_guarded() so the bus and device use the same guard. Otherwise the DMA-reentrancy protection can be bypassed.

This update was missing in CVE-2024-3446 fix.

Fixes: CVE-2026-66022
Cc: [email protected]
Cc: Philippe Mathieu-Daudé <[email protected]>
Cc: [email protected]
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4073

Message-ID: <[email protected]>

---
 hw/net/virtio-net.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
hw/net/virtio-net.c +3 -2
index 7e0c984ec6..814b99a43d 100644
@@ -3009,8 +3009,9 @@ static void virtio_net_add_queue(VirtIONet *n, int index)
         n->vqs[index].tx_vq =
             virtio_add_queue(vdev, n->net_conf.tx_queue_size,
                              virtio_net_handle_tx_bh);
-        n->vqs[index].tx_bh = qemu_bh_new_guarded(virtio_net_tx_bh, &n->vqs[index],
-                                                  &DEVICE(vdev)->mem_reentrancy_guard);
+        n->vqs[index].tx_bh = virtio_bh_new_guarded(DEVICE(vdev),
+                                                    virtio_net_tx_bh,
+                                                    &n->vqs[index]);
     }
 
     n->vqs[index].tx_waiting = 0;
Signature · 1 line
MST
#27 lore raw

[PULL 15/30] libvhost-user: validate last_batch_head in vu_check_queue_inflights

In reply to Michael S. Tsirkin

vu_check_queue_inflights uses last_batch_head from the frontend-controlled inflight shared memory as an index into desc[] without bounds checking. A malicious or buggy frontend can set last_batch_head >= desc_num, causing an out-of-bounds write.

Validate last_batch_head before using it.
Note: the value is not guest-accessible so not a security vulnerability.

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3974

Fixes 5f9ff1eff3 ("libvhost-user: Support tracking inflight I/O in shared memory")
Cc Xie Yongji <[email protected]>
Cc Stefano Garzarella <[email protected]>

Message-Id: <8133770c75c9907578dd551f4d468140a0a75cd2.1784892981.git.mst@redhat.com>

---
 subprojects/libvhost-user/libvhost-user.c | 6 ++++++
 1 file changed, 6 insertions(+)
subprojects/libvhost-user/libvhost-user.c +6 -0
index 42a5e2f1f5..c1c13dbc90 100644
@@ -1379,6 +1379,12 @@ vu_check_queue_inflights(VuDev *dev, VuVirtq *vq)
     vq->counter = 0;
 
     if (unlikely(vq->inflight->used_idx != vq->used_idx)) {
+        if (vq->inflight->last_batch_head >= vq->inflight->desc_num) {
+            vu_panic(dev, "vu_check_queue_inflights: last_batch_head %u "
+                     "out of range (desc_num %u)",
+                     vq->inflight->last_batch_head, vq->inflight->desc_num);
+            return -1;
+        }
         vq->inflight->desc[vq->inflight->last_batch_head].inflight = 0;
 
         barrier();
Signature · 1 line
MST
#28 lore raw

[PULL 12/30] virtio: fail early on bad config_len in migration

In reply to Michael S. Tsirkin

virtio_load() attempts to load config_len bytes from the migration stream. If that's huge (e.g. 4g) this will uselessly spin beyond the end of the stream for seconds. Not nice. Check qemu_file_get_error() and bail out early, instead.

Also note that config_len is int32_t but is coerced to unsigned when used. Switch it to uint32_t to make this clearer.

Fixes 2f5732e964 ("Allow mismatched virtio config-len")
Cc Dr. David Alan Gilbert <[email protected]>

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3891

Message-ID: <cfbefa358af5885eb386637216552bfeba5e7bbc.1784898922.git.mst@redhat.com>

---
 hw/virtio/virtio.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
hw/virtio/virtio.c +4 -1
index 2766217ddd..979c4065b7 100644
@@ -3505,7 +3505,7 @@ int coroutine_mixed_fn
 virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
 {
     int i, ret;
-    int32_t config_len;
+    uint32_t config_len;
     uint32_t num;
     uint32_t features;
     BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
@@ -3553,6 +3553,9 @@ virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
     qemu_get_buffer(f, vdev->config, MIN(config_len, vdev->config_len));
 
     while (config_len > vdev->config_len) {
+        if (qemu_file_get_error(f)) {
+            return -1;
+        }
         qemu_get_byte(f);
         config_len--;
     }
Signature · 1 line
MST
#29 lore raw

[PULL 13/30] vhost-user: assert nregions within limit

In reply to Michael S. Tsirkin

scrub_shadow_regions() and vhost_user_add_remove_regions() use fixed-size stack arrays sized to VHOST_USER_MAX_RAM_SLOTS and index them with dev->mem->nregions.

nregions is calculated to never overrun these, but let's add an assert to make sure we don't get a stack overflow if there's a bug.

Fixes f1aeb14b08 ("Transmit vhost-user memory regions individually")

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3910

Cc Stefano Garzarella <[email protected]>
Cc Raphael Norwitz <[email protected]>

Message-ID: <48fb8411f67e525872fb19618a886e52b670ab7f.1784896199.git.mst@redhat.com>

---
 hw/virtio/vhost-user.c | 3 +++
 1 file changed, 3 insertions(+)
hw/virtio/vhost-user.c +3 -0
index 517cc4ca71..2881cec72d 100644
@@ -946,6 +946,9 @@ static int vhost_user_add_remove_regions(struct vhost_dev *dev,
 
     msg->hdr.size = sizeof(msg->payload.mem_reg);
 
+    /* Ensure nregions fits the fixed-size arrays used below. */
+    assert(dev->mem->nregions <= VHOST_USER_MAX_RAM_SLOTS);
+
     /* Find the regions which need to be removed or added. */
     scrub_shadow_regions(dev, add_reg, &nr_add_reg, rem_reg, &nr_rem_reg,
                          shadow_pcb, track_ramblocks);
Signature · 1 line
MST
#30 lore raw

[PULL 17/30] vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64

In reply to Michael S. Tsirkin

vhost user currently saves the inflight buffer to the migration stream using VMSTATE_VBUFFER_UINT64. The size is controlled by the vhost-user backend.

But the implementation of that is broken if size is >2G: it stores the buffer size in a uint64_t field, but vmstate_size() always reads the size field as int32_t regardless of the macro used. This, in turn, causes negative or truncated lengths on load, leading to undersized allocations and down the road out-of-bounds buffer access.

There's no practical reason to support such large sizes, so it's enough to validate: read the field as uint64_t when VMS_VBUFFER_UINT64 is set, reject negative or oversized values, and propagate errors to vmstate_load_vmsd().

Note: the large value is coming from the backend, not guest, so this shouldn't be considered a security issue. The CVE was assigned before the qemu security policy was updated to exclude this class of bugs.

Fixes CVE-2026-6426
Fixes f6fdd8b2bd ("vmstate: introduce VMSTATE_VBUFFER_UINT64")
Cc Alexandr Moshkov <[email protected]>

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3675

Message-ID: <801b1501ee10241f7ac49a10570d548352b36347.1784890517.git.mst@redhat.com>

---
 include/migration/vmstate.h |  5 ++++-
 migration/vmstate.c         | 31 ++++++++++++++++++++++++++++---
 2 files changed, 32 insertions(+), 4 deletions(-)
include/migration/vmstate.h +4 -1
index 1b7f295417..e7095cd977 100644
@@ -168,6 +168,9 @@ enum VMStateFlags {
      */
     VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,
 
+    /* Use a uint64_t size field for VMS_VBUFFER instead of int32_t. */
+    VMS_VBUFFER_UINT64              = 0x40000,
+
     /* Marker for end of list */
     VMS_END                         = 0x20000,
 };
@@ -788,7 +791,7 @@ extern const VMStateInfo vmstate_info_g_byte_array;
     .field_exists = (_test),                                         \
     .size_offset  = vmstate_offset_value(_state, _field_size, uint64_t),\
     .info         = &vmstate_info_buffer,                            \
-    .flags        = VMS_VBUFFER | VMS_POINTER,                       \
+    .flags        = VMS_VBUFFER | VMS_VBUFFER_UINT64 | VMS_POINTER,  \
     .offset       = offsetof(_state, _field),                        \
 }
 
migration/vmstate.c +28 -3
index 50ebe37845..d7f03a9f5b 100644
@@ -103,10 +103,24 @@ static int vmstate_size(void *opaque, const VMStateField *field)
     int size;
 
     if (field->flags & VMS_VBUFFER) {
-        size = *(int32_t *)(opaque + field->size_offset);
-        if (field->flags & VMS_MULTIPLY) {
-            size *= field->size;
+        uint64_t usize64;
+
+        if (field->flags & VMS_VBUFFER_UINT64) {
+            usize64 = *(uint64_t *)(opaque + field->size_offset);
+        } else {
+            int32_t ssize32 = *(int32_t *)(opaque + field->size_offset);
+            if (ssize32 < 0) {
+                return -1;
+            }
+            usize64 = ssize32;
         }
+        if (field->flags & VMS_MULTIPLY) {
+            usize64 *= field->size;
+        }
+        if (usize64 > INT_MAX) {
+            return -1;
+        }
+        size = usize64;
     } else if (field->flags & VMS_ARRAY_OF_POINTER) {
         /*
          * For an array of pointer, the each element is always size of a
@@ -337,6 +351,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
             void *first_elem = opaque + field->offset;
             int i, n_elems = vmstate_n_elems(opaque, field);
             int size = vmstate_size(opaque, field);
+            if (size < 0) {
+                error_setg(errp, "VMState field '%s': invalid size",
+                           field->name);
+                return false;
+            }
 
             vmstate_handle_alloc(first_elem, field, opaque);
             if (field->flags & VMS_POINTER) {
@@ -661,6 +680,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
             bool use_dynamic_array =
                 field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;
 
+            if (size < 0) {
+                error_setg(errp, "VMState field '%s': invalid size",
+                           field->name);
+                ok = false;
+                goto out;
+            }
             trace_vmstate_save_state_loop(vmsd->name, field->name, n_elems);
             if (field->flags & VMS_POINTER) {
                 first_elem = *(void **)first_elem;
Signature · 1 line
MST
#31 lore raw

Re: [PULL 17/30] vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64

This patch is migration only change and hasn't been reviewed. Give us a few days to review it?
Can't do it now because it is on cellphone and I just bathed my son.

Please hold off merging.

Thanks.

Quoted 119 lines · On Sun, Jul 26, 2026, 5:29 p.m. Michael S. Tsirkin <[email protected]> wrote:

vhost user currently saves the inflight buffer to the migration stream using VMSTATE_VBUFFER_UINT64. The size is controlled by the vhost-user backend.

But the implementation of that is broken if size is >2G: it stores the buffer size in a uint64_t field, but vmstate_size() always reads the size field as int32_t regardless of the macro used. This, in turn, causes negative or truncated lengths on load, leading to undersized allocations and down the road out-of-bounds buffer access.

There's no practical reason to support such large sizes, so it's enough to validate: read the field as uint64_t when VMS_VBUFFER_UINT64 is set, reject negative or oversized values, and propagate errors to vmstate_load_vmsd().

Note: the large value is coming from the backend, not guest, so this shouldn't be considered a security issue. The CVE was assigned before the qemu security policy was updated to exclude this class of bugs.

Fixes CVE-2026-6426
Fixes f6fdd8b2bd ("vmstate: introduce VMSTATE_VBUFFER_UINT64")
Cc Alexandr Moshkov <[email protected]>

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3675

Message-ID: < 801b1501ee10241f7ac49a10570d548352b36347.1784890517.git.mst@redhat.com>

---
 include/migration/vmstate.h |  5 ++++-
 migration/vmstate.c         | 31 ++++++++++++++++++++++++++++---
 2 files changed, 32 insertions(+), 4 deletions(-)
include/migration/vmstate.h +4 -1
index 1b7f295417..e7095cd977 100644
@@ -168,6 +168,9 @@ enum VMStateFlags {
      */
     VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,
 
+    /* Use a uint64_t size field for VMS_VBUFFER instead of int32_t. */
+    VMS_VBUFFER_UINT64              = 0x40000,
+
     /* Marker for end of list */
     VMS_END                         = 0x20000,
 };
@@ -788,7 +791,7 @@ extern const VMStateInfo vmstate_info_g_byte_array;
     .field_exists = (_test),                                         \
     .size_offset  = vmstate_offset_value(_state, _field_size, uint64_t),\
     .info         = &vmstate_info_buffer,                            \
-    .flags        = VMS_VBUFFER | VMS_POINTER,                       \
+    .flags        = VMS_VBUFFER | VMS_VBUFFER_UINT64 | VMS_POINTER,  \
     .offset       = offsetof(_state, _field),                        \
 }
 
migration/vmstate.c +17 -3
index 50ebe37845..d7f03a9f5b 100644
@@ -103,10 +103,24 @@ static int vmstate_size(void *opaque, const
 VMStateField *field)
     int size;
 
     if (field->flags & VMS_VBUFFER) {
-        size = *(int32_t *)(opaque + field->size_offset);
-        if (field->flags & VMS_MULTIPLY) {
-            size *= field->size;
+        uint64_t usize64;
+
+        if (field->flags & VMS_VBUFFER_UINT64) {
+            usize64 = *(uint64_t *)(opaque + field->size_offset);
+        } else {
+            int32_t ssize32 = *(int32_t *)(opaque + field->size_offset);
+            if (ssize32 < 0) {
+                return -1;
+            }
+            usize64 = ssize32;
         }
+        if (field->flags & VMS_MULTIPLY) {
+            usize64 *= field->size;
+        }
+        if (usize64 > INT_MAX) {
+            return -1;
+        }
+        size = usize64;
     } else if (field->flags & VMS_ARRAY_OF_POINTER) {
         /*

* For an array of pointer, the each element is always size of a

migration/vmstate.c +5 -0
@@ -337,6 +351,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const
 VMStateDescription *vmsd,
             void *first_elem = opaque + field->offset;
             int i, n_elems = vmstate_n_elems(opaque, field);
             int size = vmstate_size(opaque, field);
+            if (size < 0) {
+                error_setg(errp, "VMState field '%s': invalid size",
+                           field->name);
+                return false;
+            }
 
             vmstate_handle_alloc(first_elem, field, opaque);
             if (field->flags & VMS_POINTER) {
migration/vmstate.c +6 -0
@@ -661,6 +680,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const
 VMStateDescription *vmsd,
             bool use_dynamic_array =
                 field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;
 
+            if (size < 0) {
+                error_setg(errp, "VMState field '%s': invalid size",
+                           field->name);
+                ok = false;
+                goto out;
+            }
             trace_vmstate_save_state_loop(vmsd->name, field->name,
 n_elems);
             if (field->flags & VMS_POINTER) {
                 first_elem = *(void **)first_elem;

--
MST

#32 lore raw

Re: [PULL 17/30] vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64

Quoted 7 lines · Peter Xu

This patch is migration only change and hasn't been reviewed.  Give us a few days to review it?
Can't do it now because it is on cellphone and I just bathed my son.

Please hold off merging.

Thanks.

OK. I'll drop this and you will merge it through the migration tree as appropriate?

Quoted 121 lines · On Sun, Jul 26, 2026, 5:29 p.m. Michael S. Tsirkin <[email protected]> wrote:
    vhost user currently saves the inflight buffer to the migration stream
    using VMSTATE_VBUFFER_UINT64. The size is controlled by the vhost-user
    backend.

    But the implementation of that is broken if size is >2G: it stores the
    buffer size in a uint64_t field, but vmstate_size() always reads the
    size field as int32_t regardless of the macro used. This, in turn,
    causes negative or truncated lengths on load, leading to undersized
    allocations and down the road out-of-bounds buffer access.

    There's no practical reason to support such large sizes, so it's enough
    to validate: read the field as uint64_t when VMS_VBUFFER_UINT64 is set,
    reject negative or oversized values, and propagate errors to
    vmstate_load_vmsd().

    Note: the large value is coming from the backend, not guest, so this
    shouldn't be considered a security issue. The CVE was assigned before
    the qemu security policy was updated to exclude this class of bugs.

    Fixes: CVE-2026-6426
    Fixes: f6fdd8b2bd ("vmstate: introduce VMSTATE_VBUFFER_UINT64")
    Cc: Alexandr Moshkov <[email protected]>
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3675
    Signed-off-by: Michael S. Tsirkin <[email protected]>
    Message-ID: <
    801b1501ee10241f7ac49a10570d548352b36347.1784890517.git.mst@redhat.com>
    ---
     include/migration/vmstate.h |  5 ++++-
     migration/vmstate.c         | 31 ++++++++++++++++++++++++++++---
     2 files changed, 32 insertions(+), 4 deletions(-)

    diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
    index 1b7f295417..e7095cd977 100644
    --- a/include/migration/vmstate.h
    +++ b/include/migration/vmstate.h
    @@ -168,6 +168,9 @@ enum VMStateFlags {
          */
         VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,

    +    /* Use a uint64_t size field for VMS_VBUFFER instead of int32_t. */
    +    VMS_VBUFFER_UINT64              = 0x40000,
    +
         /* Marker for end of list */
         VMS_END                         = 0x20000,
     };
    @@ -788,7 +791,7 @@ extern const VMStateInfo vmstate_info_g_byte_array;
         .field_exists = (_test),                                         \
         .size_offset  = vmstate_offset_value(_state, _field_size, uint64_t),\
         .info         = &vmstate_info_buffer,                            \
    -    .flags        = VMS_VBUFFER | VMS_POINTER,                       \
    +    .flags        = VMS_VBUFFER | VMS_VBUFFER_UINT64 | VMS_POINTER,  \
         .offset       = offsetof(_state, _field),                        \
     }

    diff --git a/migration/vmstate.c b/migration/vmstate.c
    index 50ebe37845..d7f03a9f5b 100644
    --- a/migration/vmstate.c
    +++ b/migration/vmstate.c
    @@ -103,10 +103,24 @@ static int vmstate_size(void *opaque, const
    VMStateField *field)
         int size;

         if (field->flags & VMS_VBUFFER) {
    -        size = *(int32_t *)(opaque + field->size_offset);
    -        if (field->flags & VMS_MULTIPLY) {
    -            size *= field->size;
    +        uint64_t usize64;
    +
    +        if (field->flags & VMS_VBUFFER_UINT64) {
    +            usize64 = *(uint64_t *)(opaque + field->size_offset);
    +        } else {
    +            int32_t ssize32 = *(int32_t *)(opaque + field->size_offset);
    +            if (ssize32 < 0) {
    +                return -1;
    +            }
    +            usize64 = ssize32;
             }
    +        if (field->flags & VMS_MULTIPLY) {
    +            usize64 *= field->size;
    +        }
    +        if (usize64 > INT_MAX) {
    +            return -1;
    +        }
    +        size = usize64;
         } else if (field->flags & VMS_ARRAY_OF_POINTER) {
             /*
              * For an array of pointer, the each element is always size of a
    @@ -337,6 +351,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const
    VMStateDescription *vmsd,
                 void *first_elem = opaque + field->offset;
                 int i, n_elems = vmstate_n_elems(opaque, field);
                 int size = vmstate_size(opaque, field);
    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                return false;
    +            }

                 vmstate_handle_alloc(first_elem, field, opaque);
                 if (field->flags & VMS_POINTER) {
    @@ -661,6 +680,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const
    VMStateDescription *vmsd,
                 bool use_dynamic_array =
                     field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;

    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                ok = false;
    +                goto out;
    +            }
                 trace_vmstate_save_state_loop(vmsd->name, field->name,
    n_elems);
                 if (field->flags & VMS_POINTER) {
                     first_elem = *(void **)first_elem;
    --
    MST
#33 lore raw

Re: [PULL 17/30] vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64

Quoted 11 lines · Michael S. Tsirkin
Quoted 7 lines · Peter Xu

This patch is migration only change and hasn't been reviewed.  Give us a few days to review it?
Can't do it now because it is on cellphone and I just bathed my son.

Please hold off merging.

Thanks.

OK. I'll drop this and you will merge it through the migration tree as appropriate?

Let's drop it first so it won't block this pull. I can definitely pick it up when ready, but I want to discuss on how to fix first. Which tree to pick it up isn't a huge matter to me, but the review.

We found a bunch of randomly introduced VMSTATE flags in the past, I used to remove some, at least my plan is in the future each time we introduce a new one (by request from other modules) we better justify it before landing too easily, in case it needs a revert again.

This vhost regression started 11.0 so IIUC we don't need to rush in 11.1. I just noticed I read that patch and didn't notice this, my bad to not have noticed..

For this one specifically..

Quoted 7 lines
Quoted 7 lines · On Sun, Jul 26, 2026, 5:29 p.m. Michael S. Tsirkin <[email protected]> wrote:

vhost user currently saves the inflight buffer to the migration stream using VMSTATE_VBUFFER_UINT64. The size is controlled by the vhost-user backend.

Quoted 7 lines
Quoted 7 lines

But the implementation of that is broken if size is >2G: it stores the buffer size in a uint64_t field, but vmstate_size() always reads the size field as int32_t regardless of the macro used. This, in turn, causes negative or truncated lengths on load, leading to undersized allocations and down the road out-of-bounds buffer access.

There's no practical reason to support such large sizes, so it's enough

If there's no real demand to use 2G or more, I suggest we change size to int32_t instead on the user, and revert f6fdd8b2 VMSTATE_VBUFFER_UINT64().

Fabiano and I were looking at rest 20+ possible security related bugs in the past 1-2 weeks, I've some patches to be posted for 11.2 too when we went through all of them. One relevant patch I haven't sent but will do soon:

https://gitlab.com/peterx/qemu/-/commit/afd2cb25defdf78a4ab5279cc16634884792c57a

We encountered quite a few of possible over-allocation on destionation side by manipulating on-wire length field like this one, I believe Fabiano is looking at how to further limit that from 2G if ever possible per-user.

I'm not sure if that idea will fly, but I think anything bigger than 2G definitely is not suggested for now when it's only for a type match not real demand..

I believe VMSTATE_VBUFFER_UINT32 is problematic on its own too, I'll see how to fix that. That one is slightly easier, worst case is we bail out for 2G (hence ignore bit 31, which shouldn't be used in reality for legit users), but I'll think about it.

Thanks,

Quoted 109 lines
Quoted 108 lines
    to validate: read the field as uint64_t when VMS_VBUFFER_UINT64 is set,
    reject negative or oversized values, and propagate errors to
    vmstate_load_vmsd().

    Note: the large value is coming from the backend, not guest, so this
    shouldn't be considered a security issue. The CVE was assigned before
    the qemu security policy was updated to exclude this class of bugs.

    Fixes: CVE-2026-6426
    Fixes: f6fdd8b2bd ("vmstate: introduce VMSTATE_VBUFFER_UINT64")
    Cc: Alexandr Moshkov <[email protected]>
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3675
    Signed-off-by: Michael S. Tsirkin <[email protected]>
    Message-ID: <
    801b1501ee10241f7ac49a10570d548352b36347.1784890517.git.mst@redhat.com>
    ---
     include/migration/vmstate.h |  5 ++++-
     migration/vmstate.c         | 31 ++++++++++++++++++++++++++++---
     2 files changed, 32 insertions(+), 4 deletions(-)

    diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
    index 1b7f295417..e7095cd977 100644
    --- a/include/migration/vmstate.h
    +++ b/include/migration/vmstate.h
    @@ -168,6 +168,9 @@ enum VMStateFlags {
          */
         VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,

    +    /* Use a uint64_t size field for VMS_VBUFFER instead of int32_t. */
    +    VMS_VBUFFER_UINT64              = 0x40000,
    +
         /* Marker for end of list */
         VMS_END                         = 0x20000,
     };
    @@ -788,7 +791,7 @@ extern const VMStateInfo vmstate_info_g_byte_array;
         .field_exists = (_test),                                         \
         .size_offset  = vmstate_offset_value(_state, _field_size, uint64_t),\
         .info         = &vmstate_info_buffer,                            \
    -    .flags        = VMS_VBUFFER | VMS_POINTER,                       \
    +    .flags        = VMS_VBUFFER | VMS_VBUFFER_UINT64 | VMS_POINTER,  \
         .offset       = offsetof(_state, _field),                        \
     }

    diff --git a/migration/vmstate.c b/migration/vmstate.c
    index 50ebe37845..d7f03a9f5b 100644
    --- a/migration/vmstate.c
    +++ b/migration/vmstate.c
    @@ -103,10 +103,24 @@ static int vmstate_size(void *opaque, const
    VMStateField *field)
         int size;

         if (field->flags & VMS_VBUFFER) {
    -        size = *(int32_t *)(opaque + field->size_offset);
    -        if (field->flags & VMS_MULTIPLY) {
    -            size *= field->size;
    +        uint64_t usize64;
    +
    +        if (field->flags & VMS_VBUFFER_UINT64) {
    +            usize64 = *(uint64_t *)(opaque + field->size_offset);
    +        } else {
    +            int32_t ssize32 = *(int32_t *)(opaque + field->size_offset);
    +            if (ssize32 < 0) {
    +                return -1;
    +            }
    +            usize64 = ssize32;
             }
    +        if (field->flags & VMS_MULTIPLY) {
    +            usize64 *= field->size;
    +        }
    +        if (usize64 > INT_MAX) {
    +            return -1;
    +        }
    +        size = usize64;
         } else if (field->flags & VMS_ARRAY_OF_POINTER) {
             /*
              * For an array of pointer, the each element is always size of a
    @@ -337,6 +351,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const
    VMStateDescription *vmsd,
                 void *first_elem = opaque + field->offset;
                 int i, n_elems = vmstate_n_elems(opaque, field);
                 int size = vmstate_size(opaque, field);
    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                return false;
    +            }

                 vmstate_handle_alloc(first_elem, field, opaque);
                 if (field->flags & VMS_POINTER) {
    @@ -661,6 +680,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const
    VMStateDescription *vmsd,
                 bool use_dynamic_array =
                     field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;

    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                ok = false;
    +                goto out;
    +            }
                 trace_vmstate_save_state_loop(vmsd->name, field->name,
    n_elems);
                 if (field->flags & VMS_POINTER) {
                     first_elem = *(void **)first_elem;
    --
    MST
Signature · 1 line
Peter Xu
#34 lore raw

Re: [PULL 17/30] vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64

Quoted 8 lines · Peter Xu
Quoted 7 lines · Michael S. Tsirkin
Quoted 6 lines · Peter Xu

This patch is migration only change and hasn't been reviewed.  Give us a few days to review it?
Can't do it now because it is on cellphone and I just bathed my son.

Please hold off merging.

Quoted 8 lines
Quoted 4 lines
Quoted 1 line

Thanks.

OK. I'll drop this and you will merge it through the migration tree as appropriate?

Let's drop it first so it won't block this pull. I can definitely pick it up when ready, but I want to discuss on how to fix first. Which tree to pick it up isn't a huge matter to me, but the review.

Done.
Pls include:

when you are merging this.

Quoted 163 lines

We found a bunch of randomly introduced VMSTATE flags in the past, I used to remove some, at least my plan is in the future each time we introduce a new one (by request from other modules) we better justify it before landing too easily, in case it needs a revert again.

This vhost regression started 11.0 so IIUC we don't need to rush in 11.1. I just noticed I read that patch and didn't notice this, my bad to not have noticed..

For this one specifically..

Quoted 7 lines
Quoted 7 lines · On Sun, Jul 26, 2026, 5:29 p.m. Michael S. Tsirkin <[email protected]> wrote:

vhost user currently saves the inflight buffer to the migration stream using VMSTATE_VBUFFER_UINT64. The size is controlled by the vhost-user backend.

Quoted 7 lines
Quoted 7 lines

But the implementation of that is broken if size is >2G: it stores the buffer size in a uint64_t field, but vmstate_size() always reads the size field as int32_t regardless of the macro used. This, in turn, causes negative or truncated lengths on load, leading to undersized allocations and down the road out-of-bounds buffer access.

There's no practical reason to support such large sizes, so it's enough

If there's no real demand to use 2G or more, I suggest we change size to int32_t instead on the user, and revert f6fdd8b2 VMSTATE_VBUFFER_UINT64().

Fabiano and I were looking at rest 20+ possible security related bugs in the past 1-2 weeks, I've some patches to be posted for 11.2 too when we went through all of them. One relevant patch I haven't sent but will do soon:

https://gitlab.com/peterx/qemu/-/commit/afd2cb25defdf78a4ab5279cc16634884792c57a

We encountered quite a few of possible over-allocation on destionation side by manipulating on-wire length field like this one, I believe Fabiano is looking at how to further limit that from 2G if ever possible per-user.

I'm not sure if that idea will fly, but I think anything bigger than 2G definitely is not suggested for now when it's only for a type match not real demand..

I believe VMSTATE_VBUFFER_UINT32 is problematic on its own too, I'll see how to fix that. That one is slightly easier, worst case is we bail out for 2G (hence ignore bit 31, which shouldn't be used in reality for legit users), but I'll think about it.

Thanks,

Quoted 109 lines
Quoted 108 lines
    to validate: read the field as uint64_t when VMS_VBUFFER_UINT64 is set,
    reject negative or oversized values, and propagate errors to
    vmstate_load_vmsd().

    Note: the large value is coming from the backend, not guest, so this
    shouldn't be considered a security issue. The CVE was assigned before
    the qemu security policy was updated to exclude this class of bugs.

    Fixes: CVE-2026-6426
    Fixes: f6fdd8b2bd ("vmstate: introduce VMSTATE_VBUFFER_UINT64")
    Cc: Alexandr Moshkov <[email protected]>
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3675
    Signed-off-by: Michael S. Tsirkin <[email protected]>
    Message-ID: <
    801b1501ee10241f7ac49a10570d548352b36347.1784890517.git.mst@redhat.com>
    ---
     include/migration/vmstate.h |  5 ++++-
     migration/vmstate.c         | 31 ++++++++++++++++++++++++++++---
     2 files changed, 32 insertions(+), 4 deletions(-)

    diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
    index 1b7f295417..e7095cd977 100644
    --- a/include/migration/vmstate.h
    +++ b/include/migration/vmstate.h
    @@ -168,6 +168,9 @@ enum VMStateFlags {
          */
         VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,

    +    /* Use a uint64_t size field for VMS_VBUFFER instead of int32_t. */
    +    VMS_VBUFFER_UINT64              = 0x40000,
    +
         /* Marker for end of list */
         VMS_END                         = 0x20000,
     };
    @@ -788,7 +791,7 @@ extern const VMStateInfo vmstate_info_g_byte_array;
         .field_exists = (_test),                                         \
         .size_offset  = vmstate_offset_value(_state, _field_size, uint64_t),\
         .info         = &vmstate_info_buffer,                            \
    -    .flags        = VMS_VBUFFER | VMS_POINTER,                       \
    +    .flags        = VMS_VBUFFER | VMS_VBUFFER_UINT64 | VMS_POINTER,  \
         .offset       = offsetof(_state, _field),                        \
     }

    diff --git a/migration/vmstate.c b/migration/vmstate.c
    index 50ebe37845..d7f03a9f5b 100644
    --- a/migration/vmstate.c
    +++ b/migration/vmstate.c
    @@ -103,10 +103,24 @@ static int vmstate_size(void *opaque, const
    VMStateField *field)
         int size;

         if (field->flags & VMS_VBUFFER) {
    -        size = *(int32_t *)(opaque + field->size_offset);
    -        if (field->flags & VMS_MULTIPLY) {
    -            size *= field->size;
    +        uint64_t usize64;
    +
    +        if (field->flags & VMS_VBUFFER_UINT64) {
    +            usize64 = *(uint64_t *)(opaque + field->size_offset);
    +        } else {
    +            int32_t ssize32 = *(int32_t *)(opaque + field->size_offset);
    +            if (ssize32 < 0) {
    +                return -1;
    +            }
    +            usize64 = ssize32;
             }
    +        if (field->flags & VMS_MULTIPLY) {
    +            usize64 *= field->size;
    +        }
    +        if (usize64 > INT_MAX) {
    +            return -1;
    +        }
    +        size = usize64;
         } else if (field->flags & VMS_ARRAY_OF_POINTER) {
             /*
              * For an array of pointer, the each element is always size of a
    @@ -337,6 +351,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const
    VMStateDescription *vmsd,
                 void *first_elem = opaque + field->offset;
                 int i, n_elems = vmstate_n_elems(opaque, field);
                 int size = vmstate_size(opaque, field);
    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                return false;
    +            }

                 vmstate_handle_alloc(first_elem, field, opaque);
                 if (field->flags & VMS_POINTER) {
    @@ -661,6 +680,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const
    VMStateDescription *vmsd,
                 bool use_dynamic_array =
                     field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;

    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                ok = false;
    +                goto out;
    +            }
                 trace_vmstate_save_state_loop(vmsd->name, field->name,
    n_elems);
                 if (field->flags & VMS_POINTER) {
                     first_elem = *(void **)first_elem;
    --
    MST
Signature · 1 line
Peter Xu
#35 lore raw

Re: [PULL 17/30] vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64

Quoted 18 lines · Michael S. Tsirkin
Quoted 8 lines · Peter Xu
Quoted 7 lines · Michael S. Tsirkin
Quoted 6 lines · Peter Xu

This patch is migration only change and hasn't been reviewed.  Give us a few days to review it?
Can't do it now because it is on cellphone and I just bathed my son.

Please hold off merging.

Quoted 8 lines
Quoted 4 lines
Quoted 1 line

Thanks.

OK. I'll drop this and you will merge it through the migration tree as appropriate?

Let's drop it first so it won't block this pull. I can definitely pick it up when ready, but I want to discuss on how to fix first. Which tree to pick it up isn't a huge matter to me, but the review.

Quoted 6 lines

Done.
Pls include:

when you are merging this.

Michael,

Please continue read at least until [1] below. I want to discuss if we should fix it in another way..

Thanks,

Quoted 25 lines
Quoted 25 lines

We found a bunch of randomly introduced VMSTATE flags in the past, I used to remove some, at least my plan is in the future each time we introduce a new one (by request from other modules) we better justify it before landing too easily, in case it needs a revert again.

This vhost regression started 11.0 so IIUC we don't need to rush in 11.1. I just noticed I read that patch and didn't notice this, my bad to not have noticed..

For this one specifically..

Quoted 13 lines
Quoted 13 lines · On Sun, Jul 26, 2026, 5:29 p.m. Michael S. Tsirkin <[email protected]> wrote:

vhost user currently saves the inflight buffer to the migration stream using VMSTATE_VBUFFER_UINT64. The size is controlled by the vhost-user backend.

But the implementation of that is broken if size is >2G: it stores the buffer size in a uint64_t field, but vmstate_size() always reads the size field as int32_t regardless of the macro used. This, in turn, causes negative or truncated lengths on load, leading to undersized allocations and down the road out-of-bounds buffer access.

Quoted 4 lines
Quoted 4 lines
Quoted 1 line
Quoted 1 line

There's no practical reason to support such large sizes, so it's enough

If there's no real demand to use 2G or more, I suggest we change size to int32_t instead on the user, and revert f6fdd8b2 VMSTATE_VBUFFER_UINT64().

[1]

Quoted 136 lines
Quoted 135 lines

Fabiano and I were looking at rest 20+ possible security related bugs in the past 1-2 weeks, I've some patches to be posted for 11.2 too when we went through all of them. One relevant patch I haven't sent but will do soon:

https://gitlab.com/peterx/qemu/-/commit/afd2cb25defdf78a4ab5279cc16634884792c57a

We encountered quite a few of possible over-allocation on destionation side by manipulating on-wire length field like this one, I believe Fabiano is looking at how to further limit that from 2G if ever possible per-user.

I'm not sure if that idea will fly, but I think anything bigger than 2G definitely is not suggested for now when it's only for a type match not real demand..

I believe VMSTATE_VBUFFER_UINT32 is problematic on its own too, I'll see how to fix that. That one is slightly easier, worst case is we bail out for 2G (hence ignore bit 31, which shouldn't be used in reality for legit users), but I'll think about it.

Thanks,

Quoted 109 lines
Quoted 108 lines
    to validate: read the field as uint64_t when VMS_VBUFFER_UINT64 is set,
    reject negative or oversized values, and propagate errors to
    vmstate_load_vmsd().

    Note: the large value is coming from the backend, not guest, so this
    shouldn't be considered a security issue. The CVE was assigned before
    the qemu security policy was updated to exclude this class of bugs.

    Fixes: CVE-2026-6426
    Fixes: f6fdd8b2bd ("vmstate: introduce VMSTATE_VBUFFER_UINT64")
    Cc: Alexandr Moshkov <[email protected]>
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3675
    Signed-off-by: Michael S. Tsirkin <[email protected]>
    Message-ID: <
    801b1501ee10241f7ac49a10570d548352b36347.1784890517.git.mst@redhat.com>
    ---
     include/migration/vmstate.h |  5 ++++-
     migration/vmstate.c         | 31 ++++++++++++++++++++++++++++---
     2 files changed, 32 insertions(+), 4 deletions(-)

    diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
    index 1b7f295417..e7095cd977 100644
    --- a/include/migration/vmstate.h
    +++ b/include/migration/vmstate.h
    @@ -168,6 +168,9 @@ enum VMStateFlags {
          */
         VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,

    +    /* Use a uint64_t size field for VMS_VBUFFER instead of int32_t. */
    +    VMS_VBUFFER_UINT64              = 0x40000,
    +
         /* Marker for end of list */
         VMS_END                         = 0x20000,
     };
    @@ -788,7 +791,7 @@ extern const VMStateInfo vmstate_info_g_byte_array;
         .field_exists = (_test),                                         \
         .size_offset  = vmstate_offset_value(_state, _field_size, uint64_t),\
         .info         = &vmstate_info_buffer,                            \
    -    .flags        = VMS_VBUFFER | VMS_POINTER,                       \
    +    .flags        = VMS_VBUFFER | VMS_VBUFFER_UINT64 | VMS_POINTER,  \
         .offset       = offsetof(_state, _field),                        \
     }

    diff --git a/migration/vmstate.c b/migration/vmstate.c
    index 50ebe37845..d7f03a9f5b 100644
    --- a/migration/vmstate.c
    +++ b/migration/vmstate.c
    @@ -103,10 +103,24 @@ static int vmstate_size(void *opaque, const
    VMStateField *field)
         int size;

         if (field->flags & VMS_VBUFFER) {
    -        size = *(int32_t *)(opaque + field->size_offset);
    -        if (field->flags & VMS_MULTIPLY) {
    -            size *= field->size;
    +        uint64_t usize64;
    +
    +        if (field->flags & VMS_VBUFFER_UINT64) {
    +            usize64 = *(uint64_t *)(opaque + field->size_offset);
    +        } else {
    +            int32_t ssize32 = *(int32_t *)(opaque + field->size_offset);
    +            if (ssize32 < 0) {
    +                return -1;
    +            }
    +            usize64 = ssize32;
             }
    +        if (field->flags & VMS_MULTIPLY) {
    +            usize64 *= field->size;
    +        }
    +        if (usize64 > INT_MAX) {
    +            return -1;
    +        }
    +        size = usize64;
         } else if (field->flags & VMS_ARRAY_OF_POINTER) {
             /*
              * For an array of pointer, the each element is always size of a
    @@ -337,6 +351,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const
    VMStateDescription *vmsd,
                 void *first_elem = opaque + field->offset;
                 int i, n_elems = vmstate_n_elems(opaque, field);
                 int size = vmstate_size(opaque, field);
    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                return false;
    +            }

                 vmstate_handle_alloc(first_elem, field, opaque);
                 if (field->flags & VMS_POINTER) {
    @@ -661,6 +680,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const
    VMStateDescription *vmsd,
                 bool use_dynamic_array =
                     field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;

    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                ok = false;
    +                goto out;
    +            }
                 trace_vmstate_save_state_loop(vmsd->name, field->name,
    n_elems);
                 if (field->flags & VMS_POINTER) {
                     first_elem = *(void **)first_elem;
    --
    MST
Signature · 1 line
Peter Xu
Signature · 1 line
Peter Xu
#36 lore raw

Re: [PULL 17/30] vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64

In reply to Peter Xu
Quoted 41 lines · Peter Xu
Quoted 11 lines · Michael S. Tsirkin
Quoted 7 lines · Peter Xu

This patch is migration only change and hasn't been reviewed.  Give us a few days to review it?
Can't do it now because it is on cellphone and I just bathed my son.

Please hold off merging.

Thanks.

OK. I'll drop this and you will merge it through the migration tree as appropriate?

Let's drop it first so it won't block this pull. I can definitely pick it up when ready, but I want to discuss on how to fix first. Which tree to pick it up isn't a huge matter to me, but the review.

We found a bunch of randomly introduced VMSTATE flags in the past, I used to remove some, at least my plan is in the future each time we introduce a new one (by request from other modules) we better justify it before landing too easily, in case it needs a revert again.

This vhost regression started 11.0 so IIUC we don't need to rush in 11.1. I just noticed I read that patch and didn't notice this, my bad to not have noticed..

For this one specifically..

Quoted 13 lines
Quoted 13 lines · On Sun, Jul 26, 2026, 5:29 p.m. Michael S. Tsirkin <[email protected]> wrote:

vhost user currently saves the inflight buffer to the migration stream using VMSTATE_VBUFFER_UINT64. The size is controlled by the vhost-user backend.

But the implementation of that is broken if size is >2G: it stores the buffer size in a uint64_t field, but vmstate_size() always reads the size field as int32_t regardless of the macro used. This, in turn, causes negative or truncated lengths on load, leading to undersized allocations and down the road out-of-bounds buffer access.

Quoted 4 lines
Quoted 1 line
Quoted 1 line

There's no practical reason to support such large sizes, so it's enough

If there's no real demand to use 2G or more, I suggest we change size to int32_t instead on the user, and revert f6fdd8b2 VMSTATE_VBUFFER_UINT64().

We can't just change it we'll need some tricks to avoid breaking existing migration format. I don't see how it will be much simpler if you take that into account.

Quoted 7 lines

Fabiano and I were looking at rest 20+ possible security related bugs in the past 1-2 weeks, I've some patches to be posted for 11.2 too when we went through all of them. One relevant patch I haven't sent but will do soon:

https://gitlab.com/peterx/qemu/-/commit/afd2cb25defdf78a4ab5279cc16634884792c57a

Quoted 7 lines

We encountered quite a few of possible over-allocation on destionation side by manipulating on-wire length field like this one, I believe Fabiano is looking at how to further limit that from 2G if ever possible per-user.

I'm not sure if that idea will fly, but I think anything bigger than 2G definitely is not suggested for now when it's only for a type match not real demand..

Frankly 2G is crazy too. I see little reason to focus specifically on >2G.

Quoted 6 lines

I believe VMSTATE_VBUFFER_UINT32 is problematic on its own too, I'll see how to fix that. That one is slightly easier, worst case is we bail out for 2G (hence ignore bit 31, which shouldn't be used in reality for legit users), but I'll think about it.

Thanks,

I just do not see (almost) any case where migration destination does not know the actual size needed, or at least
an upper bound on such.

Quoted 112 lines
Quoted 109 lines
Quoted 108 lines
    to validate: read the field as uint64_t when VMS_VBUFFER_UINT64 is set,
    reject negative or oversized values, and propagate errors to
    vmstate_load_vmsd().

    Note: the large value is coming from the backend, not guest, so this
    shouldn't be considered a security issue. The CVE was assigned before
    the qemu security policy was updated to exclude this class of bugs.

    Fixes: CVE-2026-6426
    Fixes: f6fdd8b2bd ("vmstate: introduce VMSTATE_VBUFFER_UINT64")
    Cc: Alexandr Moshkov <[email protected]>
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3675
    Signed-off-by: Michael S. Tsirkin <[email protected]>
    Message-ID: <
    801b1501ee10241f7ac49a10570d548352b36347.1784890517.git.mst@redhat.com>
    ---
     include/migration/vmstate.h |  5 ++++-
     migration/vmstate.c         | 31 ++++++++++++++++++++++++++++---
     2 files changed, 32 insertions(+), 4 deletions(-)

    diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
    index 1b7f295417..e7095cd977 100644
    --- a/include/migration/vmstate.h
    +++ b/include/migration/vmstate.h
    @@ -168,6 +168,9 @@ enum VMStateFlags {
          */
         VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,

    +    /* Use a uint64_t size field for VMS_VBUFFER instead of int32_t. */
    +    VMS_VBUFFER_UINT64              = 0x40000,
    +
         /* Marker for end of list */
         VMS_END                         = 0x20000,
     };
    @@ -788,7 +791,7 @@ extern const VMStateInfo vmstate_info_g_byte_array;
         .field_exists = (_test),                                         \
         .size_offset  = vmstate_offset_value(_state, _field_size, uint64_t),\
         .info         = &vmstate_info_buffer,                            \
    -    .flags        = VMS_VBUFFER | VMS_POINTER,                       \
    +    .flags        = VMS_VBUFFER | VMS_VBUFFER_UINT64 | VMS_POINTER,  \
         .offset       = offsetof(_state, _field),                        \
     }

    diff --git a/migration/vmstate.c b/migration/vmstate.c
    index 50ebe37845..d7f03a9f5b 100644
    --- a/migration/vmstate.c
    +++ b/migration/vmstate.c
    @@ -103,10 +103,24 @@ static int vmstate_size(void *opaque, const
    VMStateField *field)
         int size;

         if (field->flags & VMS_VBUFFER) {
    -        size = *(int32_t *)(opaque + field->size_offset);
    -        if (field->flags & VMS_MULTIPLY) {
    -            size *= field->size;
    +        uint64_t usize64;
    +
    +        if (field->flags & VMS_VBUFFER_UINT64) {
    +            usize64 = *(uint64_t *)(opaque + field->size_offset);
    +        } else {
    +            int32_t ssize32 = *(int32_t *)(opaque + field->size_offset);
    +            if (ssize32 < 0) {
    +                return -1;
    +            }
    +            usize64 = ssize32;
             }
    +        if (field->flags & VMS_MULTIPLY) {
    +            usize64 *= field->size;
    +        }
    +        if (usize64 > INT_MAX) {
    +            return -1;
    +        }
    +        size = usize64;
         } else if (field->flags & VMS_ARRAY_OF_POINTER) {
             /*
              * For an array of pointer, the each element is always size of a
    @@ -337,6 +351,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const
    VMStateDescription *vmsd,
                 void *first_elem = opaque + field->offset;
                 int i, n_elems = vmstate_n_elems(opaque, field);
                 int size = vmstate_size(opaque, field);
    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                return false;
    +            }

                 vmstate_handle_alloc(first_elem, field, opaque);
                 if (field->flags & VMS_POINTER) {
    @@ -661,6 +680,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const
    VMStateDescription *vmsd,
                 bool use_dynamic_array =
                     field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;

    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                ok = false;
    +                goto out;
    +            }
                 trace_vmstate_save_state_loop(vmsd->name, field->name,
    n_elems);
                 if (field->flags & VMS_POINTER) {
                     first_elem = *(void **)first_elem;
    --
    MST
Signature · 1 line
Peter Xu
#37 lore raw

Re: [PULL 17/30] vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64

Quoted 42 lines · Michael S. Tsirkin
Quoted 41 lines · Peter Xu
Quoted 11 lines · Michael S. Tsirkin
Quoted 7 lines · Peter Xu

This patch is migration only change and hasn't been reviewed.  Give us a few days to review it?
Can't do it now because it is on cellphone and I just bathed my son.

Please hold off merging.

Thanks.

OK. I'll drop this and you will merge it through the migration tree as appropriate?

Let's drop it first so it won't block this pull. I can definitely pick it up when ready, but I want to discuss on how to fix first. Which tree to pick it up isn't a huge matter to me, but the review.

We found a bunch of randomly introduced VMSTATE flags in the past, I used to remove some, at least my plan is in the future each time we introduce a new one (by request from other modules) we better justify it before landing too easily, in case it needs a revert again.

This vhost regression started 11.0 so IIUC we don't need to rush in 11.1. I just noticed I read that patch and didn't notice this, my bad to not have noticed..

For this one specifically..

Quoted 13 lines
Quoted 13 lines · On Sun, Jul 26, 2026, 5:29 p.m. Michael S. Tsirkin <[email protected]> wrote:

vhost user currently saves the inflight buffer to the migration stream using VMSTATE_VBUFFER_UINT64. The size is controlled by the vhost-user backend.

But the implementation of that is broken if size is >2G: it stores the buffer size in a uint64_t field, but vmstate_size() always reads the size field as int32_t regardless of the macro used. This, in turn, causes negative or truncated lengths on load, leading to undersized allocations and down the road out-of-bounds buffer access.

Quoted 8 lines
Quoted 4 lines
Quoted 1 line
Quoted 1 line

There's no practical reason to support such large sizes, so it's enough

If there's no real demand to use 2G or more, I suggest we change size to int32_t instead on the user, and revert f6fdd8b2 VMSTATE_VBUFFER_UINT64().

We can't just change it we'll need some tricks to avoid breaking existing migration format. I don't see how it will be much simpler if you take that into account.

It's not part of the stream, right?

Here IIUC the VMSD field "size" only hard-coded the binary offset to fetch the int32_t from the object* pointer. IIUC we can simply change it to int32_t and IIUC it will be able to recv stream from src with uint64_t. The stream shouldn't be affected when e.g. we migrate from an older QEMU.

Quoted 12 lines
Quoted 12 lines

Fabiano and I were looking at rest 20+ possible security related bugs in the past 1-2 weeks, I've some patches to be posted for 11.2 too when we went through all of them. One relevant patch I haven't sent but will do soon:

https://gitlab.com/peterx/qemu/-/commit/afd2cb25defdf78a4ab5279cc16634884792c57a

We encountered quite a few of possible over-allocation on destionation side by manipulating on-wire length field like this one, I believe Fabiano is looking at how to further limit that from 2G if ever possible per-user.

Quoted 5 lines
Quoted 3 lines

I'm not sure if that idea will fly, but I think anything bigger than 2G definitely is not suggested for now when it's only for a type match not real demand..

Frankly 2G is crazy too. I see little reason to focus specifically on >2G.

Yeah..

Quoted 7 lines
Quoted 7 lines

I believe VMSTATE_VBUFFER_UINT32 is problematic on its own too, I'll see how to fix that. That one is slightly easier, worst case is we bail out for 2G (hence ignore bit 31, which shouldn't be used in reality for legit users), but I'll think about it.

Quoted 5 lines
Quoted 1 line

Thanks,

I just do not see (almost) any case where migration destination does not know the actual size needed, or at least
an upper bound on such.

In many cases we transfer the length first then the array following that with the length. That's a common trick we played like vhost here.

The upper bound is indeed a challenge, I confess I don't know an answer, and we will need to be careful on adding anything that might break a legit user. I believe Fabiano might have some better clue there, so I'd leave it to him until I can read his patches.

Thanks,

Quoted 115 lines
Quoted 114 lines
Quoted 111 lines
Quoted 110 lines

    to validate: read the field as uint64_t when VMS_VBUFFER_UINT64 is set,
    reject negative or oversized values, and propagate errors to
    vmstate_load_vmsd().

    Note: the large value is coming from the backend, not guest, so this
    shouldn't be considered a security issue. The CVE was assigned before
    the qemu security policy was updated to exclude this class of bugs.

    Fixes: CVE-2026-6426
    Fixes: f6fdd8b2bd ("vmstate: introduce VMSTATE_VBUFFER_UINT64")
    Cc: Alexandr Moshkov <[email protected]>
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3675
    Signed-off-by: Michael S. Tsirkin <[email protected]>
    Message-ID: <
    801b1501ee10241f7ac49a10570d548352b36347.1784890517.git.mst@redhat.com>
    ---
     include/migration/vmstate.h |  5 ++++-
     migration/vmstate.c         | 31 ++++++++++++++++++++++++++++---
     2 files changed, 32 insertions(+), 4 deletions(-)

    diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
    index 1b7f295417..e7095cd977 100644
    --- a/include/migration/vmstate.h
    +++ b/include/migration/vmstate.h
    @@ -168,6 +168,9 @@ enum VMStateFlags {
          */
         VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,

    +    /* Use a uint64_t size field for VMS_VBUFFER instead of int32_t. */
    +    VMS_VBUFFER_UINT64              = 0x40000,
    +
         /* Marker for end of list */
         VMS_END                         = 0x20000,
     };
    @@ -788,7 +791,7 @@ extern const VMStateInfo vmstate_info_g_byte_array;
         .field_exists = (_test),                                         \
         .size_offset  = vmstate_offset_value(_state, _field_size, uint64_t),\
         .info         = &vmstate_info_buffer,                            \
    -    .flags        = VMS_VBUFFER | VMS_POINTER,                       \
    +    .flags        = VMS_VBUFFER | VMS_VBUFFER_UINT64 | VMS_POINTER,  \
         .offset       = offsetof(_state, _field),                        \
     }

    diff --git a/migration/vmstate.c b/migration/vmstate.c
    index 50ebe37845..d7f03a9f5b 100644
    --- a/migration/vmstate.c
    +++ b/migration/vmstate.c
    @@ -103,10 +103,24 @@ static int vmstate_size(void *opaque, const
    VMStateField *field)
         int size;

         if (field->flags & VMS_VBUFFER) {
    -        size = *(int32_t *)(opaque + field->size_offset);
    -        if (field->flags & VMS_MULTIPLY) {
    -            size *= field->size;
    +        uint64_t usize64;
    +
    +        if (field->flags & VMS_VBUFFER_UINT64) {
    +            usize64 = *(uint64_t *)(opaque + field->size_offset);
    +        } else {
    +            int32_t ssize32 = *(int32_t *)(opaque + field->size_offset);
    +            if (ssize32 < 0) {
    +                return -1;
    +            }
    +            usize64 = ssize32;
             }
    +        if (field->flags & VMS_MULTIPLY) {
    +            usize64 *= field->size;
    +        }
    +        if (usize64 > INT_MAX) {
    +            return -1;
    +        }
    +        size = usize64;
         } else if (field->flags & VMS_ARRAY_OF_POINTER) {
             /*
              * For an array of pointer, the each element is always size of a
    @@ -337,6 +351,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const
    VMStateDescription *vmsd,
                 void *first_elem = opaque + field->offset;
                 int i, n_elems = vmstate_n_elems(opaque, field);
                 int size = vmstate_size(opaque, field);
    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                return false;
    +            }

                 vmstate_handle_alloc(first_elem, field, opaque);
                 if (field->flags & VMS_POINTER) {
    @@ -661,6 +680,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const
    VMStateDescription *vmsd,
                 bool use_dynamic_array =
                     field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;

    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                ok = false;
    +                goto out;
    +            }
                 trace_vmstate_save_state_loop(vmsd->name, field->name,
    n_elems);
                 if (field->flags & VMS_POINTER) {
                     first_elem = *(void **)first_elem;
    --
    MST
Signature · 1 line
Peter Xu
Signature · 1 line
Peter Xu
#38 lore raw

Re: [PULL 17/30] vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64

Quoted 45 lines · Peter Xu
Quoted 44 lines · Michael S. Tsirkin
Quoted 43 lines · Peter Xu
Quoted 11 lines · Michael S. Tsirkin
Quoted 7 lines · Peter Xu

This patch is migration only change and hasn't been reviewed.  Give us a few days to review it?
Can't do it now because it is on cellphone and I just bathed my son.

Please hold off merging.

Thanks.

OK. I'll drop this and you will merge it through the migration tree as appropriate?

Let's drop it first so it won't block this pull. I can definitely pick it up when ready, but I want to discuss on how to fix first. Which tree to pick it up isn't a huge matter to me, but the review.

We found a bunch of randomly introduced VMSTATE flags in the past, I used to remove some, at least my plan is in the future each time we introduce a new one (by request from other modules) we better justify it before landing too easily, in case it needs a revert again.

This vhost regression started 11.0 so IIUC we don't need to rush in 11.1. I just noticed I read that patch and didn't notice this, my bad to not have noticed..

For this one specifically..

Quoted 14 lines
Quoted 14 lines · On Sun, Jul 26, 2026, 5:29 p.m. Michael S. Tsirkin <[email protected]> wrote:

vhost user currently saves the inflight buffer to the migration stream using VMSTATE_VBUFFER_UINT64. The size is controlled by the vhost-user backend.

But the implementation of that is broken if size is >2G: it stores the buffer size in a uint64_t field, but vmstate_size() always reads the size field as int32_t regardless of the macro used. This, in turn, causes negative or truncated lengths on load, leading to undersized allocations and down the road out-of-bounds buffer access.

There's no practical reason to support such large sizes, so it's enough

Quoted 8 lines
Quoted 6 lines
Quoted 2 lines

If there's no real demand to use 2G or more, I suggest we change size to int32_t instead on the user, and revert f6fdd8b2 VMSTATE_VBUFFER_UINT64().

We can't just change it we'll need some tricks to avoid breaking existing migration format. I don't see how it will be much simpler if you take that into account.

It's not part of the stream, right?

I think it is?

VMSTATE_UINT64(size, struct vhost_inflight) at hw/virtio/vhost.c eventually leads to qemu_put_be64s / qemu_get_be64s in migration/vmstate-types.c

Quoted 4 lines

Here IIUC the VMSD field "size" only hard-coded the binary offset to fetch the int32_t from the object* pointer. IIUC we can simply change it to int32_t and IIUC it will be able to recv stream from src with uint64_t. The stream shouldn't be affected when e.g. we migrate from an older QEMU.

I don't really know what you mean. Yes vmsd macros tie migration stream and the storage format. it kinda makes it easy to add one but the cost is issues like this.

Quoted 27 lines
Quoted 12 lines
Quoted 12 lines

Fabiano and I were looking at rest 20+ possible security related bugs in the past 1-2 weeks, I've some patches to be posted for 11.2 too when we went through all of them. One relevant patch I haven't sent but will do soon:

https://gitlab.com/peterx/qemu/-/commit/afd2cb25defdf78a4ab5279cc16634884792c57a

We encountered quite a few of possible over-allocation on destionation side by manipulating on-wire length field like this one, I believe Fabiano is looking at how to further limit that from 2G if ever possible per-user.

Quoted 5 lines
Quoted 3 lines

I'm not sure if that idea will fly, but I think anything bigger than 2G definitely is not suggested for now when it's only for a type match not real demand..

Frankly 2G is crazy too. I see little reason to focus specifically on >2G.

Yeah..

Quoted 7 lines
Quoted 7 lines

I believe VMSTATE_VBUFFER_UINT32 is problematic on its own too, I'll see how to fix that. That one is slightly easier, worst case is we bail out for 2G (hence ignore bit 31, which shouldn't be used in reality for legit users), but I'll think about it.

Quoted 8 lines
Quoted 5 lines
Quoted 1 line

Thanks,

I just do not see (almost) any case where migration destination does not know the actual size needed, or at least
an upper bound on such.

In many cases we transfer the length first then the array following that with the length. That's a common trick we played like vhost here.

we need to start specifying the cap for these.

Quoted 125 lines

The upper bound is indeed a challenge, I confess I don't know an answer, and we will need to be careful on adding anything that might break a legit user. I believe Fabiano might have some better clue there, so I'd leave it to him until I can read his patches.

Thanks,

Quoted 115 lines
Quoted 114 lines
Quoted 111 lines
Quoted 110 lines

    to validate: read the field as uint64_t when VMS_VBUFFER_UINT64 is set,
    reject negative or oversized values, and propagate errors to
    vmstate_load_vmsd().

    Note: the large value is coming from the backend, not guest, so this
    shouldn't be considered a security issue. The CVE was assigned before
    the qemu security policy was updated to exclude this class of bugs.

    Fixes: CVE-2026-6426
    Fixes: f6fdd8b2bd ("vmstate: introduce VMSTATE_VBUFFER_UINT64")
    Cc: Alexandr Moshkov <[email protected]>
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3675
    Signed-off-by: Michael S. Tsirkin <[email protected]>
    Message-ID: <
    801b1501ee10241f7ac49a10570d548352b36347.1784890517.git.mst@redhat.com>
    ---
     include/migration/vmstate.h |  5 ++++-
     migration/vmstate.c         | 31 ++++++++++++++++++++++++++++---
     2 files changed, 32 insertions(+), 4 deletions(-)

    diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
    index 1b7f295417..e7095cd977 100644
    --- a/include/migration/vmstate.h
    +++ b/include/migration/vmstate.h
    @@ -168,6 +168,9 @@ enum VMStateFlags {
          */
         VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,

    +    /* Use a uint64_t size field for VMS_VBUFFER instead of int32_t. */
    +    VMS_VBUFFER_UINT64              = 0x40000,
    +
         /* Marker for end of list */
         VMS_END                         = 0x20000,
     };
    @@ -788,7 +791,7 @@ extern const VMStateInfo vmstate_info_g_byte_array;
         .field_exists = (_test),                                         \
         .size_offset  = vmstate_offset_value(_state, _field_size, uint64_t),\
         .info         = &vmstate_info_buffer,                            \
    -    .flags        = VMS_VBUFFER | VMS_POINTER,                       \
    +    .flags        = VMS_VBUFFER | VMS_VBUFFER_UINT64 | VMS_POINTER,  \
         .offset       = offsetof(_state, _field),                        \
     }

    diff --git a/migration/vmstate.c b/migration/vmstate.c
    index 50ebe37845..d7f03a9f5b 100644
    --- a/migration/vmstate.c
    +++ b/migration/vmstate.c
    @@ -103,10 +103,24 @@ static int vmstate_size(void *opaque, const
    VMStateField *field)
         int size;

         if (field->flags & VMS_VBUFFER) {
    -        size = *(int32_t *)(opaque + field->size_offset);
    -        if (field->flags & VMS_MULTIPLY) {
    -            size *= field->size;
    +        uint64_t usize64;
    +
    +        if (field->flags & VMS_VBUFFER_UINT64) {
    +            usize64 = *(uint64_t *)(opaque + field->size_offset);
    +        } else {
    +            int32_t ssize32 = *(int32_t *)(opaque + field->size_offset);
    +            if (ssize32 < 0) {
    +                return -1;
    +            }
    +            usize64 = ssize32;
             }
    +        if (field->flags & VMS_MULTIPLY) {
    +            usize64 *= field->size;
    +        }
    +        if (usize64 > INT_MAX) {
    +            return -1;
    +        }
    +        size = usize64;
         } else if (field->flags & VMS_ARRAY_OF_POINTER) {
             /*
              * For an array of pointer, the each element is always size of a
    @@ -337,6 +351,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const
    VMStateDescription *vmsd,
                 void *first_elem = opaque + field->offset;
                 int i, n_elems = vmstate_n_elems(opaque, field);
                 int size = vmstate_size(opaque, field);
    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                return false;
    +            }

                 vmstate_handle_alloc(first_elem, field, opaque);
                 if (field->flags & VMS_POINTER) {
    @@ -661,6 +680,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const
    VMStateDescription *vmsd,
                 bool use_dynamic_array =
                     field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;

    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                ok = false;
    +                goto out;
    +            }
                 trace_vmstate_save_state_loop(vmsd->name, field->name,
    n_elems);
                 if (field->flags & VMS_POINTER) {
                     first_elem = *(void **)first_elem;
    --
    MST
Signature · 1 line
Peter Xu
Signature · 1 line
Peter Xu
#39 lore raw

Re: [PULL 17/30] vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64

Quoted 53 lines · Michael S. Tsirkin
Quoted 52 lines · Peter Xu
Quoted 50 lines · Michael S. Tsirkin
Quoted 41 lines · Peter Xu
Quoted 11 lines · Michael S. Tsirkin
Quoted 7 lines · Peter Xu

This patch is migration only change and hasn't been reviewed.  Give us a few days to review it?
Can't do it now because it is on cellphone and I just bathed my son.

Please hold off merging.

Thanks.

OK. I'll drop this and you will merge it through the migration tree as appropriate?

Let's drop it first so it won't block this pull. I can definitely pick it up when ready, but I want to discuss on how to fix first. Which tree to pick it up isn't a huge matter to me, but the review.

We found a bunch of randomly introduced VMSTATE flags in the past, I used to remove some, at least my plan is in the future each time we introduce a new one (by request from other modules) we better justify it before landing too easily, in case it needs a revert again.

This vhost regression started 11.0 so IIUC we don't need to rush in 11.1. I just noticed I read that patch and didn't notice this, my bad to not have noticed..

For this one specifically..

Quoted 13 lines
Quoted 13 lines · On Sun, Jul 26, 2026, 5:29 p.m. Michael S. Tsirkin <[email protected]> wrote:

vhost user currently saves the inflight buffer to the migration stream using VMSTATE_VBUFFER_UINT64. The size is controlled by the vhost-user backend.

But the implementation of that is broken if size is >2G: it stores the buffer size in a uint64_t field, but vmstate_size() always reads the size field as int32_t regardless of the macro used. This, in turn, causes negative or truncated lengths on load, leading to undersized allocations and down the road out-of-bounds buffer access.

Quoted 4 lines
Quoted 1 line
Quoted 1 line

There's no practical reason to support such large sizes, so it's enough

If there's no real demand to use 2G or more, I suggest we change size to int32_t instead on the user, and revert f6fdd8b2 VMSTATE_VBUFFER_UINT64().

We can't just change it we'll need some tricks to avoid breaking existing migration format. I don't see how it will be much simpler if you take that into account.

Quoted 7 lines
Quoted 1 line

It's not part of the stream, right?

I think it is?

VMSTATE_UINT64(size, struct vhost_inflight) at hw/virtio/vhost.c eventually leads to qemu_put_be64s / qemu_get_be64s in migration/vmstate-types.c

The point is that this "size" is already included on the stream by itself, changing the VMSTATE_VBUFFER_UINT64 to int32 doesn't affect it.

There's a type_check that will complain during save that type(inflight->size) != INT32 (from the macro), but we can change that.

#define vmstate_offset_value(_state, _field, _type) \
(offsetof(_state, _field) + \
type_check(_type, typeof_field(_state, _field)))

Quoted 42 lines
Quoted 4 lines

Here IIUC the VMSD field "size" only hard-coded the binary offset to fetch the int32_t from the object* pointer. IIUC we can simply change it to int32_t and IIUC it will be able to recv stream from src with uint64_t. The stream shouldn't be affected when e.g. we migrate from an older QEMU.

I don't really know what you mean. Yes vmsd macros tie migration stream and the storage format. it kinda makes it easy to add one but the cost is issues like this.

Quoted 29 lines
Quoted 12 lines
Quoted 12 lines

Fabiano and I were looking at rest 20+ possible security related bugs in the past 1-2 weeks, I've some patches to be posted for 11.2 too when we went through all of them. One relevant patch I haven't sent but will do soon:

https://gitlab.com/peterx/qemu/-/commit/afd2cb25defdf78a4ab5279cc16634884792c57a

We encountered quite a few of possible over-allocation on destionation side by manipulating on-wire length field like this one, I believe Fabiano is looking at how to further limit that from 2G if ever possible per-user.

Quoted 5 lines
Quoted 3 lines

I'm not sure if that idea will fly, but I think anything bigger than 2G definitely is not suggested for now when it's only for a type match not real demand..

Frankly 2G is crazy too. I see little reason to focus specifically on >2G.

Yeah..

Quoted 9 lines
Quoted 8 lines

I believe VMSTATE_VBUFFER_UINT32 is problematic on its own too, I'll see how to fix that. That one is slightly easier, worst case is we bail out for 2G (hence ignore bit 31, which shouldn't be used in reality for legit users), but I'll think about it.

Thanks,

Quoted 9 lines
Quoted 6 lines
Quoted 3 lines

I just do not see (almost) any case where migration destination does not know the actual size needed, or at least
an upper bound on such.

In many cases we transfer the length first then the array following that with the length. That's a common trick we played like vhost here.

we need to start specifying the cap for these.

I agree. Most things will have a sane cap once you think hard enough. Of course, there's a risk of selecting a limit too low, but currently we can't even do that.

I think the first step is defining new macros so that every len/size/etc gets properly identified. The VMSTATE_VBUFFER_* are already a start, we can add an aditional member that doesn't go on the stream and check against it when loading.

Quoted 5 lines

The upper bound is indeed a challenge, I confess I don't know an answer, and we will need to be careful on adding anything that might break a legit user. I believe Fabiano might have some better clue there, so I'd leave it to him until I can read his patches.

I'm also trying to look ahead into how we'll apply whatever fix we come up with here to the pile of unstructured qemu_put|get_byte we have all over the place. I'm experimenting with how much code we can convert into VMStateDescription/VMStateField. The QEMU_VM_* flags themselves seem to be easy to do (then we version the vmstate, deprecate, etc).

Quoted 120 lines
Quoted 120 lines

Thanks,

Quoted 115 lines
Quoted 114 lines
Quoted 111 lines
Quoted 110 lines

    to validate: read the field as uint64_t when VMS_VBUFFER_UINT64 is set,
    reject negative or oversized values, and propagate errors to
    vmstate_load_vmsd().

    Note: the large value is coming from the backend, not guest, so this
    shouldn't be considered a security issue. The CVE was assigned before
    the qemu security policy was updated to exclude this class of bugs.

    Fixes: CVE-2026-6426
    Fixes: f6fdd8b2bd ("vmstate: introduce VMSTATE_VBUFFER_UINT64")
    Cc: Alexandr Moshkov <[email protected]>
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3675
    Signed-off-by: Michael S. Tsirkin <[email protected]>
    Message-ID: <
    801b1501ee10241f7ac49a10570d548352b36347.1784890517.git.mst@redhat.com>
    ---
     include/migration/vmstate.h |  5 ++++-
     migration/vmstate.c         | 31 ++++++++++++++++++++++++++++---
     2 files changed, 32 insertions(+), 4 deletions(-)

    diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
    index 1b7f295417..e7095cd977 100644
    --- a/include/migration/vmstate.h
    +++ b/include/migration/vmstate.h
    @@ -168,6 +168,9 @@ enum VMStateFlags {
          */
         VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,

    +    /* Use a uint64_t size field for VMS_VBUFFER instead of int32_t. */
    +    VMS_VBUFFER_UINT64              = 0x40000,
    +
         /* Marker for end of list */
         VMS_END                         = 0x20000,
     };
    @@ -788,7 +791,7 @@ extern const VMStateInfo vmstate_info_g_byte_array;
         .field_exists = (_test),                                         \
         .size_offset  = vmstate_offset_value(_state, _field_size, uint64_t),\
         .info         = &vmstate_info_buffer,                            \
    -    .flags        = VMS_VBUFFER | VMS_POINTER,                       \
    +    .flags        = VMS_VBUFFER | VMS_VBUFFER_UINT64 | VMS_POINTER,  \
         .offset       = offsetof(_state, _field),                        \
     }

    diff --git a/migration/vmstate.c b/migration/vmstate.c
    index 50ebe37845..d7f03a9f5b 100644
    --- a/migration/vmstate.c
    +++ b/migration/vmstate.c
    @@ -103,10 +103,24 @@ static int vmstate_size(void *opaque, const
    VMStateField *field)
         int size;

         if (field->flags & VMS_VBUFFER) {
    -        size = *(int32_t *)(opaque + field->size_offset);
    -        if (field->flags & VMS_MULTIPLY) {
    -            size *= field->size;
    +        uint64_t usize64;
    +
    +        if (field->flags & VMS_VBUFFER_UINT64) {
    +            usize64 = *(uint64_t *)(opaque + field->size_offset);
    +        } else {
    +            int32_t ssize32 = *(int32_t *)(opaque + field->size_offset);
    +            if (ssize32 < 0) {
    +                return -1;
    +            }
    +            usize64 = ssize32;
             }
    +        if (field->flags & VMS_MULTIPLY) {
    +            usize64 *= field->size;
    +        }
    +        if (usize64 > INT_MAX) {
    +            return -1;
    +        }
    +        size = usize64;
         } else if (field->flags & VMS_ARRAY_OF_POINTER) {
             /*
              * For an array of pointer, the each element is always size of a
    @@ -337,6 +351,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const
    VMStateDescription *vmsd,
                 void *first_elem = opaque + field->offset;
                 int i, n_elems = vmstate_n_elems(opaque, field);
                 int size = vmstate_size(opaque, field);
    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                return false;
    +            }

                 vmstate_handle_alloc(first_elem, field, opaque);
                 if (field->flags & VMS_POINTER) {
    @@ -661,6 +680,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const
    VMStateDescription *vmsd,
                 bool use_dynamic_array =
                     field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;

    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                ok = false;
    +                goto out;
    +            }
                 trace_vmstate_save_state_loop(vmsd->name, field->name,
    n_elems);
                 if (field->flags & VMS_POINTER) {
                     first_elem = *(void **)first_elem;
    --
    MST
Signature · 1 line
Peter Xu
Signature · 1 line
Peter Xu
#40 lore raw

Re: [PULL 17/30] vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64

Quoted 59 lines · Fabiano Rosas
Quoted 57 lines · Michael S. Tsirkin
Quoted 45 lines · Peter Xu
Quoted 44 lines · Michael S. Tsirkin
Quoted 43 lines · Peter Xu
Quoted 11 lines · Michael S. Tsirkin
Quoted 7 lines · Peter Xu

This patch is migration only change and hasn't been reviewed.  Give us a few days to review it?
Can't do it now because it is on cellphone and I just bathed my son.

Please hold off merging.

Thanks.

OK. I'll drop this and you will merge it through the migration tree as appropriate?

Let's drop it first so it won't block this pull. I can definitely pick it up when ready, but I want to discuss on how to fix first. Which tree to pick it up isn't a huge matter to me, but the review.

We found a bunch of randomly introduced VMSTATE flags in the past, I used to remove some, at least my plan is in the future each time we introduce a new one (by request from other modules) we better justify it before landing too easily, in case it needs a revert again.

This vhost regression started 11.0 so IIUC we don't need to rush in 11.1. I just noticed I read that patch and didn't notice this, my bad to not have noticed..

For this one specifically..

Quoted 14 lines
Quoted 14 lines · On Sun, Jul 26, 2026, 5:29 p.m. Michael S. Tsirkin <[email protected]> wrote:

vhost user currently saves the inflight buffer to the migration stream using VMSTATE_VBUFFER_UINT64. The size is controlled by the vhost-user backend.

But the implementation of that is broken if size is >2G: it stores the buffer size in a uint64_t field, but vmstate_size() always reads the size field as int32_t regardless of the macro used. This, in turn, causes negative or truncated lengths on load, leading to undersized allocations and down the road out-of-bounds buffer access.

There's no practical reason to support such large sizes, so it's enough

Quoted 8 lines
Quoted 6 lines
Quoted 2 lines

If there's no real demand to use 2G or more, I suggest we change size to int32_t instead on the user, and revert f6fdd8b2 VMSTATE_VBUFFER_UINT64().

We can't just change it we'll need some tricks to avoid breaking existing migration format. I don't see how it will be much simpler if you take that into account.

It's not part of the stream, right?

I think it is?

Quoted 7 lines
Quoted 3 lines

VMSTATE_UINT64(size, struct vhost_inflight) at hw/virtio/vhost.c eventually leads to qemu_put_be64s / qemu_get_be64s in migration/vmstate-types.c

The point is that this "size" is already included on the stream by itself, changing the VMSTATE_VBUFFER_UINT64 to int32 doesn't affect it.

I'd use uint32_t there is no reason to use signed.

Quoted 8 lines

There's a type_check that will complain during save that type(inflight->size) != INT32 (from the macro), but we can change that.

#define vmstate_offset_value(_state, _field, _type) \
(offsetof(_state, _field) + \
type_check(_type, typeof_field(_state, _field)))

All i am saying we need to keep some special macro we can not just change virtio to replace VMSTATE_VBUFFER_UINT64 with VMSTATE_VBUFFER_UINT32

whether it's a flag or whatever, I don't much care.

I assigned the bug in question to Peter and you guys decide how to handle it.

Quoted 52 lines
Quoted 42 lines
Quoted 4 lines

Here IIUC the VMSD field "size" only hard-coded the binary offset to fetch the int32_t from the object* pointer. IIUC we can simply change it to int32_t and IIUC it will be able to recv stream from src with uint64_t. The stream shouldn't be affected when e.g. we migrate from an older QEMU.

I don't really know what you mean. Yes vmsd macros tie migration stream and the storage format. it kinda makes it easy to add one but the cost is issues like this.

Quoted 29 lines
Quoted 12 lines
Quoted 12 lines

Fabiano and I were looking at rest 20+ possible security related bugs in the past 1-2 weeks, I've some patches to be posted for 11.2 too when we went through all of them. One relevant patch I haven't sent but will do soon:

https://gitlab.com/peterx/qemu/-/commit/afd2cb25defdf78a4ab5279cc16634884792c57a

We encountered quite a few of possible over-allocation on destionation side by manipulating on-wire length field like this one, I believe Fabiano is looking at how to further limit that from 2G if ever possible per-user.

Quoted 5 lines
Quoted 3 lines

I'm not sure if that idea will fly, but I think anything bigger than 2G definitely is not suggested for now when it's only for a type match not real demand..

Frankly 2G is crazy too. I see little reason to focus specifically on >2G.

Yeah..

Quoted 9 lines
Quoted 8 lines

I believe VMSTATE_VBUFFER_UINT32 is problematic on its own too, I'll see how to fix that. That one is slightly easier, worst case is we bail out for 2G (hence ignore bit 31, which shouldn't be used in reality for legit users), but I'll think about it.

Thanks,

Quoted 9 lines
Quoted 6 lines
Quoted 3 lines

I just do not see (almost) any case where migration destination does not know the actual size needed, or at least
an upper bound on such.

In many cases we transfer the length first then the array following that with the length. That's a common trick we played like vhost here.

we need to start specifying the cap for these.

Quoted 8 lines

I agree. Most things will have a sane cap once you think hard enough. Of course, there's a risk of selecting a limit too low, but currently we can't even do that.

I think the first step is defining new macros so that every len/size/etc gets properly identified. The VMSTATE_VBUFFER_* are already a start, we can add an aditional member that doesn't go on the stream and check against it when loading.

right

Quoted 11 lines
Quoted 5 lines

The upper bound is indeed a challenge, I confess I don't know an answer, and we will need to be careful on adding anything that might break a legit user. I believe Fabiano might have some better clue there, so I'd leave it to him until I can read his patches.

I'm also trying to look ahead into how we'll apply whatever fix we come up with here to the pile of unstructured qemu_put|get_byte we have all over the place. I'm experimenting with how much code we can convert into VMStateDescription/VMStateField. The QEMU_VM_* flags themselves seem to be easy to do (then we version the vmstate, deprecate, etc).

Frankly if you are bothering with tree wide changes, can we please finally do something forward compatible that will allow us to add extra information without breaking compat?
Or ideally just self delimiting format, one can dream...

Quoted 120 lines
Quoted 120 lines
Quoted 120 lines

Thanks,

Quoted 115 lines
Quoted 114 lines
Quoted 111 lines
Quoted 110 lines

    to validate: read the field as uint64_t when VMS_VBUFFER_UINT64 is set,
    reject negative or oversized values, and propagate errors to
    vmstate_load_vmsd().

    Note: the large value is coming from the backend, not guest, so this
    shouldn't be considered a security issue. The CVE was assigned before
    the qemu security policy was updated to exclude this class of bugs.

    Fixes: CVE-2026-6426
    Fixes: f6fdd8b2bd ("vmstate: introduce VMSTATE_VBUFFER_UINT64")
    Cc: Alexandr Moshkov <[email protected]>
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3675
    Signed-off-by: Michael S. Tsirkin <[email protected]>
    Message-ID: <
    801b1501ee10241f7ac49a10570d548352b36347.1784890517.git.mst@redhat.com>
    ---
     include/migration/vmstate.h |  5 ++++-
     migration/vmstate.c         | 31 ++++++++++++++++++++++++++++---
     2 files changed, 32 insertions(+), 4 deletions(-)

    diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
    index 1b7f295417..e7095cd977 100644
    --- a/include/migration/vmstate.h
    +++ b/include/migration/vmstate.h
    @@ -168,6 +168,9 @@ enum VMStateFlags {
          */
         VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,

    +    /* Use a uint64_t size field for VMS_VBUFFER instead of int32_t. */
    +    VMS_VBUFFER_UINT64              = 0x40000,
    +
         /* Marker for end of list */
         VMS_END                         = 0x20000,
     };
    @@ -788,7 +791,7 @@ extern const VMStateInfo vmstate_info_g_byte_array;
         .field_exists = (_test),                                         \
         .size_offset  = vmstate_offset_value(_state, _field_size, uint64_t),\
         .info         = &vmstate_info_buffer,                            \
    -    .flags        = VMS_VBUFFER | VMS_POINTER,                       \
    +    .flags        = VMS_VBUFFER | VMS_VBUFFER_UINT64 | VMS_POINTER,  \
         .offset       = offsetof(_state, _field),                        \
     }

    diff --git a/migration/vmstate.c b/migration/vmstate.c
    index 50ebe37845..d7f03a9f5b 100644
    --- a/migration/vmstate.c
    +++ b/migration/vmstate.c
    @@ -103,10 +103,24 @@ static int vmstate_size(void *opaque, const
    VMStateField *field)
         int size;

         if (field->flags & VMS_VBUFFER) {
    -        size = *(int32_t *)(opaque + field->size_offset);
    -        if (field->flags & VMS_MULTIPLY) {
    -            size *= field->size;
    +        uint64_t usize64;
    +
    +        if (field->flags & VMS_VBUFFER_UINT64) {
    +            usize64 = *(uint64_t *)(opaque + field->size_offset);
    +        } else {
    +            int32_t ssize32 = *(int32_t *)(opaque + field->size_offset);
    +            if (ssize32 < 0) {
    +                return -1;
    +            }
    +            usize64 = ssize32;
             }
    +        if (field->flags & VMS_MULTIPLY) {
    +            usize64 *= field->size;
    +        }
    +        if (usize64 > INT_MAX) {
    +            return -1;
    +        }
    +        size = usize64;
         } else if (field->flags & VMS_ARRAY_OF_POINTER) {
             /*
              * For an array of pointer, the each element is always size of a
    @@ -337,6 +351,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const
    VMStateDescription *vmsd,
                 void *first_elem = opaque + field->offset;
                 int i, n_elems = vmstate_n_elems(opaque, field);
                 int size = vmstate_size(opaque, field);
    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                return false;
    +            }

                 vmstate_handle_alloc(first_elem, field, opaque);
                 if (field->flags & VMS_POINTER) {
    @@ -661,6 +680,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const
    VMStateDescription *vmsd,
                 bool use_dynamic_array =
                     field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;

    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                ok = false;
    +                goto out;
    +            }
                 trace_vmstate_save_state_loop(vmsd->name, field->name,
    n_elems);
                 if (field->flags & VMS_POINTER) {
                     first_elem = *(void **)first_elem;
    --
    MST
Signature · 1 line
Peter Xu
Signature · 1 line
Peter Xu
#41 lore raw

Re: [PULL 17/30] vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64

Quoted 64 lines · Michael S. Tsirkin
Quoted 63 lines · Fabiano Rosas
Quoted 60 lines · Michael S. Tsirkin
Quoted 45 lines · Peter Xu
Quoted 44 lines · Michael S. Tsirkin
Quoted 43 lines · Peter Xu
Quoted 11 lines · Michael S. Tsirkin
Quoted 7 lines · Peter Xu

This patch is migration only change and hasn't been reviewed.  Give us a few days to review it?
Can't do it now because it is on cellphone and I just bathed my son.

Please hold off merging.

Thanks.

OK. I'll drop this and you will merge it through the migration tree as appropriate?

Let's drop it first so it won't block this pull. I can definitely pick it up when ready, but I want to discuss on how to fix first. Which tree to pick it up isn't a huge matter to me, but the review.

We found a bunch of randomly introduced VMSTATE flags in the past, I used to remove some, at least my plan is in the future each time we introduce a new one (by request from other modules) we better justify it before landing too easily, in case it needs a revert again.

This vhost regression started 11.0 so IIUC we don't need to rush in 11.1. I just noticed I read that patch and didn't notice this, my bad to not have noticed..

For this one specifically..

Quoted 14 lines
Quoted 14 lines · On Sun, Jul 26, 2026, 5:29 p.m. Michael S. Tsirkin <[email protected]> wrote:

vhost user currently saves the inflight buffer to the migration stream using VMSTATE_VBUFFER_UINT64. The size is controlled by the vhost-user backend.

But the implementation of that is broken if size is >2G: it stores the buffer size in a uint64_t field, but vmstate_size() always reads the size field as int32_t regardless of the macro used. This, in turn, causes negative or truncated lengths on load, leading to undersized allocations and down the road out-of-bounds buffer access.

There's no practical reason to support such large sizes, so it's enough

Quoted 8 lines
Quoted 6 lines
Quoted 2 lines

If there's no real demand to use 2G or more, I suggest we change size to int32_t instead on the user, and revert f6fdd8b2 VMSTATE_VBUFFER_UINT64().

We can't just change it we'll need some tricks to avoid breaking existing migration format. I don't see how it will be much simpler if you take that into account.

It's not part of the stream, right?

I think it is?

VMSTATE_UINT64(size, struct vhost_inflight) at hw/virtio/vhost.c eventually leads to qemu_put_be64s / qemu_get_be64s in migration/vmstate-types.c

Quoted 6 lines
Quoted 3 lines

The point is that this "size" is already included on the stream by itself, changing the VMSTATE_VBUFFER_UINT64 to int32 doesn't affect it.

I'd use uint32_t there is no reason to use signed.

Sure, using int32_t is the first thought only because no matter which we use it can't use anything beyond INT32_MAX... with uint32_t it'll be fine as long as we also check against INT32_MAX first.

Quoted 11 lines
Quoted 10 lines

There's a type_check that will complain during save that type(inflight->size) != INT32 (from the macro), but we can change that.

#define vmstate_offset_value(_state, _field, _type) \
(offsetof(_state, _field) + \
type_check(_type, typeof_field(_state, _field)))

Quoted 8 lines

All i am saying we need to keep some special macro we can not just change virtio to replace VMSTATE_VBUFFER_UINT64 with VMSTATE_VBUFFER_UINT32

whether it's a flag or whatever, I don't much care.

I assigned the bug in question to Peter and you guys decide how to handle it.

It's a rare accident, normally we shouldn't need such conversion.. Sure, I'll try to prepare a patch today.

Quoted 77 lines
Quoted 54 lines
Quoted 44 lines
Quoted 6 lines

Here IIUC the VMSD field "size" only hard-coded the binary offset to fetch the int32_t from the object* pointer. IIUC we can simply change it to int32_t and IIUC it will be able to recv stream from src with uint64_t. The stream shouldn't be affected when e.g. we migrate from an older QEMU.

I don't really know what you mean. Yes vmsd macros tie migration stream and the storage format. it kinda makes it easy to add one but the cost is issues like this.

Quoted 29 lines
Quoted 12 lines
Quoted 12 lines

Fabiano and I were looking at rest 20+ possible security related bugs in the past 1-2 weeks, I've some patches to be posted for 11.2 too when we went through all of them. One relevant patch I haven't sent but will do soon:

https://gitlab.com/peterx/qemu/-/commit/afd2cb25defdf78a4ab5279cc16634884792c57a

We encountered quite a few of possible over-allocation on destionation side by manipulating on-wire length field like this one, I believe Fabiano is looking at how to further limit that from 2G if ever possible per-user.

Quoted 5 lines
Quoted 3 lines

I'm not sure if that idea will fly, but I think anything bigger than 2G definitely is not suggested for now when it's only for a type match not real demand..

Frankly 2G is crazy too. I see little reason to focus specifically on >2G.

Yeah..

Quoted 9 lines
Quoted 8 lines

I believe VMSTATE_VBUFFER_UINT32 is problematic on its own too, I'll see how to fix that. That one is slightly easier, worst case is we bail out for 2G (hence ignore bit 31, which shouldn't be used in reality for legit users), but I'll think about it.

Thanks,

Quoted 9 lines
Quoted 6 lines
Quoted 3 lines

I just do not see (almost) any case where migration destination does not know the actual size needed, or at least
an upper bound on such.

In many cases we transfer the length first then the array following that with the length. That's a common trick we played like vhost here.

we need to start specifying the cap for these.

Quoted 8 lines

I agree. Most things will have a sane cap once you think hard enough. Of course, there's a risk of selecting a limit too low, but currently we can't even do that.

I think the first step is defining new macros so that every len/size/etc gets properly identified. The VMSTATE_VBUFFER_* are already a start, we can add an aditional member that doesn't go on the stream and check against it when loading.

right

Quoted 11 lines
Quoted 5 lines

The upper bound is indeed a challenge, I confess I don't know an answer, and we will need to be careful on adding anything that might break a legit user. I believe Fabiano might have some better clue there, so I'd leave it to him until I can read his patches.

I'm also trying to look ahead into how we'll apply whatever fix we come up with here to the pile of unstructured qemu_put|get_byte we have all over the place. I'm experimenting with how much code we can convert into VMStateDescription/VMStateField. The QEMU_VM_* flags themselves seem to be easy to do (then we version the vmstate, deprecate, etc).

Quoted 4 lines

Frankly if you are bothering with tree wide changes, can we please finally do something forward compatible that will allow us to add extra information without breaking compat?
Or ideally just self delimiting format, one can dream...

Yes. For now VMSD fields are binary blobs so we can't easily achieve it, if we can have handshake feature (in Fabiano's queue) maybe we can do it without machine compat properties. It's only a matter of time to implement.. and how we prioritize which to work on..

Thanks,

Quoted 123 lines
Quoted 122 lines
Quoted 122 lines
Quoted 122 lines

Thanks,

Quoted 115 lines
Quoted 114 lines
Quoted 111 lines
Quoted 110 lines

    to validate: read the field as uint64_t when VMS_VBUFFER_UINT64 is set,
    reject negative or oversized values, and propagate errors to
    vmstate_load_vmsd().

    Note: the large value is coming from the backend, not guest, so this
    shouldn't be considered a security issue. The CVE was assigned before
    the qemu security policy was updated to exclude this class of bugs.

    Fixes: CVE-2026-6426
    Fixes: f6fdd8b2bd ("vmstate: introduce VMSTATE_VBUFFER_UINT64")
    Cc: Alexandr Moshkov <[email protected]>
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3675
    Signed-off-by: Michael S. Tsirkin <[email protected]>
    Message-ID: <
    801b1501ee10241f7ac49a10570d548352b36347.1784890517.git.mst@redhat.com>
    ---
     include/migration/vmstate.h |  5 ++++-
     migration/vmstate.c         | 31 ++++++++++++++++++++++++++++---
     2 files changed, 32 insertions(+), 4 deletions(-)

    diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
    index 1b7f295417..e7095cd977 100644
    --- a/include/migration/vmstate.h
    +++ b/include/migration/vmstate.h
    @@ -168,6 +168,9 @@ enum VMStateFlags {
          */
         VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,

    +    /* Use a uint64_t size field for VMS_VBUFFER instead of int32_t. */
    +    VMS_VBUFFER_UINT64              = 0x40000,
    +
         /* Marker for end of list */
         VMS_END                         = 0x20000,
     };
    @@ -788,7 +791,7 @@ extern const VMStateInfo vmstate_info_g_byte_array;
         .field_exists = (_test),                                         \
         .size_offset  = vmstate_offset_value(_state, _field_size, uint64_t),\
         .info         = &vmstate_info_buffer,                            \
    -    .flags        = VMS_VBUFFER | VMS_POINTER,                       \
    +    .flags        = VMS_VBUFFER | VMS_VBUFFER_UINT64 | VMS_POINTER,  \
         .offset       = offsetof(_state, _field),                        \
     }

    diff --git a/migration/vmstate.c b/migration/vmstate.c
    index 50ebe37845..d7f03a9f5b 100644
    --- a/migration/vmstate.c
    +++ b/migration/vmstate.c
    @@ -103,10 +103,24 @@ static int vmstate_size(void *opaque, const
    VMStateField *field)
         int size;

         if (field->flags & VMS_VBUFFER) {
    -        size = *(int32_t *)(opaque + field->size_offset);
    -        if (field->flags & VMS_MULTIPLY) {
    -            size *= field->size;
    +        uint64_t usize64;
    +
    +        if (field->flags & VMS_VBUFFER_UINT64) {
    +            usize64 = *(uint64_t *)(opaque + field->size_offset);
    +        } else {
    +            int32_t ssize32 = *(int32_t *)(opaque + field->size_offset);
    +            if (ssize32 < 0) {
    +                return -1;
    +            }
    +            usize64 = ssize32;
             }
    +        if (field->flags & VMS_MULTIPLY) {
    +            usize64 *= field->size;
    +        }
    +        if (usize64 > INT_MAX) {
    +            return -1;
    +        }
    +        size = usize64;
         } else if (field->flags & VMS_ARRAY_OF_POINTER) {
             /*
              * For an array of pointer, the each element is always size of a
    @@ -337,6 +351,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const
    VMStateDescription *vmsd,
                 void *first_elem = opaque + field->offset;
                 int i, n_elems = vmstate_n_elems(opaque, field);
                 int size = vmstate_size(opaque, field);
    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                return false;
    +            }

                 vmstate_handle_alloc(first_elem, field, opaque);
                 if (field->flags & VMS_POINTER) {
    @@ -661,6 +680,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const
    VMStateDescription *vmsd,
                 bool use_dynamic_array =
                     field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;

    +            if (size < 0) {
    +                error_setg(errp, "VMState field '%s': invalid size",
    +                           field->name);
    +                ok = false;
    +                goto out;
    +            }
                 trace_vmstate_save_state_loop(vmsd->name, field->name,
    n_elems);
                 if (field->flags & VMS_POINTER) {
                     first_elem = *(void **)first_elem;
    --
    MST
Signature · 1 line
Peter Xu
Signature · 1 line
Peter Xu
Signature · 1 line
Peter Xu
#42 lore raw
In reply to Michael S. Tsirkin
Quoted 9 lines · Michael S. Tsirkin

The following changes since commit cbd42e2b75b23953a9bdb073c8531518b1bd6163:

Update version for v11.1.0-rc1 release (2026-07-21 13:18:25 -0400)

are available in the Git repository at:

https://git.kernel.org/pub/scm/virt/kvm/mst/qemu.git tags/for_upstream

for you to fetch changes up to 13342b9c4104f3bf93c2e52e7de4b383971d7bd6:

moved to f2fe2afd27ddf93b49a16a29e8224d16b9888953 now:
I dropped
vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64
will go in through the migration tree.

Quoted 84 lines

backends/rng: cap request size to avoid oversized allocation (2026-07-26 17:26:41 -0400)

----------------------------------------------------------------
pci, vhost, virtio, iommu: bugfixes

Fixes all over the place, including a bunch of CVE fixes.

----------------------------------------------------------------
Clément MATHIEU--DRIF (1):
intel_iommu: Check address mask before using it in pasid-based iotlb invalidation

GuoHan Zhao (1):
hw/virtio/vdpa-dev: pass set_config buffer to vhost backend

Haotian Jiang (1):
hw/cxl: fix OOB access in cxl_doe_cdat_rsp via entry_handle

Laurent Vivier (4):
hw/virtio: reject zero-length packed indirect descriptor table hw/net/virtio-net: Protect from DMA re-entrancy bugs hw/virtio-rng: Fix host use-after-free (CVE-2026-50624) backends/rng: cap request size to avoid oversized allocation

Manos Pitsidianakis (2):
virtio-snd: check rx buffer descriptor size
virtio-snd: check for overflow before g_malloc0

Michael S. Tsirkin (18):
virtio: use masked features with set_features_ex
virtio-net: fix OOB read in RSC receive path
virtio-net: fix short frame OOB read in receive_filter()
libvhost-user: protect against OOB writes in vu_set_inflight_fd
libvhost-user: protect against OOB vring queue access
vhost: do not crash on ring map failure
virtio-scsi: fix SCSIRequest leak on a bad request
virtio-mmio: fix QUEUE_NUM_MAX
virtio: fix queue size validation against allocated maximum
virtio: stop migrating num_default, validate vring.num on load
virtio: fail early on bad config_len in migration
vhost-user: assert nregions within limit
virtio-pmem: wait for flush requests on unrealize
libvhost-user: validate last_batch_head in vu_check_queue_inflights
libvhost-user: fix heap overflow in vu_check_queue_inflights
vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64
libvduse: validate vq size
virtio-iommu: fix OOM due to unbounded call_rcu

Peter Maydell (3):
hw/pci-host/q35.c: Always initialize smram-region even if SMM disabled hw/pci-host/q35.c: Factor out creation of SMRAM MRs hw/pci-host/q35.c: Avoid early return in mch_write_config()

 include/hw/scsi/scsi.h                    |   1 +
 include/hw/virtio/virtio-bus.h            |   1 +
 include/hw/virtio/virtio-iommu.h          |   1 +
 include/hw/virtio/virtio-mmio.h           |   1 +
 include/hw/virtio/virtio-pmem.h           |   1 +
 include/migration/vmstate.h               |   5 +-
 include/system/rng.h                      |  14 ++++
 backends/rng.c                            |  21 +++++-
 hw/audio/virtio-snd.c                     |  24 +++++--
 hw/core/machine.c                         |   1 +
 hw/i386/intel_iommu.c                     |   9 +++
 hw/mem/cxl_type3.c                        |   3 +
 hw/net/virtio-net.c                       |  25 ++++++-
 hw/pci-bridge/cxl_upstream.c              |   3 +
 hw/pci-host/q35.c                         | 110 ++++++++++++++++--------------
 hw/scsi/scsi-bus.c                        |   7 ++
 hw/scsi/virtio-scsi.c                     |   2 +
 hw/virtio/vdpa-dev.c                      |   2 +-
 hw/virtio/vhost-user.c                    |   3 +
 hw/virtio/vhost.c                         |   5 +-
 hw/virtio/virtio-iommu.c                  |  29 ++++++++
 hw/virtio/virtio-mmio.c                   |   7 +-
 hw/virtio/virtio-pmem.c                   |  17 ++++-
 hw/virtio/virtio-rng.c                    |   2 +
 hw/virtio/virtio.c                        |  43 ++++++++----
 migration/vmstate.c                       |  31 ++++++++-
 subprojects/libvduse/libvduse.c           |   5 ++
 subprojects/libvhost-user/libvhost-user.c |  61 ++++++++++++++++-
 28 files changed, 345 insertions(+), 89 deletions(-)
#43 lore raw

Hi Michael

Quoted 10 lines · Michael S. Tsirkin
Quoted 8 lines · Michael S. Tsirkin

The following changes since commit cbd42e2b75b23953a9bdb073c8531518b1bd6163:

Update version for v11.1.0-rc1 release (2026-07-21 13:18:25 -0400)

are available in the Git repository at:

https://git.kernel.org/pub/scm/virt/kvm/mst/qemu.git tags/for_upstream

Quoted 8 lines
Quoted 1 line

for you to fetch changes up to 13342b9c4104f3bf93c2e52e7de4b383971d7bd6:

moved to f2fe2afd27ddf93b49a16a29e8224d16b9888953 now:
I dropped
vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64
will go in through the migration tree.

Some of the commits have duplicate Signed-off lines. It's also strange to be both the Author and Reviewed-by (and some did not receive external review)

I guess we could teach checkpatch about those quirks

Quoted 86 lines
Quoted 84 lines

backends/rng: cap request size to avoid oversized allocation (2026-07-26 17:26:41 -0400)

----------------------------------------------------------------
pci, vhost, virtio, iommu: bugfixes

Fixes all over the place, including a bunch of CVE fixes.

----------------------------------------------------------------
Clément MATHIEU--DRIF (1):
intel_iommu: Check address mask before using it in pasid-based iotlb invalidation

GuoHan Zhao (1):
hw/virtio/vdpa-dev: pass set_config buffer to vhost backend

Haotian Jiang (1):
hw/cxl: fix OOB access in cxl_doe_cdat_rsp via entry_handle

Laurent Vivier (4):
hw/virtio: reject zero-length packed indirect descriptor table hw/net/virtio-net: Protect from DMA re-entrancy bugs hw/virtio-rng: Fix host use-after-free (CVE-2026-50624) backends/rng: cap request size to avoid oversized allocation

Manos Pitsidianakis (2):
virtio-snd: check rx buffer descriptor size
virtio-snd: check for overflow before g_malloc0

Michael S. Tsirkin (18):
virtio: use masked features with set_features_ex
virtio-net: fix OOB read in RSC receive path
virtio-net: fix short frame OOB read in receive_filter()
libvhost-user: protect against OOB writes in vu_set_inflight_fd
libvhost-user: protect against OOB vring queue access
vhost: do not crash on ring map failure
virtio-scsi: fix SCSIRequest leak on a bad request
virtio-mmio: fix QUEUE_NUM_MAX
virtio: fix queue size validation against allocated maximum
virtio: stop migrating num_default, validate vring.num on load
virtio: fail early on bad config_len in migration
vhost-user: assert nregions within limit
virtio-pmem: wait for flush requests on unrealize
libvhost-user: validate last_batch_head in vu_check_queue_inflights
libvhost-user: fix heap overflow in vu_check_queue_inflights
vmstate: fix type confusion in vmstate_size() for VMSTATE_VBUFFER_UINT64
libvduse: validate vq size
virtio-iommu: fix OOM due to unbounded call_rcu

Peter Maydell (3):
hw/pci-host/q35.c: Always initialize smram-region even if SMM disabled hw/pci-host/q35.c: Factor out creation of SMRAM MRs hw/pci-host/q35.c: Avoid early return in mch_write_config()

 include/hw/scsi/scsi.h                    |   1 +
 include/hw/virtio/virtio-bus.h            |   1 +
 include/hw/virtio/virtio-iommu.h          |   1 +
 include/hw/virtio/virtio-mmio.h           |   1 +
 include/hw/virtio/virtio-pmem.h           |   1 +
 include/migration/vmstate.h               |   5 +-
 include/system/rng.h                      |  14 ++++
 backends/rng.c                            |  21 +++++-
 hw/audio/virtio-snd.c                     |  24 +++++--
 hw/core/machine.c                         |   1 +
 hw/i386/intel_iommu.c                     |   9 +++
 hw/mem/cxl_type3.c                        |   3 +
 hw/net/virtio-net.c                       |  25 ++++++-
 hw/pci-bridge/cxl_upstream.c              |   3 +
 hw/pci-host/q35.c                         | 110 ++++++++++++++++--------------
 hw/scsi/scsi-bus.c                        |   7 ++
 hw/scsi/virtio-scsi.c                     |   2 +
 hw/virtio/vdpa-dev.c                      |   2 +-
 hw/virtio/vhost-user.c                    |   3 +
 hw/virtio/vhost.c                         |   5 +-
 hw/virtio/virtio-iommu.c                  |  29 ++++++++
 hw/virtio/virtio-mmio.c                   |   7 +-
 hw/virtio/virtio-pmem.c                   |  17 ++++-
 hw/virtio/virtio-rng.c                    |   2 +
 hw/virtio/virtio.c                        |  43 ++++++++----
 migration/vmstate.c                       |  31 ++++++++-
 subprojects/libvduse/libvduse.c           |   5 ++
 subprojects/libvhost-user/libvhost-user.c |  61 ++++++++++++++++-
 28 files changed, 345 insertions(+), 89 deletions(-)
Signature · 1 line
Marc-André Lureau