Skip to content

Commit a210be7

Browse files
committed
Fix dangling-pointer problem in before-row update trigger processing.
ExecUpdate checked for whether ExecBRUpdateTriggers had returned a new tuple value by seeing if the returned tuple was pointer-equal to the old one. But the "old one" was in estate->es_junkFilter's result slot, which would be scribbled on if we had done an EvalPlanQual update in response to a concurrent update of the target tuple; therefore we were comparing a dangling pointer to a live one. Given the right set of circumstances we could get a false match, resulting in not forcing the tuple to be stored in the slot we thought it was stored in. In the case reported by Maxim Boguk in bug #5798, this led to "cannot extract system attribute from virtual tuple" failures when trying to do "RETURNING ctid". I believe there is a very-low-probability chance of more serious errors, such as generating incorrect index entries based on the original rather than the trigger-modified version of the row. In HEAD, change all of ExecBRInsertTriggers, ExecIRInsertTriggers, ExecBRUpdateTriggers, and ExecIRUpdateTriggers so that they continue to have similar APIs. In the back branches I just changed ExecBRUpdateTriggers, since there is no bug in the ExecBRInsertTriggers case.
1 parent fee7802 commit a210be7

File tree

4 files changed

+161
-144
lines changed

4 files changed

+161
-144
lines changed

src/backend/commands/copy.c

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,7 +1836,7 @@ CopyFrom(CopyState cstate)
18361836
ResultRelInfo *resultRelInfo;
18371837
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
18381838
ExprContext *econtext;
1839-
TupleTableSlot *slot;
1839+
TupleTableSlot *myslot;
18401840
MemoryContext oldcontext = CurrentMemoryContext;
18411841
ErrorContextCallback errcontext;
18421842
CommandId mycid = GetCurrentCommandId(true);
@@ -1932,8 +1932,10 @@ CopyFrom(CopyState cstate)
19321932
estate->es_result_relation_info = resultRelInfo;
19331933

19341934
/* Set up a tuple slot too */
1935-
slot = ExecInitExtraTupleSlot(estate);
1936-
ExecSetSlotDescriptor(slot, tupDesc);
1935+
myslot = ExecInitExtraTupleSlot(estate);
1936+
ExecSetSlotDescriptor(myslot, tupDesc);
1937+
/* Triggers might need a slot as well */
1938+
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
19371939

19381940
/* Prepare to catch AFTER triggers. */
19391941
AfterTriggerBeginQuery();
@@ -1960,6 +1962,7 @@ CopyFrom(CopyState cstate)
19601962

19611963
for (;;)
19621964
{
1965+
TupleTableSlot *slot;
19631966
bool skip_tuple;
19641967
Oid loaded_oid = InvalidOid;
19651968

@@ -1983,32 +1986,28 @@ CopyFrom(CopyState cstate)
19831986
/* Triggers and stuff need to be invoked in query context. */
19841987
MemoryContextSwitchTo(oldcontext);
19851988

1989+
/* Place tuple in tuple slot --- but slot shouldn't free it */
1990+
slot = myslot;
1991+
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
1992+
19861993
skip_tuple = false;
19871994

19881995
/* BEFORE ROW INSERT Triggers */
19891996
if (resultRelInfo->ri_TrigDesc &&
19901997
resultRelInfo->ri_TrigDesc->trig_insert_before_row)
19911998
{
1992-
HeapTuple newtuple;
1993-
1994-
newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple);
1999+
slot = ExecBRInsertTriggers(estate, resultRelInfo, slot);
19952000

1996-
if (newtuple == NULL) /* "do nothing" */
2001+
if (slot == NULL) /* "do nothing" */
19972002
skip_tuple = true;
1998-
else if (newtuple != tuple) /* modified by Trigger(s) */
1999-
{
2000-
heap_freetuple(tuple);
2001-
tuple = newtuple;
2002-
}
2003+
else /* trigger might have changed tuple */
2004+
tuple = ExecMaterializeSlot(slot);
20032005
}
20042006

20052007
if (!skip_tuple)
20062008
{
20072009
List *recheckIndexes = NIL;
20082010

2009-
/* Place tuple in tuple slot */
2010-
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
2011-
20122011
/* Check the constraints of the tuple */
20132012
if (cstate->rel->rd_att->constr)
20142013
ExecConstraints(resultRelInfo, slot, estate);

src/backend/commands/trigger.c

Lines changed: 120 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1909,12 +1909,13 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
19091909
false, NULL, NULL, NIL, NULL);
19101910
}
19111911

