mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-10-02 23:06:49 +02:00
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 ff11e7f4b9
.
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
This commit is contained in:
parent
5744931830
commit
2688852a49
@ -3461,6 +3461,8 @@ static void AfterTriggerExecute(EState *estate,
|
||||
TupleTableSlot *trig_tuple_slot2);
|
||||
static AfterTriggersTableData *GetAfterTriggersTableData(Oid relid,
|
||||
CmdType cmdType);
|
||||
static TupleTableSlot *GetAfterTriggersStoreSlot(AfterTriggersTableData *table,
|
||||
TupleDesc tupdesc);
|
||||
static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs);
|
||||
static SetConstraintState SetConstraintStateCreate(int numalloc);
|
||||
static SetConstraintState SetConstraintStateCopy(SetConstraintState state);
|
||||
@ -4261,6 +4263,31 @@ GetAfterTriggersTableData(Oid relid, CmdType cmdType)
|
||||
return table;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a TupleTableSlot suitable for holding the tuples to be put
|
||||
* into AfterTriggersTableData's transition table tuplestores.
|
||||
*/
|
||||
static TupleTableSlot *
|
||||
GetAfterTriggersStoreSlot(AfterTriggersTableData *table,
|
||||
TupleDesc tupdesc)
|
||||
{
|
||||
/* Create it if not already done. */
|
||||
if (!table->storeslot)
|
||||
{
|
||||
MemoryContext oldcxt;
|
||||
|
||||
/*
|
||||
* We only need this slot only until AfterTriggerEndQuery, but making
|
||||
* it last till end-of-subxact is good enough. It'll be freed by
|
||||
* AfterTriggerFreeQuery().
|
||||
*/
|
||||
oldcxt = MemoryContextSwitchTo(CurTransactionContext);
|
||||
table->storeslot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual);
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
|
||||
return table->storeslot;
|
||||
}
|
||||
|
||||
/*
|
||||
* MakeTransitionCaptureState
|
||||
@ -4549,6 +4576,8 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
|
||||
table->new_tuplestore = NULL;
|
||||
if (ts)
|
||||
tuplestore_end(ts);
|
||||
if (table->storeslot)
|
||||
ExecDropSingleTupleTableSlot(table->storeslot);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -5398,17 +5427,10 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
|
||||
|
||||
if (map != NULL)
|
||||
{
|
||||
AfterTriggersTableData *table = transition_capture->tcs_private;
|
||||
TupleTableSlot *storeslot;
|
||||
|
||||
storeslot = transition_capture->tcs_private->storeslot;
|
||||
if (!storeslot)
|
||||
{
|
||||
storeslot = ExecAllocTableSlot(&estate->es_tupleTable,
|
||||
map->outdesc,
|
||||
&TTSOpsVirtual);
|
||||
transition_capture->tcs_private->storeslot = storeslot;
|
||||
}
|
||||
|
||||
storeslot = GetAfterTriggersStoreSlot(table, map->outdesc);
|
||||
execute_attr_map_slot(map->attrMap, oldslot, storeslot);
|
||||
tuplestore_puttupleslot(old_tuplestore, storeslot);
|
||||
}
|
||||
@ -5428,18 +5450,10 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
|
||||
original_insert_tuple);
|
||||
else if (map != NULL)
|
||||
{
|
||||
AfterTriggersTableData *table = transition_capture->tcs_private;
|
||||
TupleTableSlot *storeslot;
|
||||
|
||||
storeslot = transition_capture->tcs_private->storeslot;
|
||||
|
||||
if (!storeslot)
|
||||
{
|
||||
storeslot = ExecAllocTableSlot(&estate->es_tupleTable,
|
||||
map->outdesc,
|
||||
&TTSOpsVirtual);
|
||||
transition_capture->tcs_private->storeslot = storeslot;
|
||||
}
|
||||
|
||||
storeslot = GetAfterTriggersStoreSlot(table, map->outdesc);
|
||||
execute_attr_map_slot(map->attrMap, newslot, storeslot);
|
||||
tuplestore_puttupleslot(new_tuplestore, storeslot);
|
||||
}
|
||||
|
@ -3201,3 +3201,62 @@ create trigger aft_row after insert or update on trigger_parted
|
||||
create table trigger_parted_p1 partition of trigger_parted for values in (1)
|
||||
partition by list (a);
|
||||
create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
|
||||
-- verify transition table conversion slot's lifetime
|
||||
-- https://postgr.es/m/39a71864-b120-5a5c-8cc5-c632b6f16761@amazon.com
|
||||
create table convslot_test_parent (col1 text primary key);
|
||||
create table convslot_test_child (col1 text primary key,
|
||||
foreign key (col1) references convslot_test_parent(col1) on delete cascade on update cascade
|
||||
);
|
||||
alter table convslot_test_child add column col2 text not null default 'tutu';
|
||||
insert into convslot_test_parent(col1) values ('1');
|
||||
insert into convslot_test_child(col1) values ('1');
|
||||
insert into convslot_test_parent(col1) values ('3');
|
||||
insert into convslot_test_child(col1) values ('3');
|
||||
create or replace function trigger_function1()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
AS $$
|
||||
begin
|
||||
raise notice 'trigger = %, old_table = %',
|
||||
TG_NAME,
|
||||
(select string_agg(old_table::text, ', ' order by col1) from old_table);
|
||||
return null;
|
||||
end; $$;
|
||||
create or replace function trigger_function2()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
AS $$
|
||||
begin
|
||||
raise notice 'trigger = %, new table = %',
|
||||
TG_NAME,
|
||||
(select string_agg(new_table::text, ', ' order by col1) from new_table);
|
||||
return null;
|
||||
end; $$;
|
||||
create trigger but_trigger after update on convslot_test_child
|
||||
referencing new table as new_table
|
||||
for each statement execute function trigger_function2();
|
||||
update convslot_test_parent set col1 = col1 || '1';
|
||||
NOTICE: trigger = but_trigger, new table = (11,tutu), (31,tutu)
|
||||
create or replace function trigger_function3()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
AS $$
|
||||
begin
|
||||
raise notice 'trigger = %, old_table = %, new table = %',
|
||||
TG_NAME,
|
||||
(select string_agg(old_table::text, ', ' order by col1) from old_table),
|
||||
(select string_agg(new_table::text, ', ' order by col1) from new_table);
|
||||
return null;
|
||||
end; $$;
|
||||
create trigger but_trigger2 after update on convslot_test_child
|
||||
referencing old table as old_table new table as new_table
|
||||
for each statement execute function trigger_function3();
|
||||
update convslot_test_parent set col1 = col1 || '1';
|
||||
NOTICE: trigger = but_trigger, new table = (111,tutu), (311,tutu)
|
||||
NOTICE: trigger = but_trigger2, old_table = (11,tutu), (31,tutu), new table = (111,tutu), (311,tutu)
|
||||
create trigger bdt_trigger after delete on convslot_test_child
|
||||
referencing old table as old_table
|
||||
for each statement execute function trigger_function1();
|
||||
delete from convslot_test_parent;
|
||||
NOTICE: trigger = bdt_trigger, old_table = (111,tutu), (311,tutu)
|
||||
drop table convslot_test_child, convslot_test_parent;
|
||||
|
@ -2383,3 +2383,68 @@ create trigger aft_row after insert or update on trigger_parted
|
||||
create table trigger_parted_p1 partition of trigger_parted for values in (1)
|
||||
partition by list (a);
|
||||
create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
|
||||
|
||||
-- verify transition table conversion slot's lifetime
|
||||
-- https://postgr.es/m/39a71864-b120-5a5c-8cc5-c632b6f16761@amazon.com
|
||||
create table convslot_test_parent (col1 text primary key);
|
||||
create table convslot_test_child (col1 text primary key,
|
||||
foreign key (col1) references convslot_test_parent(col1) on delete cascade on update cascade
|
||||
);
|
||||
|
||||
alter table convslot_test_child add column col2 text not null default 'tutu';
|
||||
insert into convslot_test_parent(col1) values ('1');
|
||||
insert into convslot_test_child(col1) values ('1');
|
||||
insert into convslot_test_parent(col1) values ('3');
|
||||
insert into convslot_test_child(col1) values ('3');
|
||||
|
||||
create or replace function trigger_function1()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
AS $$
|
||||
begin
|
||||
raise notice 'trigger = %, old_table = %',
|
||||
TG_NAME,
|
||||
(select string_agg(old_table::text, ', ' order by col1) from old_table);
|
||||
return null;
|
||||
end; $$;
|
||||
|
||||
create or replace function trigger_function2()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
AS $$
|
||||
begin
|
||||
raise notice 'trigger = %, new table = %',
|
||||
TG_NAME,
|
||||
(select string_agg(new_table::text, ', ' order by col1) from new_table);
|
||||
return null;
|
||||
end; $$;
|
||||
|
||||
create trigger but_trigger after update on convslot_test_child
|
||||
referencing new table as new_table
|
||||
for each statement execute function trigger_function2();
|
||||
|
||||
update convslot_test_parent set col1 = col1 || '1';
|
||||
|
||||
create or replace function trigger_function3()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
AS $$
|
||||
begin
|
||||
raise notice 'trigger = %, old_table = %, new table = %',
|
||||
TG_NAME,
|
||||
(select string_agg(old_table::text, ', ' order by col1) from old_table),
|
||||
(select string_agg(new_table::text, ', ' order by col1) from new_table);
|
||||
return null;
|
||||
end; $$;
|
||||
|
||||
create trigger but_trigger2 after update on convslot_test_child
|
||||
referencing old table as old_table new table as new_table
|
||||
for each statement execute function trigger_function3();
|
||||
update convslot_test_parent set col1 = col1 || '1';
|
||||
|
||||
create trigger bdt_trigger after delete on convslot_test_child
|
||||
referencing old table as old_table
|
||||
for each statement execute function trigger_function1();
|
||||
delete from convslot_test_parent;
|
||||
|
||||
drop table convslot_test_child, convslot_test_parent;
|
||||
|
Loading…
Reference in New Issue
Block a user