Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 262eb99

Browse files
committed
Fix use-after-free bug with AfterTriggersTableData.storeslot
AfterTriggerSaveEvent() wrongly allocates the slot in execution-span memory context, whereas the correct thing is to allocate it in a transaction-span context, because that's where the enclosing AfterTriggersTableData instance belongs into. Backpatch to 12 (the test back to 11, where it works well with no code changes, and it's good to have to confirm that the case was previously well supported); this bug seems introduced by commit ff11e7f. Reported-by: Bertrand Drouvot <bdrouvot@amazon.com> Author: Amit Langote <amitlangote09@gmail.com> Discussion: https://postgr.es/m/39a71864-b120-5a5c-8cc5-c632b6f16761@amazon.com
1 parent fb1e218 commit 262eb99

File tree

3 files changed

+157
-19
lines changed

3 files changed

+157
-19
lines changed

src/backend/commands/trigger.c

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3832,6 +3832,8 @@ static void AfterTriggerExecute(EState *estate,
38323832
TupleTableSlot *trig_tuple_slot2);
38333833
static AfterTriggersTableData *GetAfterTriggersTableData(Oid relid,
38343834
CmdType cmdType);
3835+
static TupleTableSlot *GetAfterTriggersStoreSlot(AfterTriggersTableData *table,
3836+
TupleDesc tupdesc);
38353837
static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs);
38363838
static SetConstraintState SetConstraintStateCreate(int numalloc);
38373839
static SetConstraintState SetConstraintStateCopy(SetConstraintState state);
@@ -4634,6 +4636,31 @@ GetAfterTriggersTableData(Oid relid, CmdType cmdType)
46344636
return table;
46354637
}
46364638

4639+
/*
4640+
* Returns a TupleTableSlot suitable for holding the tuples to be put
4641+
* into AfterTriggersTableData's transition table tuplestores.
4642+
*/
4643+
static TupleTableSlot *
4644+
GetAfterTriggersStoreSlot(AfterTriggersTableData *table,
4645+
TupleDesc tupdesc)
4646+
{
4647+
/* Create it if not already done. */
4648+
if (!table->storeslot)
4649+
{
4650+
MemoryContext oldcxt;
4651+
4652+
/*
4653+
* We only need this slot only until AfterTriggerEndQuery, but making
4654+
* it last till end-of-subxact is good enough. It'll be freed by
4655+
* AfterTriggerFreeQuery().
4656+
*/
4657+
oldcxt = MemoryContextSwitchTo(CurTransactionContext);
4658+
table->storeslot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual);
4659+
MemoryContextSwitchTo(oldcxt);
4660+
}
4661+
4662+
return table->storeslot;
4663+
}
46374664

