BenSpex opened a new pull request, #2475:
URL: https://github.com/apache/age/pull/2475

   ## Summary
   
   Fixes #2474.
   
   A non-superuser subject to `FORCE ROW LEVEL SECURITY` crashed the backend 
with **SIGSEGV** when running `MATCH (n) DETACH DELETE n` 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). 
Reproduces on `release_PG18_1.7.0` and current `master`.
   
   ## Root cause / mechanism
   
   AGE deletes a vertex's connected edges in `check_for_connected_edges()` 
(`src/backend/executor/cypher_delete.c`), 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 #2309): 
`check_for_connected_edges()` compiles the edge label's security quals and 
evaluates them per candidate edge via `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**. (A cassert build trips the 
`Assert(ActiveSnapshotSet())` in `postquel_start` first — that assert is 
exactly the "caller should have ensured a suitable snapshot is active" 
contract.)
   
   Backtrace (assert build) at the crash site:
   
   ```
   Assertion failed: ActiveSnapshotSet(), functions.c
     postquel_start (functions.c)
     fmgr_sql
     ExecQual -> check_security_quals   (cypher_utils.c)
     process_edges_by_index             (cypher_delete.c)
     check_for_connected_edges          (cypher_delete.c)
     end_cypher_delete                  (cypher_delete.c)
     ExecEndCustomScan -> ExecEndPlan -> standard_ExecutorEnd
   ```
   
   This explains the full narrowing in the issue:
   
   - **vertex path** (`process_delete_list`) runs during *normal execution* 
while a snapshot is active → fine;
   - **edge-only `DELETE`** and **node-only `DETACH DELETE`** never evaluate an 
edge qual at teardown → fine;
   - **superuser** bypasses RLS → fine;
   - a **constant-only** edge policy never enters `fmgr_sql` → fine;
   - 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 returning. The scans 
themselves already pass `es_snapshot` explicitly, 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.
   
   ```c
   if (!ActiveSnapshotSet())
   {
       PushActiveSnapshot(estate->es_snapshot);
       pushed_snapshot = true;
   }
   ...
   if (pushed_snapshot)
       PopActiveSnapshot();
   ```
   
   ## 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.
   
   ## Verification
   
   - **A/B on an identical release-style build** (PG18, `-O2`, 
`--enable-cassert` off): unpatched → `client backend ... was terminated by 
signal 11: Segmentation fault`; patched → deletes cleanly, no crash.
   - `make installcheck` against PG18 (`--enable-cassert`) — **all 43 tests 
pass**, including the new case.
   - The issue's function-based repro no longer crashes and deletes correctly.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to