Server crash: Use-after-free in AfterTriggerEndQuery()
Hi,
The crash occurs when the per-query firing loop in
AfterTriggerEndQuery() exits via the "all fired" path. If
afterTriggerInvokeEvents() reallocated query_stack while firing, the
loop's local qs pointer is left dangling, and the subsequent
FireAfterTriggerBatchCallbacks(qs->batch_callbacks) reads
batch_callbacks from the freed memory and crashes.
Here is the reproducible test that has an AFTER INSERT trigger on a
referenced table that recursively inserts rows into itself:
--
create table trigger_recursive_pk (id int primary key);
create table trigger_recursive_fk (id int references trigger_recursive_pk(id));
insert into trigger_recursive_pk select g from generate_series(1, 15) g;
create function trigger_recursive_fn() returns trigger language plpgsql as $$
begin
if new.id < 10 then
insert into trigger_recursive_fk values (new.id + 1);
end if;
return new;
end$$;
create trigger trigger_recursive after insert on trigger_recursive_fk
for each row execute function trigger_recursive_fn();
insert into trigger_recursive_fk values (1);
--
The attached patch fixes the reported issue by recomputing qs
immediately before calling FireAfterTriggerBatchCallbacks().
--
Regards,
Amul Sul
EDB: http://www.enterprisedb.com
Attachments:
0001-Fix-use-after-free-of-qs-in-AfterTriggerEndQuery.patchapplication/x-patch; name=0001-Fix-use-after-free-of-qs-in-AfterTriggerEndQuery.patchDownload+63-1
On Tue, May 5, 2026 at 15:37 Amul Sul <sulamul@gmail.com> wrote:
Hi,
The crash occurs when the per-query firing loop in
AfterTriggerEndQuery() exits via the "all fired" path. If
afterTriggerInvokeEvents() reallocated query_stack while firing, the
loop's local qs pointer is left dangling, and the subsequent
FireAfterTriggerBatchCallbacks(qs->batch_callbacks) reads
batch_callbacks from the freed memory and crashes.Here is the reproducible test that has an AFTER INSERT trigger on a
referenced table that recursively inserts rows into itself:--
create table trigger_recursive_pk (id int primary key);
create table trigger_recursive_fk (id int references
trigger_recursive_pk(id));
insert into trigger_recursive_pk select g from generate_series(1, 15) g;create function trigger_recursive_fn() returns trigger language plpgsql as
$$
begin
if new.id < 10 then
insert into trigger_recursive_fk values (new.id + 1);
end if;
return new;
end$$;create trigger trigger_recursive after insert on trigger_recursive_fk
for each row execute function trigger_recursive_fn();insert into trigger_recursive_fk values (1);
--The attached patch fixes the reported issue by recomputing qs
immediately before calling FireAfterTriggerBatchCallbacks().
Thanks Amul for the report. I'll look at this on Thursday when I'm back at
work.
- Amit
Show quoted text
Hi,
On Tue, May 5, 2026 at 8:38 AM Amul Sul <sulamul@gmail.com> wrote:
Here is the reproducible test that has an AFTER INSERT trigger on a
referenced table that recursively inserts rows into itself:--
create table trigger_recursive_pk (id int primary key);
create table trigger_recursive_fk (id int references trigger_recursive_pk(id));
insert into trigger_recursive_pk select g from generate_series(1, 15) g;create function trigger_recursive_fn() returns trigger language plpgsql as $$
begin
if new.id < 10 then
insert into trigger_recursive_fk values (new.id + 1);
end if;
return new;
end$$;create trigger trigger_recursive after insert on trigger_recursive_fk
for each row execute function trigger_recursive_fn();insert into trigger_recursive_fk values (1);
--
I've managed to reproduce the issue on the current HEAD thanks to the
script. Doing a git bissect, the failure was introduced with
34a30786293005 when the batch_callbacks list was added.
The attached patch fixes the reported issue by recomputing qs
immediately before calling FireAfterTriggerBatchCallbacks().
The patch fixes the issue and the change looks reasonable.
Regards,
Anthonin Bonnefoy
Hi Anthonin,
On Wed, May 6, 2026 at 5:14 PM Anthonin Bonnefoy
<anthonin.bonnefoy@datadoghq.com> wrote:
On Tue, May 5, 2026 at 8:38 AM Amul Sul <sulamul@gmail.com> wrote:
Here is the reproducible test that has an AFTER INSERT trigger on a
referenced table that recursively inserts rows into itself:--
create table trigger_recursive_pk (id int primary key);
create table trigger_recursive_fk (id int references trigger_recursive_pk(id));
insert into trigger_recursive_pk select g from generate_series(1, 15) g;create function trigger_recursive_fn() returns trigger language plpgsql as $$
begin
if new.id < 10 then
insert into trigger_recursive_fk values (new.id + 1);
end if;
return new;
end$$;create trigger trigger_recursive after insert on trigger_recursive_fk
for each row execute function trigger_recursive_fn();insert into trigger_recursive_fk values (1);
--I've managed to reproduce the issue on the current HEAD thanks to the
script. Doing a git bissect, the failure was introduced with
34a30786293005 when the batch_callbacks list was added.The attached patch fixes the reported issue by recomputing qs
immediately before calling FireAfterTriggerBatchCallbacks().The patch fixes the issue and the change looks reasonable.
Thanks for the review. I agree.
Attached v2. I simplified the test because the FK isn't really needed
to reproduce the bug, since the use-after-free is the stale qs load
itself. Also reworded the comment above the recompute and tweaked the
commit message a bit.
Will push tomorrow barring objections. Thanks again, Amul, for the patch.
--
Thanks, Amit Langote
Attachments:
v2-0001-Fix-use-after-free-of-qs-in-AfterTriggerEndQuery.patchapplication/octet-stream; name=v2-0001-Fix-use-after-free-of-qs-in-AfterTriggerEndQuery.patchDownload+53-1
On Thu, May 7, 2026 at 6:09 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Wed, May 6, 2026 at 5:14 PM Anthonin Bonnefoy
<anthonin.bonnefoy@datadoghq.com> wrote:On Tue, May 5, 2026 at 8:38 AM Amul Sul <sulamul@gmail.com> wrote:
Here is the reproducible test that has an AFTER INSERT trigger on a
referenced table that recursively inserts rows into itself:--
create table trigger_recursive_pk (id int primary key);
create table trigger_recursive_fk (id int references trigger_recursive_pk(id));
insert into trigger_recursive_pk select g from generate_series(1, 15) g;create function trigger_recursive_fn() returns trigger language plpgsql as $$
begin
if new.id < 10 then
insert into trigger_recursive_fk values (new.id + 1);
end if;
return new;
end$$;create trigger trigger_recursive after insert on trigger_recursive_fk
for each row execute function trigger_recursive_fn();insert into trigger_recursive_fk values (1);
--I've managed to reproduce the issue on the current HEAD thanks to the
script. Doing a git bissect, the failure was introduced with
34a30786293005 when the batch_callbacks list was added.The attached patch fixes the reported issue by recomputing qs
immediately before calling FireAfterTriggerBatchCallbacks().The patch fixes the issue and the change looks reasonable.
Thanks for the review. I agree.
Attached v2. I simplified the test because the FK isn't really needed
to reproduce the bug, since the use-after-free is the stale qs load
itself. Also reworded the comment above the recompute and tweaked the
commit message a bit.Will push tomorrow barring objections. Thanks again, Amul, for the patch.
Pushed.
--
Thanks, Amit Langote