46384665
/*
46394666
* MakeTransitionCaptureState
@@ -4922,6 +4949,8 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
49224949
table->new_tuplestore = NULL;
49234950
if (ts)
49244951
tuplestore_end(ts);
4952+
if (table->storeslot)
4953+
ExecDropSingleTupleTableSlot(table->storeslot);
49254954
}
49264955

49274956
/*
@@ -5771,17 +5800,10 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
57715800

57725801
if (map != NULL)
57735802
{
5803+
AfterTriggersTableData *table = transition_capture->tcs_private;
57745804
TupleTableSlot *storeslot;
57755805

5776-
storeslot = transition_capture->tcs_private->storeslot;
5777-
if (!storeslot)
5778-
{
5779-
storeslot = ExecAllocTableSlot(&estate->es_tupleTable,
5780-
map->outdesc,
5781-
&TTSOpsVirtual);
5782-
transition_capture->tcs_private->storeslot = storeslot;
5783-
}
5784-
5806+
storeslot = GetAfterTriggersStoreSlot(table, map->outdesc);
57855807
execute_attr_map_slot(map->attrMap, oldslot, storeslot);
57865808
tuplestore_puttupleslot(old_tuplestore, storeslot);
57875809
}
@@ -5801,18 +5823,10 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
58015823
original_insert_tuple);
58025824
else if (map != NULL)
58035825
{
5826+
AfterTriggersTableData *table = transition_capture->tcs_private;
58045827
TupleTableSlot *storeslot;
58055828

5806-
storeslot = transition_capture->tcs_private->storeslot;
5807-
5808-
if (!storeslot)
5809-
{
5810-
storeslot = ExecAllocTableSlot(&estate->es_tupleTable,
5811-
map->outdesc,
5812-
&TTSOpsVirtual);
5813-
transition_capture->tcs_private->storeslot = storeslot;
5814-
}
5815-
5829+
storeslot = GetAfterTriggersStoreSlot(table, map->outdesc);
58165830
execute_attr_map_slot(map->attrMap, newslot, storeslot);
58175831
tuplestore_puttupleslot(new_tuplestore, storeslot);
58185832
}

src/test/regress/expected/triggers.out

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3083,3 +3083,62 @@ drop table self_ref;
30833083
drop function dump_insert();
30843084
drop function dump_update();
30853085
drop function dump_delete();
3086+
-- verify transition table conversion slot's lifetime
3087+
-- https://postgr.es/m/39a71864-b120-5a5c-8cc5-c632b6f16761@amazon.com
3088+
create table convslot_test_parent (col1 text primary key);
3089+
create table convslot_test_child (col1 text primary key,
3090+
foreign key (col1) references convslot_test_parent(col1) on delete cascade on update cascade
3091+
);
3092+
alter table convslot_test_child add column col2 text not null default 'tutu';
3093+
insert into convslot_test_parent(col1) values ('1');
3094+
insert into convslot_test_child(col1) values ('1');
3095+
insert into convslot_test_parent(col1) values ('3');
3096+
insert into convslot_test_child(col1) values ('3');
3097+
create or replace function trigger_function1()
3098+
returns trigger
3099+
language plpgsql
3100+
AS $$
3101+
begin
3102+
raise notice 'trigger = %, old_table = %',
3103+
TG_NAME,
3104+
(select string_agg(old_table::text, ', ' order by col1) from old_table);
3105+
return null;
3106+
end; $$;
3107+
create or replace function trigger_function2()
3108+
returns trigger
3109+
language plpgsql
3110+
AS $$
3111+
begin
3112+
raise notice 'trigger = %, new table = %',
3113+
TG_NAME,
3114+
(select string_agg(new_table::text, ', ' order by col1) from new_table);
3115+
return null;
3116+
end; $$;
3117+
create trigger but_trigger after update on convslot_test_child
3118+
referencing new table as new_table
3119+
for each statement execute function trigger_function2();
3120+
update convslot_test_parent set col1 = col1 || '1';
3121+
NOTICE: trigger = but_trigger, new table = (11,tutu), (31,tutu)
3122+
create or replace function trigger_function3()
3123+
returns trigger
3124+
language plpgsql
3125+
AS $$
3126+
begin
3127+
raise notice 'trigger = %, old_table = %, new table = %',
3128+
TG_NAME,
3129+
(select string_agg(old_table::text, ', ' order by col1) from old_table),
3130+
(select string_agg(new_table::text, ', ' order by col1) from new_table);
3131+
return null;
3132+
end; $$;
3133+
create trigger but_trigger2 after update on convslot_test_child
3134+
referencing old table as old_table new table as new_table
3135+
for each statement execute function trigger_function3();
3136+
update convslot_test_parent set col1 = col1 || '1';
3137+
NOTICE: trigger = but_trigger, new table = (111,tutu), (311,tutu)
3138+
NOTICE: trigger = but_trigger2, old_table = (11,tutu), (31,tutu), new table = (111,tutu), (311,tutu)
3139+
create trigger bdt_trigger after delete on convslot_test_child
3140+
referencing old table as old_table
3141+
for each statement execute function trigger_function1();
3142+
delete from convslot_test_parent;
3143+
NOTICE: trigger = bdt_trigger, old_table = (111,tutu), (311,tutu)
3144+
drop table convslot_test_child, convslot_test_parent;

src/test/regress/sql/triggers.sql

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,3 +2296,68 @@ drop table self_ref;
22962296
drop function dump_insert();
22972297
drop function dump_update();
22982298
drop function dump_delete();
2299+
2300+
-- verify transition table conversion slot's lifetime
2301+
-- https://postgr.es/m/39a71864-b120-5a5c-8cc5-c632b6f16761@amazon.com
2302+
create table convslot_test_parent (col1 text primary key);
2303+
create table convslot_test_child (col1 text primary key,
2304+
foreign key (col1) references convslot_test_parent(col1) on delete cascade on update cascade
2305+
);
2306+
2307+
alter table convslot_test_child add column col2 text not null default 'tutu';
2308+
insert into convslot_test_parent(col1) values ('1');
2309+
insert into convslot_test_child(col1) values ('1');
2310+
insert into convslot_test_parent(col1) values ('3');
2311+
insert into convslot_test_child(col1) values ('3');
2312+
2313+
create or replace function trigger_function1()
2314+
returns trigger
2315+
language plpgsql
2316+
AS $$
2317+
begin
2318+
raise notice 'trigger = %, old_table = %',
2319+
TG_NAME,
2320+
(select string_agg(old_table::text, ', ' order by col1) from old_table);
2321+
return null;
2322+
end; $$;
2323+
2324+
create or replace function trigger_function2()
2325+
returns trigger
2326+
language plpgsql
2327+
AS $$
2328+
begin
2329+
raise notice 'trigger = %, new table = %',
2330+
TG_NAME,
2331+
(select string_agg(new_table::text, ', ' order by col1) from new_table);
2332+
return null;
2333+
end; $$;
2334+
2335+
create trigger but_trigger after update on convslot_test_child
2336+
referencing new table as new_table
2337+
for each statement execute function trigger_function2();
2338+
2339+
update convslot_test_parent set col1 = col1 || '1';
2340+
2341+
create or replace function trigger_function3()
2342+
returns trigger
2343+
language plpgsql
2344+
AS $$
2345+
begin
2346+
raise notice 'trigger = %, old_table = %, new table = %',
2347+
TG_NAME,
2348+
(select string_agg(old_table::text, ', ' order by col1) from old_table),
2349+
(select string_agg(new_table::text, ', ' order by col1) from new_table);
2350+
return null;
2351+
end; $$;
2352+
2353+
create trigger but_trigger2 after update on convslot_test_child
2354+
referencing old table as old_table new table as new_table
2355+
for each statement execute function trigger_function3();
2356+
update convslot_test_parent set col1 = col1 || '1';
2357+
2358+
create trigger bdt_trigger after delete on convslot_test_child
2359+
referencing old table as old_table
2360+
for each statement execute function trigger_function1();
2361+
delete from convslot_test_parent;
2362+
2363+
drop table convslot_test_child, convslot_test_parent;

0 commit comments

Comments
 (0)