[Libguestfs] [libnbd PATCH v2 04/12] api: Add nbd_set_request_meta_context()

Eric Blake eblake at redhat.com
Tue Sep 6 09:38:30 UTC 2022


On Mon, Sep 05, 2022 at 04:35:28PM +0200, Laszlo Ersek wrote:
> On 08/31/22 16:39, Eric Blake wrote:
> > Add a new control knob nbd_set_request_meta_context(), modeled after
> > the existing nbd_set_request_structured_replies(), to make it possible
> > to skip the NBD_OPT_SET_META_CONTEXT half of the two-command sequence
> > currently performed in nbd_opt_go() and nbd_opt_info().  Also add a
> > counterpart nbd_get_request_meta_context() for symmetry.
> >
...
> > +++ b/generator/states-newstyle-opt-meta-context.c
> > @@ -29,9 +29,6 @@ STATE_MACHINE {
> >     */
> >    assert (h->gflags & LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE);
> >    nbd_internal_reset_size_and_flags (h);
> > -  for (i = 0; i < h->meta_contexts.len; ++i)
> > -    free (h->meta_contexts.ptr[i].name);
> > -  meta_vector_reset (&h->meta_contexts);
> >    if (h->opt_current == NBD_OPT_LIST_META_CONTEXT) {
> >      assert (h->opt_mode);
> >      assert (CALLBACK_IS_NOT_NULL (h->opt_cb.fn.context));
> > @@ -40,13 +37,19 @@ STATE_MACHINE {
> >    else {
> >      assert (CALLBACK_IS_NULL (h->opt_cb.fn.context));
> >      opt = NBD_OPT_SET_META_CONTEXT;
> > -    if (!h->structured_replies || h->request_meta_contexts.len == 0) {
> > -      SET_NEXT_STATE (%^OPT_GO.START);
> > -      return 0;
> > +    if (h->request_meta) {
> > +      for (i = 0; i < h->meta_contexts.len; ++i)
> > +        free (h->meta_contexts.ptr[i].name);
> > +      meta_vector_reset (&h->meta_contexts);
> > +      h->meta_valid = false;
> >      }
> >    }
> > -
> > -  assert (!h->meta_valid);
> > +  if (opt != h->opt_current &&
> > +      (!h->request_meta || !h->structured_replies ||
> > +       h->request_meta_contexts.len == 0)) {
> > +    SET_NEXT_STATE (%^OPT_GO.START);
> > +    return 0;
> > +  }
> >
> >    /* Calculate the length of the option request data. */
> >    len = 4 /* exportname len */ + strlen (h->export_name) + 4 /* nr queries */;
> 
> I'm confused about moving the transition to GO.START out of its current
> enclosing block, and then compensating (?) for that unnesting with the
> additional (opt != h->opt_current) restriction.
> 
> More precisely, I don't understand how (opt != h->opt_current) could
> ever evaluate to 1.

Good question.  Thanks to tweaks I made to 2/12 before pushing that
one (55b09667), in v3 this patch 4/12 will be starting with a better
comment pre-patch:

  /* This state group is reached from:
   * h->opt_mode == false (h->opt_current == 0):
   *   nbd_connect_*()
   *     -> conditionally use SET, next state OPT_GO for NBD_OPT_GO
   * h->opt_mode == true (h->opt_current matches calling API):
   *   nbd_opt_info()
   *     -> conditionally use SET, next state OPT_GO for NBD_OPT_INFO
   *   nbd_opt_go()
   *     -> conditionally use SET, next state OPT_GO for NBD_OPT_GO
   *   nbd_opt_list_meta_context()
   *     -> conditionally use LIST, next state NEGOTIATING
   *
   * For now, we start by unconditionally clearing h->exportsize and friends,
   * as well as h->meta_contexts and h->meta_valid.
   * If SET is conditional, we skip it if structured replies were
   * not negotiated, or if there were no contexts to request.
   * SET then manipulates h->meta_contexts, and sets h->meta_valid on success.
   * If OPT_GO is later successful, it populates h->exportsize and friends,
   * and also sets h->meta_valid if we skipped SET here.
   * LIST is conditional, skipped if structured replies were not negotiated.
   * There is a callback if and only if the command is LIST.
   */


> 
> Here's the code, with this patch applied:
> 
> >   if (h->opt_current == NBD_OPT_LIST_META_CONTEXT) {
> >     assert (h->opt_mode);
> >     assert (CALLBACK_IS_NOT_NULL (h->opt_cb.fn.context));
> >     opt = h->opt_current;
> >   }
> >   else {
> >     assert (CALLBACK_IS_NULL (h->opt_cb.fn.context));
> >     opt = NBD_OPT_SET_META_CONTEXT;

And this is the key point - here, we are slamming opt to
SET_META_CONTEXT, while leaving h->opt_current as 0 (nbd_connect_*),
GO, or INFO.  h->opt_current won't be SET_META_CONTEXT until later in
the series when the nbd_opt_set_meta_context() API is added; at which
point, the v3 patch will update the comment to match.

> >     if (h->request_meta) {
> >       for (i = 0; i < h->meta_contexts.len; ++i)
> >         free (h->meta_contexts.ptr[i].name);
> >       meta_vector_reset (&h->meta_contexts);
> >       h->meta_valid = false;
> >     }
> >   }
> >   if (opt != h->opt_current &&
> >       (!h->request_meta || !h->structured_replies ||
> >        h->request_meta_contexts.len == 0)) {
> >     SET_NEXT_STATE (%^OPT_GO.START);
> >     return 0;
> >   }
> 
> My understanding is that we enter this code with either one of two
> possible "h->opt_current" values: NBD_OPT_LIST_META_CONTEXT,
> NBD_OPT_SET_META_CONTEXT.

That's where the confusion stemmed from; without the comment
explaining the 4 possible values of h->opt_current on entry (5 after
nbd_opt_set_meta_context() is introduced later), it's harder to see
why it made more sense to isolate the decision to short-circuit over
to OPT_GO (what the comment calls "conditional SET") separately from
the decision of whether to reset h->meta_contexts (we want the reset
for SET but not for LIST, even if we don't end up sending SET over the
wire).

> 
> - In the first case, when "h->opt_current" equals LIST, "opt" at the end
>   of the first branch will be LIST (via assignment from
>   "h->opt_current"), that is, equal "h->opt_current".
> 
> - In the second case (when "h->opt_current" differs from LIST, hence,
>   when it equals SET), "opt" at the end of the second branch will be SET
>   (after having directly been assigned SET) -- that is, "opt" will equal
>   "h->opt_current" *again*.
> 
> That suggests the (opt != h->opt_current) condition will never evaluate
> to "true". (And it also suggests that there is no good reason for the
> "opt" local variable to exist...)
> 
> Now, in case I'm wrong, and we enter this code with a *third*
> "h->opt_current" value, then we have:
> 
> - opt = SET, and
> 
> - "h->opt_current" differing from both SET and LIST.
> 
> Then we indeed transition to GO.START -- but then, wouldn't the
> following hunk be *simpler*?

For this patch, the following hunk would indeed be less convoluted,
but later in the series when nbd_opt_set_meta_context() is added, I
really did need to rearrange the control flow; whether doing it in
this patch makes the most sense, or deferring the control flow
rearrangement to later would have been wiser, I don't know, but we'll
see if the comments in v3 make it easier to follow.

> 
> > diff --git a/generator/states-newstyle-opt-meta-context.c b/generator/states-newstyle-opt-meta-context.c
> > index 5c654543a228..1e31eedd4cef 100644
> > --- a/generator/states-newstyle-opt-meta-context.c
> > +++ b/generator/states-newstyle-opt-meta-context.c
> > @@ -40,14 +40,21 @@ STATE_MACHINE {
> >    else {
> >      assert (CALLBACK_IS_NULL (h->opt_cb.fn.context));
> >      opt = NBD_OPT_SET_META_CONTEXT;
> > -    if (!h->structured_replies || h->request_meta_contexts.len == 0) {
> > +
> > +    if (h->request_meta) {
> > +      for (i = 0; i < h->meta_contexts.len; ++i)
> > +        free (h->meta_contexts.ptr[i].name);
> > +      meta_vector_reset (&h->meta_contexts);
> > +      h->meta_valid = false;
> > +     }
> > +
> > +    if (!h->structured_replies ||
> > +        !h->request_meta || h->request_meta_contexts.len == 0) {
> >        SET_NEXT_STATE (%^OPT_GO.START);
> >        return 0;
> >      }
> >    }
> >
> > -  assert (!h->meta_valid);
> > -
> >    /* Calculate the length of the option request data. */
> >    len = 4 /* exportname len */ + strlen (h->export_name) + 4 /* nr queries */;
> >    for (i = 0; i < h->request_meta_contexts.len; ++i)
> 
> ... FWIW, I think the formulation I'm proposing is correct (unlike the
> patch) even in case we can only enter this section with SET and LIST!

As stated above, in this patch, we can only enter with h->opt_current
== 0, LIST, GO, or INFO.  And for what it's worth, in an earlier
iteration of my patch, my logic did look more like your proposed
alternative hunk.

> 
> Back to the patch:
> 
> On 08/31/22 16:39, Eric Blake wrote:
> > diff --git a/lib/flags.c b/lib/flags.c
> > index 91efc1a..c8c68ea 100644
> > --- a/lib/flags.c
> > +++ b/lib/flags.c
> > @@ -37,7 +37,6 @@ nbd_internal_reset_size_and_flags (struct nbd_handle *h)
> >
> >    h->exportsize = 0;
> >    h->eflags = 0;
> > -  h->meta_valid = false;
> >    h->block_minimum = 0;
> >    h->block_preferred = 0;
> >    h->block_maximum = 0;
> 
> Shouldn't we clear "h->meta_valid" in nbd_close() as well?
> 
> nbd_close() calls nbd_internal_reset_size_and_flags(), and clears the
> metacontext vector.

Hmm, good question.  It is undefined to call nbd_FOO(nbd) if you have
previously called nbd_close(nbd); but to minimize the risk of
nbd_can_meta_context() doing something weird if the UAF hasn't already
hit the worse case of something else clobbering the memory, then yes,
explicitly clearing h->meta_valid during close is probably wise.  I'll
add that into v3.
> > +++ b/tests/opt-set-meta
> > @@ -0,0 +1,210 @@
> > +#! /bin/sh
> > +
> > +# opt-set-meta - temporary wrapper script for .libs/opt-set-meta
> > +# Generated by libtool (GNU libtool) 2.4.6
> 
> ... Ugh, I initially missed this "generated by" line, and wondered (a)
> why the script read like line noise, (b) why you spent time on ZSH and
> Tru64 (!!!) compatibility, (c) what the script *actually did*.
> 
> Did you git-add this (generated) script to the commit by mistake,
> perhaps?

Yep, and I had already mentioned it in my reply to 0/12; but by
failing to also mention it in reply to this message, I've cost you
some review time; sorry about that.  At any rate, I've fixed that
already in my tree for v3 to quit adding libtool-generated files.  I
don't know if it would have been any easier had it been an actual
binary instead of a libtool script (would git have given me the
one-line 'binary diff' message, or blown up the email into trying to
represent the binary?)

> 
> Looks OK to me otherwise.
> 
> Thanks,
> Laszlo
> 

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


More information about the Libguestfs mailing list