1912-
HeapTuple
1912+
TupleTableSlot *
19131913
ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
1914-
HeapTuple trigtuple)
1914+
TupleTableSlot *slot)
19151915
{
19161916
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
1917-
HeapTuple newtuple = trigtuple;
1917+
HeapTuple slottuple = ExecMaterializeSlot(slot);
1918+
HeapTuple newtuple = slottuple;
19181919
HeapTuple oldtuple;
19191920
TriggerData LocTriggerData;
19201921
int i;
@@ -1947,12 +1948,29 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
19471948
relinfo->ri_TrigFunctions,
19481949
relinfo->ri_TrigInstrument,
19491950
GetPerTupleMemoryContext(estate));
1950-
if (oldtuple != newtuple && oldtuple != trigtuple)
1951+
if (oldtuple != newtuple && oldtuple != slottuple)
19511952
heap_freetuple(oldtuple);
19521953
if (newtuple == NULL)
1953-
break;
1954+
return NULL; /* "do nothing" */
1955+
}
1956+
1957+
if (newtuple != slottuple)
1958+
{
1959+
/*
1960+
* Return the modified tuple using the es_trig_tuple_slot. We assume
1961+
* the tuple was allocated in per-tuple memory context, and therefore
1962+
* will go away by itself. The tuple table slot should not try to
1963+
* clear it.
1964+
*/
1965+
TupleTableSlot *newslot = estate->es_trig_tuple_slot;
1966+
TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
1967+
1968+
if (newslot->tts_tupleDescriptor != tupdesc)
1969+
ExecSetSlotDescriptor(newslot, tupdesc);
1970+
ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
1971+
slot = newslot;
19541972
}
1955-
return newtuple;
1973+
return slot;
19561974
}
19571975

19581976
void
@@ -1966,12 +1984,13 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
19661984
true, NULL, trigtuple, recheckIndexes, NULL);
19671985
}
19681986

1969-
HeapTuple
1987+
TupleTableSlot *
19701988
ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
1971-
HeapTuple trigtuple)
1989+
TupleTableSlot *slot)
19721990
{
19731991
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
1974-
HeapTuple newtuple = trigtuple;
1992+
HeapTuple slottuple = ExecMaterializeSlot(slot);
1993+
HeapTuple newtuple = slottuple;
19751994
HeapTuple oldtuple;
19761995
TriggerData LocTriggerData;
19771996
int i;
@@ -2004,12 +2023,29 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
20042023
relinfo->ri_TrigFunctions,
20052024
relinfo->ri_TrigInstrument,
20062025
GetPerTupleMemoryContext(estate));
2007-
if (oldtuple != newtuple && oldtuple != trigtuple)
2026+
if (oldtuple != newtuple && oldtuple != slottuple)
20082027
heap_freetuple(oldtuple);
20092028
if (newtuple == NULL)
2010-
break;
2029+
return NULL; /* "do nothing" */
2030+
}
2031+
2032+
if (newtuple != slottuple)
2033+
{
2034+
/*
2035+
* Return the modified tuple using the es_trig_tuple_slot. We assume
2036+
* the tuple was allocated in per-tuple memory context, and therefore
2037+
* will go away by itself. The tuple table slot should not try to
2038+
* clear it.
2039+
*/
2040+
TupleTableSlot *newslot = estate->es_trig_tuple_slot;
2041+
TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
2042+
2043+
if (newslot->tts_tupleDescriptor != tupdesc)
2044+
ExecSetSlotDescriptor(newslot, tupdesc);
2045+
ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
2046+
slot = newslot;
20112047
}
2012-
return newtuple;
2048+
return slot;
20132049
}
20142050

20152051
void
@@ -2257,32 +2293,44 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
22572293
GetModifiedColumns(relinfo, estate));
22582294
}
22592295

2260-
HeapTuple
2296+
TupleTableSlot *
22612297
ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
22622298
ResultRelInfo *relinfo,
2263-
ItemPointer tupleid, HeapTuple newtuple)
2299+
ItemPointer tupleid, TupleTableSlot *slot)
22642300
{
22652301
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
2302+
HeapTuple slottuple = ExecMaterializeSlot(slot);
2303+
HeapTuple newtuple = slottuple;
22662304
TriggerData LocTriggerData;
22672305
HeapTuple trigtuple;
22682306
HeapTuple oldtuple;
2269-
HeapTuple intuple = newtuple;
22702307
TupleTableSlot *newSlot;
22712308
int i;
22722309
Bitmapset *modifiedCols;
22732310

2311+
/* get a copy of the on-disk tuple we are planning to update */
22742312
trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
22752313
&newSlot);
22762314
if (trigtuple == NULL)
2277-
return NULL;
2315+
return NULL; /* cancel the update action */
22782316

