Thread

  1. [PATCH] Fix use-after-free of propgraph orphan static lists on xact abort

    SATYANARAYANA NARLAPURAM <satyanarlapuram@gmail.com> — 2026-05-12T17:52:52Z

    Hi hackers,
    
    My PG19 fuzz tests detected the $subject issue in dependency.c.
    
    Repro: It is not 100% deterministic because of the timeout, if hard to
    follow can write
    an injection point test.
    
    \set ON_ERROR_STOP off
    
    CREATE TABLE n1(id int PRIMARY KEY, name text);
    
    -- Many indexes ensure the cascade takes enough time after the propgraph
    -- element's deleteOneObject() for statement_timeout to fire.
    DO $$ BEGIN
      FOR i IN 1..50 LOOP
        EXECUTE format('CREATE INDEX idx_%s ON n1 (id) WHERE id > %s', i, i);
      END LOOP;
    END $$;
    CREATE PROPERTY GRAPH pg1 VERTEX TABLES (n1 LABEL l1 PROPERTIES(id, name));
    
    CREATE TABLE n2(id int PRIMARY KEY, name text);
    CREATE PROPERTY GRAPH pg2 VERTEX TABLES (n2 LABEL l2 PROPERTIES(id, name));
    
    SET statement_timeout = '3ms';
    DROP TABLE n1 CASCADE;
    RESET statement_timeout;
    
    DROP TABLE n2 CASCADE;
    
    SELECT 1 AS status;
    
    The static lists propgraph_orphan_label_graphids and
    propgraph_orphan_property_graphids
    in dependency.c are allocated in TopTransactionContext during
    deleteOneObject().  They are
    only reset on the success path of
    performDeletion()/performMultipleDeletions(), in
    run_deferred_propgraph_orphan_cleanup(). If a transaction aborts after the
    lists have been
    populated, for example due to statement_timeout, or any other error thrown
    during the cascade),
    AbortTransaction() frees TopTransactionContext but the static pointers
    remain set.  A subsequent
    property graph cascade in the same backend session then passes the dangling
    pointers to
    list_append_unique_oid(), which walks freed memory.  With assertions
    enabled this trips
    
      TRAP: failed Assert("IsOidList(list)"), File: "list.c", Line: 726
    
    Without assertions this silently corrupts memory. Fix by resetting both
    statics to NIL in the PG_FINALLY() blocks of
    performDeletion() and performMultipleDeletions(), which execute on
    both success and error paths.
    
    Thanks,
    Satya