Thread

Commits

  1. Save/restore SPI's global variables in SPI_connect() and SPI_finish().

  1. *_to_xml() should copy SPI_processed/SPI_tuptable

    Chapman Flack <chap@anastigmatix.net> — 2018-09-05T21:09:59Z

    I encountered this in 9.5 and haven't yet written a reproducer
    for 10 or 11, but the code in question looks the same in master.
    
    
    In xml.c, query_to_xml_internal() contains a loop that refers
    to SPI_processed every iteration:
    
    for (i = 0; i < SPI_processed; i++)
        SPI_sql_row_to_xmlelement(i, result, tablename, nulls,
                                  tableforest, targetns, top_level);
    
    likewise, that function it is calling contains a loop that
    repeatedly dereferences SPI_tuptable:
    
    for (i = 1; i <= SPI_tuptable->tupdesc->natts; i++)
    ...
    ... map_sql_value_to_xml_value(colval,
                                   SPI_gettypeid(SPI_tuptable->tupdesc, ...
    
    
    and map_sql_value_to_xml_value can use OidOutputFunctionCall. If that
    attribute's data type has an output function that uses SPI, the next
    iteration here dereferences a null SPI_tuptable.
    
    The root of the problem does not seem to be an output function that
    uses SPI; indeed, the chance that one might do so seems to have been
    the original motivation for OutputFunctionCall().
    
    The essence of the problem here seems to be simply that these routines
    in xml.c are not making local-variable copies of SPI_processed and
    SPI_tuptable, as the docs for SPI_execute recommend.
    
    Would it be worth also expanding that note in the docs? It currently
    says:
    
    ---
    All SPI query-execution functions set both SPI_processed and
    SPI_tuptable .... Save these two global variables into local procedure
    variables if you need to access the result table of SPI_execute or
    another query-execution function across later calls.
    ---
    
    but might it bear emphasis that those later calls can happen
    indirectly from things that you call (e.g. output functions)?
    Those will be nested in their own SPI_connect/SPI_finish, which
    a reasonable person might guess would save/restore those globals,
    but they don't; SPI_finish merely clears them, on the assumption
    that you've got your local copies if you need them.
    
    -Chap
    
    
    
  2. Re: *_to_xml() should copy SPI_processed/SPI_tuptable

    Tom Lane <tgl@sss.pgh.pa.us> — 2018-09-05T22:07:25Z

    Chapman Flack <chap@anastigmatix.net> writes:
    > In xml.c, query_to_xml_internal() contains a loop that refers
    > to SPI_processed every iteration:
    > for (i = 0; i < SPI_processed; i++)
    >     SPI_sql_row_to_xmlelement(i, result, tablename, nulls,
    >                               tableforest, targetns, top_level);
    > likewise, that function it is calling contains a loop that
    > repeatedly dereferences SPI_tuptable:
    > for (i = 1; i <= SPI_tuptable->tupdesc->natts; i++)
    > ...
    > ... map_sql_value_to_xml_value(colval,
    >                                SPI_gettypeid(SPI_tuptable->tupdesc, ...
    > and map_sql_value_to_xml_value can use OidOutputFunctionCall
    > [ which could call arbitrary code that clobbers SPI_tuptable ]
    
    Ugh.  I wonder how many other places have latent issues of the same kind.
    
    > The essence of the problem here seems to be simply that these routines
    > in xml.c are not making local-variable copies of SPI_processed and
    > SPI_tuptable, as the docs for SPI_execute recommend.
    
    I think that's blaming the messenger TBH.  The *real* problem is the
    damfool decision to use global variables for this in the first place.
    Should we change that somehow?
    
    Really the right API would have entailed something like
    
    int SPI_execute(const char *src, bool read_only, long tcount,
                    SPITupleTable **tuptable, uint64 *processed);
    
    ... and I guess we'd have to think about SPI_lastoid too, so maybe
    returning a struct would be smarter than a pointer-per-value.
    
    So some possible alternatives are:
    
    * Just do it.  Probably breaks too much 3rd-party code to be very
    popular, though.
    
    * Invent SPI_execute_r() and so on with APIs as above, and deprecate
    the old functions, but don't remove 'em right away.  Grotty, and it
    does little to fix any latent problems that may exist outside core.
    
    * Replace SPI_tuptable et al with macros that access fields in the
    current SPI stack level (similar to the way that, eg, errno works
    on most modern platforms).  This seems do-able, if a bit grotty.
    
    None of this is very back-patchable, so I guess we need a one-off
    backpatched change for query_to_xml_internal and anyplace else that
    looks to be at serious risk.
    
    			regards, tom lane
    
    
    
  3. Re: *_to_xml() should copy SPI_processed/SPI_tuptable

    Chapman Flack <chap@anastigmatix.net> — 2018-09-05T22:38:01Z

    On 09/05/18 18:07, Tom Lane wrote:
    
    > * Replace SPI_tuptable et al with macros that access fields in the
    > current SPI stack level (similar to the way that, eg, errno works
    > on most modern platforms).  This seems do-able, if a bit grotty.
    
    It would mean they'd have to *be* in the stack frame, where they
    currently aren't; they're ignored by SPI_connect and just zeroed
    by SPI_finish, on the grounds that they're transient and you've
    copied them if you care.
    
    Another alternative might be to have SPI_connect save them and
    SPI_finish put them back, which leaves you just responsible for
    reasoning about your own code. You'd still be expected to save them
    across your own uses of other SPI calls, but no longer exposed to
    spooky action at a distance from nested uses of SPI in stuff you call.
    
    -Chap
    
    
    
  4. Re: *_to_xml() should copy SPI_processed/SPI_tuptable

    Tom Lane <tgl@sss.pgh.pa.us> — 2018-09-05T22:54:43Z

    Chapman Flack <chap@anastigmatix.net> writes:
    > On 09/05/18 18:07, Tom Lane wrote:
    >> * Replace SPI_tuptable et al with macros that access fields in the
    >> current SPI stack level (similar to the way that, eg, errno works
    >> on most modern platforms).  This seems do-able, if a bit grotty.
    
    > It would mean they'd have to *be* in the stack frame, where they
    > currently aren't;
    
    Right, I was assuming that was obvious ...
    
    > Another alternative might be to have SPI_connect save them and
    > SPI_finish put them back, which leaves you just responsible for
    > reasoning about your own code. You'd still be expected to save them
    > across your own uses of other SPI calls, but no longer exposed to
    > spooky action at a distance from nested uses of SPI in stuff you call.
    
    Hmm.  I'd thought about that briefly and concluded that it didn't offer
    a full fix, but on reflection it's not clear why it'd be any less of
    a fix than the macroized-SPI_tuptable approach.  You end up with
    per-SPI-stack-level storage either way, and while that isn't perfect
    it does go a long way, as you say.  More, this has the huge advantage
    of being back-patchable, because there'd be no API/ABI change.
    
    			regards, tom lane
    
    
    
  5. Re: *_to_xml() should copy SPI_processed/SPI_tuptable

    Tom Lane <tgl@sss.pgh.pa.us> — 2018-09-06T16:25:34Z

    I wrote:
    > Chapman Flack <chap@anastigmatix.net> writes:
    >> Another alternative might be to have SPI_connect save them and
    >> SPI_finish put them back, which leaves you just responsible for
    >> reasoning about your own code. You'd still be expected to save them
    >> across your own uses of other SPI calls, but no longer exposed to
    >> spooky action at a distance from nested uses of SPI in stuff you call.
    
    > Hmm.  I'd thought about that briefly and concluded that it didn't offer
    > a full fix, but on reflection it's not clear why it'd be any less of
    > a fix than the macroized-SPI_tuptable approach.  You end up with
    > per-SPI-stack-level storage either way, and while that isn't perfect
    > it does go a long way, as you say.  More, this has the huge advantage
    > of being back-patchable, because there'd be no API/ABI change.
    
    Here's a proposed patch along that line.  I included SPI_result (SPI's
    equivalent of errno) in the set of saved-and-restored variables,
    so that all the exposed SPI globals behave the same.
    
    Also, in further service of providing insulation between SPI nesting
    levels, I thought it'd be a good idea for SPI_connect() to reset the
    globals to zero/NULL after saving them.  This ensures that a nesting
    level can't accidentally be affected by the state of an outer level.
    It's barely possible that there's somebody out there who's *intentionally*
    accessing state from a calling SPI level, but that seems like it'd be
    pretty dangerous programming practice.  Still, maybe there's an argument
    for omitting that hunk in released branches.
    
    Comments?
    
    			regards, tom lane
    
    
  6. Re: *_to_xml() should copy SPI_processed/SPI_tuptable

    Peter Eisentraut <peter.eisentraut@2ndquadrant.com> — 2018-09-06T20:23:03Z

    On 06/09/2018 18:25, Tom Lane wrote:
    > Here's a proposed patch along that line.  I included SPI_result (SPI's
    > equivalent of errno) in the set of saved-and-restored variables,
    > so that all the exposed SPI globals behave the same.
    > 
    > Also, in further service of providing insulation between SPI nesting
    > levels, I thought it'd be a good idea for SPI_connect() to reset the
    > globals to zero/NULL after saving them.  This ensures that a nesting
    > level can't accidentally be affected by the state of an outer level.
    > It's barely possible that there's somebody out there who's *intentionally*
    > accessing state from a calling SPI level, but that seems like it'd be
    > pretty dangerous programming practice.  Still, maybe there's an argument
    > for omitting that hunk in released branches.
    
    These look like good changes.  I'm not sure about backpatching them, but
    as you say the chances that someone wants to do this intentionally are low.
    
    -- 
    Peter Eisentraut              http://www.2ndQuadrant.com/
    PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
    
    
    
  7. Re: *_to_xml() should copy SPI_processed/SPI_tuptable

    Tom Lane <tgl@sss.pgh.pa.us> — 2018-09-06T20:40:00Z

    Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
    > On 06/09/2018 18:25, Tom Lane wrote:
    >> Also, in further service of providing insulation between SPI nesting
    >> levels, I thought it'd be a good idea for SPI_connect() to reset the
    >> globals to zero/NULL after saving them.  This ensures that a nesting
    >> level can't accidentally be affected by the state of an outer level.
    >> It's barely possible that there's somebody out there who's *intentionally*
    >> accessing state from a calling SPI level, but that seems like it'd be
    >> pretty dangerous programming practice.  Still, maybe there's an argument
    >> for omitting that hunk in released branches.
    
    > These look like good changes.  I'm not sure about backpatching them, but
    > as you say the chances that someone wants to do this intentionally are low.
    
    Yeah.  I'd put a higher probability on the idea that this will fix
    somebody's latent bug in code that's never been tested inside an
    outer level of SPI call.  It would, for example, work to rely on
    SPI_tuptable being initially NULL, as long as you hadn't tried it
    inside an outer call.
    
    			regards, tom lane
    
    
    
  8. Re: *_to_xml() should copy SPI_processed/SPI_tuptable

    Andrew Gierth <andrew@tao11.riddles.org.uk> — 2018-09-06T20:47:40Z

    >>>>> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
    
     Tom> Also, in further service of providing insulation between SPI
     Tom> nesting levels, I thought it'd be a good idea for SPI_connect() to
     Tom> reset the globals to zero/NULL after saving them. This ensures
     Tom> that a nesting level can't accidentally be affected by the state
     Tom> of an outer level. It's barely possible that there's somebody out
     Tom> there who's *intentionally* accessing state from a calling SPI
     Tom> level, but that seems like it'd be pretty dangerous programming
     Tom> practice. Still, maybe there's an argument for omitting that hunk
     Tom> in released branches.
    
     Tom> Comments?
    
    +1 to backpatching it all, including the initialization in SPI_connect.
    
    -- 
    Andrew (irc:RhodiumToad)
    
    
    
  9. Re: *_to_xml() should copy SPI_processed/SPI_tuptable

    Tom Lane <tgl@sss.pgh.pa.us> — 2018-09-08T00:10:52Z

    Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
    > "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
    >  Tom> Comments?
    
    > +1 to backpatching it all, including the initialization in SPI_connect.
    
    Done.
    
    			regards, tom lane