java-topology/whitepaper/outreach/pgbouncer-0002.md
russell@unturf.com 652608142a feat: close outreach doc gap — 276 docs (batches 11-16)
All projects with patches now have outreach docs. 276 new docs covering
CWE-407, CWE-312, CWE-362 across C, C++, Java, Python, Go, Rust, C#,
PHP, Ruby, JavaScript, Dart, Erlang, R, and more.

Outreach gap: 276 -> 0.
2026-04-15 13:57:42 -04:00

2.7 KiB
Raw Blame History

PgBouncer — CWE-407 Disclosure Brief (pgbouncer-0002)

2026-04-13 · Patch available — awaiting upstream merge

Finding

O(D) linear scan in find_database() where a linked list of all configured databases is traversed for every connection attempt. Replaced with an AA-tree index for O(log D) lookup.

The Defect

pgbouncer-0002 (PATCHED — MEDIUM): src/objects.c

PgDatabase *find_database(const char *name)
{
    struct List *item, *tmp;
    PgDatabase *db;
    statlist_for_each(item, &database_list) {
        db = container_of(item, PgDatabase, head);
        if (strcmp(db->name, name) == 0)
            return db;
    }
    // also searches autodatabase_idle_list
    statlist_for_each_safe(item, &autodatabase_idle_list, tmp) {
        db = container_of(item, PgDatabase, head);
        if (strcmp(db->name, name) == 0) { ... }
    }
    return NULL;
}

find_database() fires on every new client connection. With D configured databases, each connection costs O(D) string comparisons. Multi-tenant deployments with hundreds of databases compound this for every connection in the pool.

Complexity Proof

At D=500 databases and C=10,000 connections per second:

  • Defective: 10,000 × 250 (avg scan) = 2,500,000 string comparisons per second
  • Fixed: 10,000 × log₂(500) ≈ 90,000 comparisons per second
  • ~28× op reduction.

Impact

PgBouncer is deployed at scale in multi-tenant PostgreSQL environments (SaaS platforms, cloud database providers). Hundreds of configured databases with thousands of connections per second make find_database() a hot path. The AA-tree index mirrors the existing user_tree pattern already used for user lookup.

The Fix

Add a parallel AATree db_name_tree index alongside the existing database_list, maintained at add_database() time:

// Before — O(D) linked list scan:
statlist_for_each(item, &database_list) {
    db = container_of(item, PgDatabase, head);
    if (strcmp(db->name, name) == 0) return db;
}

// After — O(log D) AA-tree lookup:
node = aatree_search(&db_name_tree, (uintptr_t)name);
if (node) {
    db = container_of(node, PgDatabase, name_tree_node);
    return db;
}

Patch

Fix available: defects/pgbouncer-0002/patch/pgbouncer-0002.patch

Single-file patch in objects.c.

What We Ask

A patch is ready for review.

  1. Confirm receipt and assign a tracking reference (pgbouncer/pgbouncer).
  2. Assess severity — fires on every connection attempt, compounds with database count.
  3. Coordinate a disclosure date — we target 90 days from first contact.
  4. We will credit the PgBouncer team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.