Server crash: Use-after-free in AfterTriggerEndQuery()

Started by Amul Sul9 days ago5 messageshackers
Jump to latest
#1Amul Sul
sulamul@gmail.com

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
#2Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amul Sul (#1)
Re: Server crash: Use-after-free in AfterTriggerEndQuery()

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
#3Anthonin Bonnefoy
anthonin.bonnefoy@datadoghq.com
In reply to: Amit Langote (#2)
Re: Server crash: Use-after-free in AfterTriggerEndQuery()

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

#4Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Anthonin Bonnefoy (#3)
Re: Server crash: Use-after-free in AfterTriggerEndQuery()

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
#5Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#4)
Re: Server crash: Use-after-free in AfterTriggerEndQuery()

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