You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@age.apache.org by "BenSpex (via GitHub)" <gi...@apache.org> on 2026/07/10 14:23:01 UTC

[I] Segfault: DETACH DELETE of an edge-connected vertex under FORCE ROW LEVEL SECURITY as a non-superuser [age]

BenSpex opened a new issue, #2474:
URL: https://github.com/apache/age/issues/2474

   ## Environment
   - Apache AGE **1.7.0** (`apache/age:release_PG18_1.7.0`) — **crashes**
   - Apache AGE **1.8.0** (current master, `apache/age:dev_snapshot_PG18`) — **still crashes**
   - PostgreSQL 18.1, official `apache/age` image (age in `shared_preload_libraries`)
   
   ## Summary
   When a **non-superuser** role that is subject to **FORCE ROW LEVEL SECURITY** with a policy on an
   **edge label table** runs `MATCH (n) DETACH DELETE n` where `n` has at least one connected edge, the
   backend crashes with **SIGSEGV (signal 11)** ("server closed the connection unexpectedly"). The
   server restarts and recovers, but the operation cannot complete.
   
   RLS enforcement for Cypher DELETE was added at the executor level in #2309; this appears to be a gap
   in the `DETACH DELETE` implicit-edge-cleanup path when an edge-label RLS policy is in force.
   
   ## Minimal reproduction
   ```sql
   -- === as a SUPERUSER ===
   LOAD 'age';
   SET search_path = ag_catalog, "$user", public;
   SELECT create_graph('g');
   SELECT create_vlabel('g', 'V');
   SELECT create_elabel('g', 'E');
   
   CREATE ROLE rls_user LOGIN PASSWORD 'pw';
   GRANT USAGE ON SCHEMA ag_catalog, g TO rls_user;
   GRANT SELECT, INSERT, UPDATE, DELETE ON g."V", g."E", g."_ag_label_vertex", g."_ag_label_edge" TO rls_user;
   GRANT USAGE ON ALL SEQUENCES IN SCHEMA g TO rls_user;
   ALTER ROLE rls_user SET search_path = "$user", public, ag_catalog;
   
   -- an ordinary agtype-keyed policy, fixed to tid='x' for the repro
   ALTER TABLE g."V" ENABLE ROW LEVEL SECURITY; ALTER TABLE g."V" FORCE ROW LEVEL SECURITY;
   ALTER TABLE g."E" ENABLE ROW LEVEL SECURITY; ALTER TABLE g."E" FORCE ROW LEVEL SECURITY;
   CREATE POLICY p ON g."V" FOR ALL TO rls_user
     USING      (ag_catalog.agtype_access_operator(properties, '"tid"'::agtype) = '"x"'::agtype)
     WITH CHECK (ag_catalog.agtype_access_operator(properties, '"tid"'::agtype) = '"x"'::agtype);
   CREATE POLICY p ON g."E" FOR ALL TO rls_user
     USING      (ag_catalog.agtype_access_operator(properties, '"tid"'::agtype) = '"x"'::agtype)
     WITH CHECK (ag_catalog.agtype_access_operator(properties, '"tid"'::agtype) = '"x"'::agtype);
   
   -- === reconnect as rls_user ===
   -- \c "dbname=... user=rls_user password=pw"
   SET search_path = "$user", public, ag_catalog;
   SELECT * FROM cypher('g', $$ CREATE (:V {tid:'x'})-[:E {tid:'x'}]->(:V {tid:'x'}) $$) AS (v agtype);
   SELECT * FROM cypher('g', $$ MATCH (n) DETACH DELETE n $$) AS (v agtype);  -- <-- SIGSEGV here
   ```
   
   ## Observed
   ```
   server closed the connection unexpectedly
   	This probably means the server terminated abnormally before or while processing the request.
   connection to server was lost
   ```
   Server log: `LOG: client backend (PID …) was terminated by signal 11: Segmentation fault`.
   
   ## Expected
   `DETACH DELETE` deletes the vertex and its connected edges that are visible to the role under RLS,
   without crashing (as it does for a superuser / a role that bypasses RLS).
   
   ## Narrowing (what works vs. crashes, same non-superuser role, same FORCE RLS)
   - ✅ edge `CREATE` (WITH CHECK enforced correctly)
   - ✅ edge `SET` / property `UPDATE`
   - ✅ **edge-only** delete: `MATCH ()-[r:E]->() DELETE r`
   - ✅ **node-only** `DETACH DELETE` (vertex with **no** connected edge)
   - ✅ two-step: `MATCH ()-[r]->() DELETE r` then `MATCH (n) DELETE n`
   - ❌ **`DETACH DELETE` of a vertex WITH a connected edge → SIGSEGV** (the only failing path)
   - ✅ the same `DETACH DELETE` as a superuser (RLS bypass) — so it is specific to RLS-bound roles.
   
   Disabling the RLS policy on the **edge** label (`g."E"`) alone avoids the crash (the vertex-label
   policy is not the trigger), which points at the edge-policy evaluation during DETACH's implicit edge
   deletion.
   
   ## Impact
   Any multi-tenant AGE deployment that isolates tenants via RLS on the graph label tables cannot run
   `DETACH DELETE` (e.g. a projection rebuild) under the tenant-bound runtime role.
   


