Commit 595273f1b7 for qemu.org

commit 595273f1b71e7de40867223d1566be8355353921
Author: Fabiano Rosas <farosas@suse.de>
Date:   Tue Aug 18 15:24:41 2026 -0300

    migration: Harden vmstate_size

    Protect vmstate_size against overflow of the on-stack 64bit integer
    during multiplication. The data at size_indirect.offset comes from the
    migration stream.

    Acked-by: Peter Xu <peterx@redhat.com>
    Signed-off-by: Fabiano Rosas <farosas@suse.de>

diff --git a/migration/vmstate.c b/migration/vmstate.c
index cbcd86e5b6..24004565bf 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -126,14 +126,20 @@ static uint64_t vmstate_n_elems(void *opaque, const VMStateField *field)
     return n_elems;
 }

-static uint64_t vmstate_size(void *opaque, const VMStateField *field)
+static bool vmstate_size(void *opaque, const VMStateField *field,
+                         uint64_t *sz, Error **errp)
 {
     uint64_t size;

+    *sz = 0;
+
     if (field->flags & VMS_VBUFFER) {
         size = vmstate_read_from_offset(&field->size_indirect, opaque);
-        if (field->flags & VMS_MULTIPLY) {
-            size *= field->size;
+        if ((field->flags & VMS_MULTIPLY) &&
+            umul64_overflow(size, field->size, &size)) {
+            error_setg(errp, "%s: VMState field '%s' multiply overflow",
+                       __func__, field->name);
+            return false;
         }
     } else if (field->flags & VMS_ARRAY_OF_POINTER) {
         /*
@@ -145,7 +151,8 @@ static uint64_t vmstate_size(void *opaque, const VMStateField *field)
         size = field->size;
     }

-    return size;
+    *sz = size;
+    return true;
 }

 static bool vmstate_handle_alloc(void *ptr, const VMStateField *field,
@@ -377,7 +384,11 @@ bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
             void *first_elem = opaque + field->offset;
             int i;
             uint64_t n_elems = vmstate_n_elems(opaque, field);
-            uint64_t size = vmstate_size(opaque, field);
+            uint64_t size;
+
+            if (!vmstate_size(opaque, field, &size, errp)) {
+                return false;
+            }

             if (!vmstate_handle_alloc(first_elem, field, n_elems, size, errp)) {
                 return false;
@@ -695,9 +706,10 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
             void *first_elem = opaque + field->offset;
             int i;
             uint64_t n_elems = vmstate_n_elems(opaque, field);
-            uint64_t size = vmstate_size(opaque, field);
+            uint64_t size;
             JSONWriter *vmdesc_loop = vmdesc;
             bool is_prev_null = false;
+
             /*
              * When this is enabled, it means we will always push a ptr
              * marker first for each element saying if it's populated.
@@ -705,6 +717,10 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
             bool use_dynamic_array =
                 field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;

+            if (!vmstate_size(opaque, field, &size, errp)) {
+                return false;
+            }
+
             trace_vmstate_save_state_loop(vmsd->name, field->name, n_elems);
             if (field->flags & VMS_POINTER) {
                 first_elem = *(void **)first_elem;