PostgreSQL — CWE-407 Analysis ============================== .. contents:: :local: Overview -------- PostgreSQL's query planner is a cost-based optimizer that performs join ordering, equivalence class reasoning, target list construction, and join elimination. The planner is implemented in C and uses PostgreSQL's own ``List`` type (a singly-linked list of ``void *`` cells) with O(n) membership functions: ``list_member()``, ``list_member_ptr()``, ``list_member_int()``, ``list_member_oid()``, and ``list_member_xid()``. **Status: 5 confirmed defects — all unpatched** The scanner used pg-specific patterns (``list_member*`` membership tests, ``foreach`` loop macro) rather than generic C patterns. 58 candidates were identified; 5 confirmed as true CWE-407 defects in hot planner paths. PostgreSQL developers independently acknowledged the O(n²) risk in ``execExpr.c:626`` (patched locally with bitmapset) — confirming the pattern is real and known to the project. Confirmed Defects ----------------- .. list-table:: :header-rows: 1 :widths: 15 35 20 15 15 * - ID - File:Line - Pattern - Complexity - Severity * - postgresql-0001 - ``optimizer/util/tlist.c:812`` - ``tlist_member`` in sort/group labeling foreach - O(sort_keys × width) - HIGH * - postgresql-0002 - ``optimizer/prep/preptlist.c:180,206,316`` - ``tlist_member`` in MERGE/UPDATE target dedup (3 loops) - O(W² × C²) for MERGE - HIGH * - postgresql-0003 - ``optimizer/path/equivclass.c:1041`` - ``list_member`` in equivalence class var matching - O(M × E) per class - HIGH * - postgresql-0004 - ``optimizer/util/analyzejoins.c:1914`` - ``list_member`` in join elimination expr merge - O(E²) per eliminated join - HIGH * - postgresql-0005 - ``nodes/list.c:1077-1478`` - ``list_union``, ``list_intersect``, ``list_difference`` - O(|a| × |b|) per call - MEDIUM Developer Acknowledgment ------------------------ A PostgreSQL developer commented in ``execExpr.c:626``:: /* Using list_member_int() tests, but that risks O(N^2) behavior with many columns */ That site was subsequently patched with ``bitmapset``. The five sites above were not caught in the same pass — confirming they are unaddressed. Threat Model ------------ All five defects are triggered at **planning time** — before execution begins. For OLTP queries the planner is amortized over many executions via plan caching. For ad-hoc analytical queries, uncached plans are regenerated on every execution. Worst-case scenarios: - **MERGE with many WHEN clauses** (postgresql-0002): PostgreSQL 15+ feature, upsert-heavy workloads. W=50 WHEN clauses × C=20 columns → O(W²×C²) ≈ 10⁶ membership tests per plan. - **Star schema with many equivalence members** (postgresql-0003): analytical databases with wide dimension tables. K equivalence classes × M=20 members × E=20 exprvars → O(K×400). - **Views with many columns + self-join** (postgresql-0004): joins against wide views trigger join elimination on every query. E=50 projected columns → 2,500 list_member calls. Fix Pattern ----------- All five defects follow the same fix pattern: 1. Before the ``foreach`` loop, build an O(1) membership structure: - For Var nodes: ``Bitmapset *`` keyed on ``varattno`` (already used by PG) - For expression pointer identity: pointer-identity hash set (palloc'd) - For int/oid/xid variants: ``Bitmapset *`` or integer hash set 2. Replace ``list_member*(list, item)`` in the loop body with O(1) set lookup. 3. Free the temporary structure after the loop (palloc memory is freed with context). PostgreSQL already has ``bitmapset.h`` and ``HTAB`` (hash table) as first-class utilities — no new dependencies required. False Positives Triaged ----------------------- 53 of 58 scanner candidates were false positives: - **``execExpr.c:626``** — already patched by PG developers with bitmapset (O(1) check) - **``nodeHashjoin.c``**, **``nodeNestloop.c``** — list traversal is the join execution itself, not a membership test inside traversal - **``ruleutils.c``**, **``deparse.c``** — display/deparse code, cold path - **``pg_dump``** — dump utility, not query planner, not performance-critical - Remaining 48 candidates — bounded input (typically < 10 elements) or genuinely cold paths Scan Details ------------ Scanner: ``tools/scans/postgresql.sh`` Strategy: Lead with ``list_member*`` call sites, confirm ``foreach`` loop context within ±20 lines. Inverted from generic C scanner because PG uses its own list API (``foreach``/``lfirst``) rather than ``std::find`` or ``.count()``. Directories scanned: - ``src/backend/optimizer`` — query planner - ``src/backend/nodes`` — node utilities including ``list.c`` - ``src/backend/rewrite`` — rule rewriter - ``src/backend/executor`` — execution engine - ``src/backend/parser`` — SQL parser - ``src/backend/planner`` — (alias for optimizer in older PG layouts) References ---------- * Tickets: postgresql-0001 through postgresql-0005 * Scanner: ``tools/scans/postgresql.sh`` * Developer acknowledgment: ``execExpr.c:626`` comment (bitmapset fix) * PostgreSQL List API: ``src/include/nodes/pg_list.h``