Technical Analysis: Use-After-Free in Property Graph Orphan Static Lists on Transaction Abort
Overview
This thread presents a short-lived bug report that was ultimately retracted. The submitter reported a use-after-free vulnerability in PostgreSQL's dependency cascade machinery related to the SQL/PGQ Property Graph feature, but later acknowledged the issue existed only in their local patch set and not in upstream HEAD.
The Reported Problem (As Described)
Core Issue: Static Pointer Lifetime vs. Memory Context Lifetime
The reported bug follows a classic PostgreSQL memory management anti-pattern: static (file-scoped) pointers holding references to memory allocated in a transient MemoryContext.
The claimed scenario:
dependency.cmaintains staticList *pointers (propgraph_orphan_label_graphidsandpropgraph_orphan_property_graphids) that accumulate OIDs of property graph elements orphaned during a dependency cascade.- These lists are allocated in
TopTransactionContextduringdeleteOneObject(). - The lists are only reset (set to NIL) on the success path — specifically in
run_deferred_propgraph_orphan_cleanup(), called at the end ofperformDeletion()/performMultipleDeletions(). - If the transaction aborts (e.g., due to
statement_timeout) after the lists have been populated but before cleanup runs,AbortTransaction()destroysTopTransactionContext, freeing the underlying memory. - The static pointers still point to the now-freed memory.
- A subsequent property graph cascade in the same backend session calls
list_append_unique_oid()with the dangling pointer, which dereferences freed memory.
Why This Pattern Is Dangerous
This is a well-known hazard in PostgreSQL's memory management model. The rules are:
- Memory allocated in a transaction-scoped context is valid only for the lifetime of that transaction.
- Static/global pointers that survive transaction boundaries must be registered with transaction callbacks (
RegisterXactCallback) or reset in appropriate cleanup paths (e.g.,AtEOXact_*functions, orPG_FINALLYblocks).
The List infrastructure in PostgreSQL uses a tagged-union header (IsOidList(list) checks list->type == T_OidList), so dereferencing freed memory either triggers an assertion failure or silently corrupts the heap — the latter being far worse.
Proposed Fix
The submitter proposed resetting both static pointers to NIL in PG_FINALLY() blocks wrapping performDeletion() and performMultipleDeletions(). This ensures cleanup occurs on both success and error paths. This is the standard PostgreSQL idiom for this pattern — PG_FINALLY blocks execute regardless of whether an error was thrown, making them ideal for resource cleanup that must happen unconditionally.
Architectural Context: Property Graphs (SQL/PGQ)
The SQL/PGQ property graph feature (being developed for PG19) introduces CREATE PROPERTY GRAPH which defines graph views over existing tables. When a base table is dropped with CASCADE, the property graph's vertex/edge tables and associated labels become orphaned. The dependency system needs to track these orphans and clean them up in a deferred fashion — hence the static accumulator lists.
The deferred cleanup pattern is necessary because property graph cleanup may involve catalog modifications that shouldn't happen mid-cascade (to avoid re-entrant catalog access or deadlock scenarios).
Resolution
The thread was quickly resolved as a false alarm. Ashutosh Bapat attempted to reproduce and could not find the referenced static variables in dependency.c at HEAD. The original submitter then acknowledged they had been working with local patches that introduced this code, and the issue does not exist in upstream PostgreSQL HEAD.
Technical Lessons
Despite being retracted, the thread illustrates an important pattern:
-
Static pointers to transaction-scoped memory are a recurring source of bugs in PostgreSQL. The correct mitigation is either: (a) allocate in a longer-lived context and free explicitly, (b) use
PG_FINALLYorAtEOXactcallbacks to reset pointers, or (c) useTopMemoryContextwith explicit lifecycle management. -
Fuzz testing with statement_timeout is an effective technique for finding cleanup-path bugs. The timeout fires asynchronously via
CHECK_FOR_INTERRUPTS(), simulating mid-operation failures that exercise error recovery paths. -
Property Graph (SQL/PGQ) integration with the dependency system will need careful attention to these lifetime issues as the feature matures toward commit.