-- 
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: dev-unsubscribe@age.apache.org.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [I] Segfault: DETACH DELETE of an edge-connected vertex under FORCE ROW LEVEL SECURITY as a non-superuser [age]

Posted by "BenSpex (via GitHub)" <gi...@apache.org>.
BenSpex commented on issue #2474:
URL: https://github.com/apache/age/issues/2474#issuecomment-4936579834

   **Root cause found + fix PR opened: #2475.**
   
   Correction to the repro above: the crash requires the edge-label policy qual to call a **SQL-language function** (e.g. a `STABLE` tenant-accessor wrapping the property lookup). The inline constant qual as written above may NOT crash — a constant-only qual never dispatches into `fmgr_sql`. Replace the `E` policy with:
   
   ```sql
   CREATE FUNCTION current_tid() RETURNS agtype LANGUAGE sql STABLE AS $$ SELECT '"x"'::agtype $$;
   CREATE POLICY p ON g."E" FOR ALL TO rls_user
     USING      (ag_catalog.agtype_access_operator(properties, '"tid"'::agtype) = current_tid())
     WITH CHECK (ag_catalog.agtype_access_operator(properties, '"tid"'::agtype) = current_tid());
   ```
   
   **Mechanism:** `check_for_connected_edges()` runs from `end_cypher_delete()` during executor teardown (`PortalCleanup`) — after the portal's active snapshot has been popped. Evaluating the connected-edge RLS quals there (`check_security_quals` → `ExecQual`) dispatches into `fmgr_sql`/`postquel_start` when the qual contains a SQL function, which needs an active snapshot → `GetActiveSnapshot()` NULL-deref → SIGSEGV. Verified on `release_PG18_1.7.0` and current master; also A/B-verified on a release-style build (-O2, cassert off): unpatched segfaults, patched (#2475, pushes `estate->es_snapshot` for the duration of the scan) deletes cleanly. Full `installcheck` green incl. a new regression test in `regress/sql/security.sql`.


-- 
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: dev-unsubscribe@age.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [I] Segfault: DETACH DELETE of an edge-connected vertex under FORCE ROW LEVEL SECURITY as a non-superuser [age]

Posted by "MuhammadTahaNaveed (via GitHub)" <gi...@apache.org>.
MuhammadTahaNaveed closed issue #2474: Segfault: DETACH DELETE of an edge-connected vertex under FORCE ROW LEVEL SECURITY as a non-superuser
URL: https://github.com/apache/age/issues/2474


-- 
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: dev-unsubscribe@age.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org