[PATCH] Fix segmentation fault caused by reentrancy in RI_Fkey_cascade_del (ri_triggers.c)
Hi hackers,
We found a bug where executing a DELETE on a self-referential table that
fires triggers can cause a segmentation fault. This is due to a
*use-after-free* of a Postgres plan generated by the referential integrity
module (ri_triggers.c, RI_FKey_cascade_del). The crash occurs if the
Postgres plancache is invalidated (ResetPlanCache) during the execution of
a reentrant RI trigger.
A reentrant RI_FKey_cascade_del can occur if a table is self-referential
(i.e., it has a foreign key referencing its own primary key) and has BEFORE
DELETE triggers that delete rows from that same table.
-
*The first patch* adds a test case that reproduces the segmentation
fault. The crash itself happens in _SPI_execute_plan, but the root cause
is that the plan being executed was prematurely freed by the RI module.
-
*The second patch* fixes ri_triggers.c by introducing reentrancy guards,
which maintain a reference count of plans in execution to prevent them from
being freed while active.
Feedback and reviews are welcome.
Best regards,
Lucas Jeffrey
Attachments:
v2-0002-Fix-crash-of-self-referencing-tables-with-delete-tri.patchtext/x-patch; charset=US-ASCII; name=v2-0002-Fix-crash-of-self-referencing-tables-with-delete-tri.patchDownload+85-2
v2-0001-Add-test-case-that-triggers-self-referencing-table-c.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Add-test-case-that-triggers-self-referencing-table-c.patchDownload+111-1
Hi Luqui,
On 2026-May-29, Lucas Jeffrey wrote:
We found a bug where executing a DELETE on a self-referential table that
fires triggers can cause a segmentation fault. This is due to a
*use-after-free* of a Postgres plan generated by the referential integrity
module (ri_triggers.c, RI_FKey_cascade_del). The crash occurs if the
Postgres plancache is invalidated (ResetPlanCache) during the execution of
a reentrant RI trigger.
I confirm that this causes a crash. I'm really surprised that this kind
of behavior has survived this long.
However, while I'm not saying that we absolutely shouldn't fix this, I
think it's wrong usage to have BEFORE DELETE triggers delete other rows.
It's generally possible to have other BEFORE triggers after those ones,
that modify the row being deleted in a way that would make that other
trigger delete some _other_ row instead. As I recall, BEFORE triggers
are supposed to change row-local state only; any external state
(deletions or updates in other rows or other tables) should occur in
AFTER triggers, once the state from BEFORE triggers has quiesced.
The docs have this text:
Typically, row-level BEFORE triggers are used for checking or
modifying the data that will be inserted or updated. For example, a
BEFORE trigger might be used to insert the current time into a
timestamp column, or to check that two elements of the row are
consistent. Row-level AFTER triggers are most sensibly used to
propagate the updates to other tables, or make consistency checks
against other tables. The reason for this division of labor is that
an AFTER trigger can be certain it is seeing the final value of the
row, while a BEFORE trigger cannot; there might be other BEFORE
triggers firing after it. [...]
https://www.postgresql.org/docs/18/trigger-definition.html
Would changing you row-deleting BEFORE DELETE triggers to AFTER DELETE
triggers also fix your issue?
Thanks,
--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
Hi hackers
I detected some failing tests and I created a new version of the patch that
fixes those errors in regression tests.
El vie, 29 may 2026 a las 12:32, Lucas Jeffrey (<
lucas.jeffrey@anachronics.com>) escribió:
Show quoted text
Hi hackers,
We found a bug where executing a DELETE on a self-referential table that
fires triggers can cause a segmentation fault. This is due to a
*use-after-free* of a Postgres plan generated by the referential
integrity module (ri_triggers.c, RI_FKey_cascade_del). The crash occurs
if the Postgres plancache is invalidated (ResetPlanCache) during the
execution of a reentrant RI trigger.A reentrant RI_FKey_cascade_del can occur if a table is self-referential
(i.e., it has a foreign key referencing its own primary key) and has BEFORE
DELETE triggers that delete rows from that same table.-
*The first patch* adds a test case that reproduces the segmentation
fault. The crash itself happens in _SPI_execute_plan, but the root
cause is that the plan being executed was prematurely freed by the RI
module.
-*The second patch* fixes ri_triggers.c by introducing reentrancy
guards, which maintain a reference count of plans in execution to prevent
them from being freed while active.Feedback and reviews are welcome.
Best regards,
Lucas Jeffrey