From 7eed40b73cb564bd3575e0a40ee17ed795b77cef Mon Sep 17 00:00:00 2001 From: Matheus Alcantara Date: Thu, 24 Sep 2026 11:33:01 -0300 Subject: [PATCH v2 2/2] Check EXECUTE on functions invoked by the batched RI fast path The RI fast-path EXECUTE-privilege check added ri_CheckFunctionPermissions() to verify ACL_EXECUTE on the two functions the fast path runs on the FK values but wired it only into the per-row path, ri_FastPathCheck(). The batched path used by ordinary DML, ri_FastPathBatchFlush(), invokes the same two functions through build_index_scankeys() and the SK_SEARCHARRAY array probe, so a plain INSERT, UPDATE, or COPY still skipped the check that the SPI path performs during executor startup. The privilege of the referenced table's owner is what applies, so a role lacking EXECUTE on a user-defined equality operator or cast function could reach it via routine DML. Call ri_CheckFunctionPermissions() once per flush in ri_FastPathBatchFlush(), next to the existing ri_CheckPermissions() relation check and under the same switch to the referenced table's owner, so the batched path performs the same checks as the per-row and SPI paths. --- src/backend/utils/adt/ri_triggers.c | 1 + src/test/regress/expected/foreign_key.out | 20 +++++++++++++++++++- src/test/regress/sql/foreign_key.sql | 18 +++++++++++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index e584df17980..2ea4ac04342 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -3141,6 +3141,7 @@ ri_FastPathBatchFlush(RI_FastPathEntry *fpentry, Relation fk_rel, ri_populate_fastpath_metadata(riinfo, fk_rel, idx_rel); } Assert(riinfo->fpmeta); + ri_CheckFunctionPermissions(riinfo, riinfo->fpmeta); /* * Take our own reference to the metadata for the duration of the flush. diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index f5b10c73c10..6cd6becf13c 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -3807,7 +3807,8 @@ DROP FUNCTION fp_auto_pk; -- functions it invokes on the FK values: the equality operator's function -- and, for a cross-type FK, the implicit cast function. Validation of a new -- constraint by a role that cannot use the bulk check (RLS is enabled on the --- referenced table) runs per-row checks through the fast path. +-- referenced table) runs per-row checks through the fast path, and ordinary +-- DML runs the batched fast path; both are exercised below. -- -- Wrapped with BEGIN...ROLLBACK to ensure opclasses used here don't interfere -- with nearby tests. @@ -3882,6 +3883,23 @@ ALTER TABLE fktable_acl_cast ERROR: permission denied for function fkenum_to_int4 CONTEXT: SQL statement "SELECT 1 FROM "public"."pktable_acl_cast_part" x WHERE "a" OPERATOR(pg_catalog.=) $1::pg_catalog.int4 FOR KEY SHARE OF x" ROLLBACK TO SAVEPOINT s; +-- Ordinary DML uses the batched fast path, distinct from the per-row path that +-- validation exercises above. Create each constraint NOT VALID so its +-- functions are first reached when a later INSERT is checked; the EXECUTE +-- check, made as the PK owner, then fails before the probe even though the +-- inserted value satisfies the FK. +SAVEPOINT s; +ALTER TABLE fktable_acl_op + ADD FOREIGN KEY (a) REFERENCES pktable_acl_op (a) NOT VALID; +INSERT INTO fktable_acl_op VALUES (1); -- fails +ERROR: permission denied for function regress_ri_int4eq +ROLLBACK TO SAVEPOINT s; +SAVEPOINT s; +ALTER TABLE fktable_acl_cast + ADD FOREIGN KEY (a) REFERENCES pktable_acl_cast (a) NOT VALID; +INSERT INTO fktable_acl_cast VALUES ('one'); -- fails +ERROR: permission denied for function fkenum_to_int4 +ROLLBACK TO SAVEPOINT s; RESET ROLE; ROLLBACK; -- A STABLE cast used by an FK check must see changes made by earlier AFTER diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index bd5a47a836c..7b101bf2ba1 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -2772,7 +2772,8 @@ DROP FUNCTION fp_auto_pk; -- functions it invokes on the FK values: the equality operator's function -- and, for a cross-type FK, the implicit cast function. Validation of a new -- constraint by a role that cannot use the bulk check (RLS is enabled on the --- referenced table) runs per-row checks through the fast path. +-- referenced table) runs per-row checks through the fast path, and ordinary +-- DML runs the batched fast path; both are exercised below. -- -- Wrapped with BEGIN...ROLLBACK to ensure opclasses used here don't interfere -- with nearby tests. @@ -2846,6 +2847,21 @@ SAVEPOINT s; ALTER TABLE fktable_acl_cast ADD FOREIGN KEY (a) REFERENCES pktable_acl_cast_part (a); -- fails (SPI path message) ROLLBACK TO SAVEPOINT s; +-- Ordinary DML uses the batched fast path, distinct from the per-row path that +-- validation exercises above. Create each constraint NOT VALID so its +-- functions are first reached when a later INSERT is checked; the EXECUTE +-- check, made as the PK owner, then fails before the probe even though the +-- inserted value satisfies the FK. +SAVEPOINT s; +ALTER TABLE fktable_acl_op + ADD FOREIGN KEY (a) REFERENCES pktable_acl_op (a) NOT VALID; +INSERT INTO fktable_acl_op VALUES (1); -- fails +ROLLBACK TO SAVEPOINT s; +SAVEPOINT s; +ALTER TABLE fktable_acl_cast + ADD FOREIGN KEY (a) REFERENCES pktable_acl_cast (a) NOT VALID; +INSERT INTO fktable_acl_cast VALUES ('one'); -- fails +ROLLBACK TO SAVEPOINT s; RESET ROLE; ROLLBACK; -- 2.50.1 (Apple Git-155)