"Michael S. Tsirkin" <[email protected]> writes:

> On Mon, Jul 27, 2026 at 04:29:55PM -0400, Peter Xu wrote:
>> On Mon, Jul 27, 2026 at 03:06:38PM -0400, Michael S. Tsirkin wrote:
>> > On Mon, Jul 27, 2026 at 08:49:44AM -0400, Peter Xu wrote:
>> > > On Mon, Jul 27, 2026 at 04:53:15AM -0400, Michael S. Tsirkin wrote:
>> > > > On Sun, Jul 26, 2026 at 08:31:57PM -0400, Peter Xu wrote:
>> > > > > 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..
>> > > 
>> > > > 
>> > > > > 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
>> > > 
>> > > 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
>

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)))


>> 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.
>
>
>
>
>> > 
>> > > 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..
>> > 
>> > Frankly 2G is crazy too. I see little reason to focus specifically on >2G.
>> 
>> Yeah..
>> 
>> > 
>> > 
>> > > 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.
>> 
>> 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.

> 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). 

>> Thanks,
>> 
>> > 
>> > 
>> > > > >     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....@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
>> > > > > 
>> > > > > 
>> > > > 
>> > > 
>> > > -- 
>> > > Peter Xu
>> > 
>> 
>> -- 
>> Peter Xu

Reply via email to