[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