Em sex., 9 de out. de 2020 às 18:02, Stephen Frost <sfr...@snowman.net> escreveu:
> Greetings, > > * Ranier Vilela (ranier...@gmail.com) wrote: > > Em sex., 9 de out. de 2020 às 14:05, Tomas Vondra < > > tomas.von...@2ndquadrant.com> escreveu: > > > > > On Fri, Oct 09, 2020 at 12:24:16PM -0300, Ranier Vilela wrote: > > > >I think that TupIsNull macro is no longer appropriate, to protect > > > >ExecCopySlot. > > > > > > > >See at tuptable.h: > > > >#define TupIsNull(slot) \ > > > >((slot) == NULL || TTS_EMPTY(slot)) > > > > > > > >If var node->group_pivot is NULL, ExecCopySlot will > > > >dereference a null pointer (first arg). > > [...] > > > The trap is not on the second part of expression. Is in the first. > > If the pointer is NULL, ExecCopySlot will be called. > > Yeah, that's interesting, and this isn't the only place there's a risk > of that happening, in doing a bit of review of TupIsNull() callers: > > src/backend/executor/nodeGroup.c: > > if (TupIsNull(firsttupleslot)) > { > outerslot = ExecProcNode(outerPlanState(node)); > if (TupIsNull(outerslot)) > { > /* empty input, so return nothing */ > node->grp_done = true; > return NULL; > } > /* Copy tuple into firsttupleslot */ > ExecCopySlot(firsttupleslot, outerslot); > > Seems that 'firsttupleslot' could possibly be a NULL pointer at this > point? > > src/backend/executor/nodeWindowAgg.c: > > /* Fetch next row if we didn't already */ > if (TupIsNull(agg_row_slot)) > { > if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto, > agg_row_slot)) > break; /* must be end of partition */ > } > > If agg_row_slot ends up being an actual NULL pointer, this looks likely > to end up resulting in a crash too. > > /* > * If this is the very first partition, we need to fetch the first > input > * row to store in first_part_slot. > */ > if (TupIsNull(winstate->first_part_slot)) > { > TupleTableSlot *outerslot = ExecProcNode(outerPlan); > > if (!TupIsNull(outerslot)) > ExecCopySlot(winstate->first_part_slot, outerslot); > else > { > /* outer plan is empty, so we have nothing to do */ > winstate->partition_spooled = true; > winstate->more_partitions = false; > return; > } > } > > This seems like another risky case, since we don't check that > winstate->first_part_slot is a non-NULL pointer. > > if (winstate->frameheadpos == 0 && > TupIsNull(winstate->framehead_slot)) > { > /* fetch first row into framehead_slot, if we didn't > already */ > if (!tuplestore_gettupleslot(winstate->buffer, true, true, > winstate->framehead_slot)) > elog(ERROR, "unexpected end of tuplestore"); > } > > There's a few of these 'framehead_slot' cases, and then some with > 'frametail_slot', all a similar pattern to above. > > > For convenience, I will reproduce it: > > static inline TupleTableSlot * > > ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot) > > { > > Assert(!TTS_EMPTY(srcslot)); > > AssertArg(srcslot != dstslot); > > > > dstslot->tts_ops->copyslot(dstslot, srcslot); > > > > return dstslot; > > } > > > > The second arg is not empty? Yes. > > The second arg is different from the first arg (NULL)? Yes. > > > > dstslot->tts_ops->copyslot(dstslot, srcslot); // dereference dstslot > (which > > is NULL) > > Right, just to try and clarify further, the issue here is with this code: > > if (TupIsNull(node->group_pivot)) > ExecCopySlot(node->group_pivot, node->transfer_tuple); > > With TupIsNull defined as: > > ((slot) == NULL || TTS_EMPTY(slot)) > > That means we get: > > if ((node->group_pivot) == NULL || TTS_EMPTY(node->group_pivot)) > ExecCopySlot(node->group_pivot, node->transfer_tuple); > > Which means that if we reach this point with node->group_pivot as NULL, > then we'll pass that to ExecCopySlot() and eventually end up > dereferencing it and crashing. > > I haven't tried to run back farther up to see if it's possible that > there's other checks which prevent node->group_pivot (and the other > cases) from actually being a NULL pointer by the time we reach this > code, but I agree that it seems to be a bit concerning to have the code > written this way- TupIsNull() allows the pointer *itself* to be NULL and > callers of it need to realize that (if nothing else by at least > commenting that there's other checks in place to make sure that it can't > end up actually being a NULL pointer when we're passing it to some other > function that'll dereference it). > > As a side-note, this kind of further analysis and looking for other, > similar, cases that might be problematic is really helpful and important > to do whenever you come across a case like this, and will also lend a > bit more validation that this is really an issue and something we need > to look at and not a one-off mistake (which, as much as we'd like to > think we never make mistakes, isn't typically the case...). > Several places. TupIsNull it looks like a minefield... regards, Ranier Vilela