[Libguestfs] [libnbd PATCH v3 01/22] block_status: Refactor array storage

Eric Blake eblake at redhat.com
Fri May 26 13:19:14 UTC 2023


On Thu, May 25, 2023 at 06:30:37PM +0200, Laszlo Ersek wrote:
> On 5/25/23 15:00, Eric Blake wrote:
> > For 32-bit block status, we were able to cheat and use an array with
> > an odd number of elements, with array[0] holding the context id, and
> > passing &array[1] to the user's callback.  But once we have 64-bit
> > extents, we can no longer abuse array element 0 like that, for two
> > reasons: 64-bit extents contain uint64_t which might not be
> > alignment-compatible with an array of uint32_t on all architectures,
> > and the new NBD_REPLY_TYPE_BLOCK_STATUS_EXT adds an additional count
> > field before the array.
> >
> > +++ b/generator/states-reply-structured.c
> > @@ -126,19 +126,10 @@  REPLY.STRUCTURED_REPLY.CHECK:
> >          length < 12 || ((length-4) & 7) != 0)
> 
> This is important (the context doesn't show it in full): we're under
> NBD_REPLY_TYPE_BLOCK_STATUS here (nested under
> REPLY.STRUCTURED_REPLY.CHECK), and we enforce that
> 
>   length = be32toh (h->sbuf.sr.structured_reply.length);
> 
> contains the context_id (4 bytes), plus a positive integral number of
> block descriptor structures (8 bytes each).

And when 64-bit replies are added, there's a counterpart that contains
a header (4+4 bytes) and then a positive integral number of block
descriptors (16 bytes each).

> > @@ -445,15 +468,16 @@  REPLY.STRUCTURED_REPLY.RECV_BS_ENTRIES:
> >      assert (h->bs_entries);
> >      assert (length >= 12);
> >      assert (h->meta_valid);
> > +    count = (length - sizeof h->sbuf.sr.payload.bs_hdr) / sizeof *h->bs_entries;
> 
> I have a slight problem with the pre-patch code here. We keep the
> existent assertions (good), but I think the pre-patch RECV_BS_ENTRIES
> code misses an assertion. Namely, after the size check (i.e., 12+
> bytes), the pre-patch code should have said
> 
>       assert ((length-4) & 7) == 0);
> 
> emphasizing the explicit check under REPLY.STRUCTURED_REPLY.CHECK.
> 
> The pre-patch code relies on this (a) silently by expecting (length/4)
> to be an integer (in the mathematical sense), and (b) very silently by
> expecting (length/4) to be an *odd* integer >= 3.

Likewise, once 64-bit replies are added, we will depend on (h->len/8)
being an *odd* integer >= 3, but additionally that the count in the
header match the (h->len-8)/16 computation.

> 
> Here's what I suggest as an update for this patch (to be squashed):
> 
> diff --git a/generator/states-reply-structured.c b/generator/states-reply-structured.c
> index da1e46929cd0..6cd4a49baa26 100644
> --- a/generator/states-reply-structured.c
> +++ b/generator/states-reply-structured.c
> @@ -43,6 +43,20 @@ structured_reply_in_bounds (uint64_t offset, uint32_t length,
>    return true;
>  }
> 
> +static bool
> +bs_reply_length_ok (uint32_t length)
> +{
> +  if (length < (sizeof (struct nbd_structured_reply_block_status_hdr) +
> +                sizeof (struct nbd_block_descriptor)))
> +    return false;
> +
> +  length -= sizeof (struct nbd_structured_reply_block_status_hdr);
> +  if (length % sizeof (struct nbd_block_descriptor) != 0)
> +    return false;
> +
> +  return true;
> +}

This is only valid for the 32-bit reply; but could easily be
generalized to cover the 64-bit reply as well.  I definitely like
wrapping the computation behind a helper function, so that we don't
have to open-code repeat it in multiple spots.

I could even go with doing this sort of cleanup as its own
prerequisite patch instead of squashing it in with this one,
because....

> +
>  STATE_MACHINE {
>   REPLY.STRUCTURED_REPLY.START:
>    /* We've only read the simple_reply.  The structured_reply is longer,
> @@ -123,7 +137,7 @@  REPLY.STRUCTURED_REPLY.CHECK:
> 
>    case NBD_REPLY_TYPE_BLOCK_STATUS:
>      if (cmd->type != NBD_CMD_BLOCK_STATUS ||
> -        length < 12 || ((length-4) & 7) != 0)
> +        !bs_reply_length_ok (length))
>        goto resync;

...that does indeed look easier to read.

> @@ -466,8 +480,11 @@  REPLY.STRUCTURED_REPLY.RECV_BS_ENTRIES:
>      assert (cmd->type == NBD_CMD_BLOCK_STATUS);
>      assert (CALLBACK_IS_NOT_NULL (cmd->cb.fn.extent));
>      assert (h->bs_entries);
> -    assert (length >= 12);
> +    assert (bs_reply_length_ok (length));
>      assert (h->meta_valid);
> +    STATIC_ASSERT ((sizeof (struct nbd_block_descriptor) %
> +                    sizeof *h->bs_entries) == 0,
> +                   _block_desc_is_multiple_of_bs_entry);
>      count = (length - sizeof h->sbuf.sr.payload.bs_hdr) / sizeof *h->bs_entries;

That static assertion also makes sense.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org


More information about the Libguestfs mailing list