22792317
/*
2280-
* In READ COMMITTED isolation level it's possible that newtuple was
2318+
* In READ COMMITTED isolation level it's possible that target tuple was
22812319
* changed due to concurrent update. In that case we have a raw subplan
2282-
* output tuple and need to run it through the junk filter.
2320+
* output tuple in newSlot, and need to run it through the junk filter to
2321+
* produce an insertable tuple.
2322+
*
2323+
* Caution: more than likely, the passed-in slot is the same as the
2324+
* junkfilter's output slot, so we are clobbering the original value of
2325+
* slottuple by doing the filtering. This is OK since neither we nor our
2326+
* caller have any more interest in the prior contents of that slot.
22832327
*/
22842328
if (newSlot != NULL)
2285-
intuple = newtuple = ExecRemoveJunk(relinfo->ri_junkFilter, newSlot);
2329+
{
2330+
slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
2331+
slottuple = ExecMaterializeSlot(slot);
2332+
newtuple = slottuple;
2333+
}
22862334

22872335
modifiedCols = GetModifiedColumns(relinfo, estate);
22882336

@@ -2314,13 +2362,33 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
23142362
relinfo->ri_TrigFunctions,
23152363
relinfo->ri_TrigInstrument,
23162364
GetPerTupleMemoryContext(estate));
2317-
if (oldtuple != newtuple && oldtuple != intuple)
2365+
if (oldtuple != newtuple && oldtuple != slottuple)
23182366
heap_freetuple(oldtuple);
23192367
if (newtuple == NULL)
2320-
break;
2368+
{
2369+
heap_freetuple(trigtuple);
2370+
return NULL; /* "do nothing" */
2371+
}
23212372
}
23222373
heap_freetuple(trigtuple);
2323-
return newtuple;
2374+
2375+
if (newtuple != slottuple)
2376+
{
2377+
/*
2378+
* Return the modified tuple using the es_trig_tuple_slot. We assume
2379+
* the tuple was allocated in per-tuple memory context, and therefore
2380+
* will go away by itself. The tuple table slot should not try to
2381+
* clear it.
2382+
*/
2383+
TupleTableSlot *newslot = estate->es_trig_tuple_slot;
2384+
TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
2385+
2386+
if (newslot->tts_tupleDescriptor != tupdesc)
2387+
ExecSetSlotDescriptor(newslot, tupdesc);
2388+
ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
2389+
slot = newslot;
2390+
}
2391+
return slot;
23242392
}
23252393

23262394
void
@@ -2342,14 +2410,15 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
23422410
}
23432411
}
23442412

2345-
HeapTuple
2413+
TupleTableSlot *
23462414
ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
2347-
HeapTuple oldtuple, HeapTuple newtuple)
2415+
HeapTuple trigtuple, TupleTableSlot *slot)
23482416
{
23492417
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
2418+
HeapTuple slottuple = ExecMaterializeSlot(slot);
2419+
HeapTuple newtuple = slottuple;
23502420
TriggerData LocTriggerData;
2351-
HeapTuple intuple = newtuple;
2352-
HeapTuple rettuple;
2421+
HeapTuple oldtuple;
23532422
int i;
23542423

23552424
LocTriggerData.type = T_TriggerData;
@@ -2367,26 +2436,42 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
23672436
TRIGGER_TYPE_UPDATE))
23682437
continue;
23692438
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
2370-
NULL, oldtuple, newtuple))
2439+
NULL, trigtuple, newtuple))
23712440
continue;
23722441

2373-
LocTriggerData.tg_trigtuple = oldtuple;
2374-
LocTriggerData.tg_newtuple = newtuple;
2442+
LocTriggerData.tg_trigtuple = trigtuple;
2443+
LocTriggerData.tg_newtuple = oldtuple = newtuple;
23752444
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
23762445
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
23772446
LocTriggerData.tg_trigger = trigger;
2378-
rettuple = ExecCallTriggerFunc(&LocTriggerData,
2447+
newtuple = ExecCallTriggerFunc(&LocTriggerData,
23792448
i,
23802449
relinfo->ri_TrigFunctions,
23812450
relinfo->ri_TrigInstrument,
23822451
GetPerTupleMemoryContext(estate));
2383-
if (newtuple != rettuple && newtuple != intuple)
2384-
heap_freetuple(newtuple);
2385-
newtuple = rettuple;
2452+
if (oldtuple != newtuple && oldtuple != slottuple)
2453+
heap_freetuple(oldtuple);
23862454
if (newtuple == NULL)
2387-
break;
2455+
return NULL; /* "do nothing" */
2456+
}
2457+
2458+
if (newtuple != slottuple)
2459+
{
2460+
/*
2461+
* Return the modified tuple using the es_trig_tuple_slot. We assume
2462+
* the tuple was allocated in per-tuple memory context, and therefore
2463+
* will go away by itself. The tuple table slot should not try to
2464+
* clear it.
2465+
*/
2466+
TupleTableSlot *newslot = estate->es_trig_tuple_slot;
2467+
TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
2468+
2469+
if (newslot->tts_tupleDescriptor != tupdesc)
2470+
ExecSetSlotDescriptor(newslot, tupdesc);
2471+
ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
2472+
slot = newslot;
23882473
}
2389-
return newtuple;
2474+
return slot;
23902475
}
23912476

23922477
void

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy