Commit 25b69256 authored by Tom Lane's avatar Tom Lane

Prevent dangling-pointer access when update trigger returns old tuple.

A before-update row trigger may choose to return the "new" or "old" tuple
unmodified.  ExecBRUpdateTriggers failed to consider the second
possibility, and would proceed to free the "old" tuple even if it was the
one returned, leading to subsequent access to already-deallocated memory.
In debug builds this reliably leads to an "invalid memory alloc request
size" failure; in production builds it might accidentally work, but data
corruption is also possible.

This is a very old bug.  There are probably a couple of reasons it hasn't
been noticed up to now.  It would be more usual to return NULL if one
wanted to suppress the update action; returning "old" is significantly less
efficient since the update will occur anyway.  Also, none of the standard
PLs would ever cause this because they all returned freshly-manufactured
tuples even if they were just copying "old".  But commit 4b93f579 changed
that for plpgsql, making it possible to see the bug with a plpgsql trigger.
Still, this is certainly legal behavior for a trigger function, so it's
ExecBRUpdateTriggers's fault not plpgsql's.

It seems worth creating a test case that exercises returning "old" directly
with a C-language trigger; testing this through plpgsql seems unreliable
because its behavior might change again.

Report and fix by Rushabh Lathia; regression test case by me.
Back-patch to all supported branches.

Discussion: https://postgr.es/m/CAGPqQf1P4pjiNPrMof=P_16E-DFjt457j+nH2ex3=nBTew7tXw@mail.gmail.com
parent 5e6a63c0
...@@ -2815,7 +2815,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ...@@ -2815,7 +2815,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
return NULL; /* "do nothing" */ return NULL; /* "do nothing" */
} }
} }
if (trigtuple != fdw_trigtuple) if (trigtuple != fdw_trigtuple && trigtuple != newtuple)
heap_freetuple(trigtuple); heap_freetuple(trigtuple);
if (newtuple != slottuple) if (newtuple != slottuple)
......
...@@ -119,6 +119,32 @@ SELECT trigger_name, event_manipulation, event_object_schema, event_object_table ...@@ -119,6 +119,32 @@ SELECT trigger_name, event_manipulation, event_object_schema, event_object_table
DROP TABLE pkeys; DROP TABLE pkeys;
DROP TABLE fkeys; DROP TABLE fkeys;
DROP TABLE fkeys2; DROP TABLE fkeys2;
-- Check behavior when trigger returns unmodified trigtuple
create table trigtest (f1 int, f2 text);
create trigger trigger_return_old
before insert or delete or update on trigtest
for each row execute procedure trigger_return_old();
insert into trigtest values(1, 'foo');
select * from trigtest;
f1 | f2
----+-----
1 | foo
(1 row)
update trigtest set f2 = f2 || 'bar';
select * from trigtest;
f1 | f2
----+-----
1 | foo
(1 row)
delete from trigtest;
select * from trigtest;
f1 | f2
----+----
(0 rows)
drop table trigtest;
create sequence ttdummy_seq increment 10 start 0 minvalue 0; create sequence ttdummy_seq increment 10 start 0 minvalue 0;
create table tttest ( create table tttest (
price_id int4, price_id int4,
......
...@@ -37,6 +37,11 @@ CREATE FUNCTION autoinc () ...@@ -37,6 +37,11 @@ CREATE FUNCTION autoinc ()
AS '@libdir@/autoinc@DLSUFFIX@' AS '@libdir@/autoinc@DLSUFFIX@'
LANGUAGE C; LANGUAGE C;
CREATE FUNCTION trigger_return_old ()
RETURNS trigger
AS '@libdir@/regress@DLSUFFIX@'
LANGUAGE C;
CREATE FUNCTION ttdummy () CREATE FUNCTION ttdummy ()
RETURNS trigger RETURNS trigger
AS '@libdir@/regress@DLSUFFIX@' AS '@libdir@/regress@DLSUFFIX@'
......
...@@ -35,6 +35,10 @@ CREATE FUNCTION autoinc () ...@@ -35,6 +35,10 @@ CREATE FUNCTION autoinc ()
RETURNS trigger RETURNS trigger
AS '@libdir@/autoinc@DLSUFFIX@' AS '@libdir@/autoinc@DLSUFFIX@'
LANGUAGE C; LANGUAGE C;
CREATE FUNCTION trigger_return_old ()
RETURNS trigger
AS '@libdir@/regress@DLSUFFIX@'
LANGUAGE C;
CREATE FUNCTION ttdummy () CREATE FUNCTION ttdummy ()
RETURNS trigger RETURNS trigger
AS '@libdir@/regress@DLSUFFIX@' AS '@libdir@/regress@DLSUFFIX@'
......
...@@ -203,6 +203,21 @@ reverse_name(PG_FUNCTION_ARGS) ...@@ -203,6 +203,21 @@ reverse_name(PG_FUNCTION_ARGS)
PG_RETURN_CSTRING(new_string); PG_RETURN_CSTRING(new_string);
} }
PG_FUNCTION_INFO_V1(trigger_return_old);
Datum
trigger_return_old(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
HeapTuple tuple;
if (!CALLED_AS_TRIGGER(fcinfo))
elog(ERROR, "trigger_return_old: not fired by trigger manager");
tuple = trigdata->tg_trigtuple;
return PointerGetDatum(tuple);
}
#define TTDUMMY_INFINITY 999999 #define TTDUMMY_INFINITY 999999
......
...@@ -103,6 +103,22 @@ DROP TABLE pkeys; ...@@ -103,6 +103,22 @@ DROP TABLE pkeys;
DROP TABLE fkeys; DROP TABLE fkeys;
DROP TABLE fkeys2; DROP TABLE fkeys2;
-- Check behavior when trigger returns unmodified trigtuple
create table trigtest (f1 int, f2 text);
create trigger trigger_return_old
before insert or delete or update on trigtest
for each row execute procedure trigger_return_old();
insert into trigtest values(1, 'foo');
select * from trigtest;
update trigtest set f2 = f2 || 'bar';
select * from trigtest;
delete from trigtest;
select * from trigtest;
drop table trigtest;
create sequence ttdummy_seq increment 10 start 0 minvalue 0; create sequence ttdummy_seq increment 10 start 0 minvalue 0;
create table tttest ( create table tttest (
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment