Fix segfault on DETACH DELETE under RLS with a function-based edge policy (#2474)#2475
Open
BenSpex wants to merge 1 commit into
Open
Fix segfault on DETACH DELETE under RLS with a function-based edge policy (#2474)#2475BenSpex wants to merge 1 commit into
BenSpex wants to merge 1 commit into
Conversation
…licy A non-superuser subject to row-level security crashed the backend with SIGSEGV when running DETACH DELETE on a vertex that has a connected edge, if the edge label carried an RLS policy whose USING/WITH CHECK qual invokes a function (e.g. a STABLE tenant/owner accessor). Reported in issue apache#2474; reproduces on 1.7.0 and master. Mechanism: AGE deletes a vertex's connected edges in check_for_connected_edges(), which is called from end_cypher_delete() -- i.e. during executor shutdown (ExecEndPlan, reached via PortalCleanup -> ExecutorEnd when the portal is dropped). By that point the portal's active snapshot has already been popped, so the active-snapshot stack is empty. RLS for Cypher DELETE is enforced at the executor level (added in apache#2309): check_for_connected_edges() compiles the edge label's security quals and evaluates them per candidate edge with check_security_quals() -> ExecQual(). When the qual calls a SQL-language function, ExecQual() dispatches into fmgr_sql() -> postquel_start(), which runs the function's query and reads the current snapshot with GetActiveSnapshot(). With no snapshot on the stack that is a NULL-pointer dereference -> SIGSEGV (an assert build trips the Assert(ActiveSnapshotSet()) in postquel_start first). This is why the narrowing in the report holds: the vertex path (process_delete_list) runs during normal execution while a snapshot is active; edge-only DELETE and node-only DETACH DELETE never evaluate an edge qual at teardown; a superuser bypasses RLS; and a constant-only edge policy never enters fmgr_sql, so only DETACH DELETE of an edge-connected vertex under a function-bearing edge policy trips it. Fix: Ensure an active snapshot for the duration of the connected-edge scan in check_for_connected_edges(). es_snapshot is still valid there (the EState is not torn down until this returns) and is the correct snapshot for reading the edges, so push it if none is active and pop it before return. The scans themselves already pass es_snapshot explicitly, so this only affects RLS qual evaluation; non-RLS paths are unchanged. On the error path (e.g. an RLS denial raised mid-scan) transaction abort resets the active-snapshot stack, so the unpaired push is cleaned up. Test: Adds a regression to the security suite (PART 11b): an edge label policy whose qual calls a STABLE SQL function, then DETACH DELETE of an edge-connected vertex as the RLS-bound role. It must delete the vertex and its edge (leaving the other endpoint) instead of crashing. The existing PART 11 uses a constant-only edge policy, which does not exercise the snapshot-dependent function path.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #2474.
A non-superuser subject to
FORCE ROW LEVEL SECURITYcrashed the backend with SIGSEGV when runningMATCH (n) DETACH DELETE non a vertex that has a connected edge, if the edge label carried an RLS policy whoseUSING/WITH CHECKqual invokes a function (e.g. aSTABLEtenant/owner accessor). Reproduces onrelease_PG18_1.7.0and currentmaster.Root cause / mechanism
AGE deletes a vertex's connected edges in
check_for_connected_edges()(src/backend/executor/cypher_delete.c), which is called fromend_cypher_delete()— i.e. during executor shutdown (ExecEndPlan, reached viaPortalCleanup→ExecutorEndwhen the portal is dropped). By that point the portal's active snapshot has already been popped, so the active-snapshot stack is empty.RLS for Cypher DELETE is enforced at the executor level (added in #2309):
check_for_connected_edges()compiles the edge label's security quals and evaluates them per candidate edge viacheck_security_quals()→ExecQual(). When the qual calls a SQL-language function,ExecQual()dispatches intofmgr_sql()→postquel_start(), which runs the function's query and reads the current snapshot withGetActiveSnapshot(). With no snapshot on the stack that is a NULL-pointer dereference → SIGSEGV. (A cassert build trips theAssert(ActiveSnapshotSet())inpostquel_startfirst — that assert is exactly the "caller should have ensured a suitable snapshot is active" contract.)Backtrace (assert build) at the crash site:
This explains the full narrowing in the issue:
process_delete_list) runs during normal execution while a snapshot is active → fine;DELETEand node-onlyDETACH DELETEnever evaluate an edge qual at teardown → fine;fmgr_sql→ fine;DETACH DELETEof an edge-connected vertex under a function-bearing edge policy trips it.Fix
Ensure an active snapshot for the duration of the connected-edge scan in
check_for_connected_edges().es_snapshotis still valid there (theEStateis not torn down until this returns) and is the correct snapshot for reading the edges, so push it if none is active and pop it before returning. The scans themselves already passes_snapshotexplicitly, so this only affects RLS qual evaluation; non-RLS paths are unchanged. On an error path (e.g. an RLS denial raised mid-scan) transaction abort resets the active-snapshot stack, so the unpaired push is cleaned up.Test
Adds a regression to the security suite (
PART 11b): an edge-label policy whose qual calls aSTABLESQL function, thenDETACH DELETEof an edge-connected vertex as the RLS-bound role. It must delete the vertex and its edge (leaving the other endpoint) instead of crashing. The existingPART 11uses a constant-only edge policy, which does not exercise the snapshot-dependent function path.Verification
-O2,--enable-cassertoff): unpatched →client backend ... was terminated by signal 11: Segmentation fault; patched → deletes cleanly, no crash.make installcheckagainst PG18 (--enable-cassert) — all 43 tests pass, including the new case.