java-topology/tools/tickets/defects/tor-0001.md
russell@unturf.com db29a08762 undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
2026-03-26 19:48:18 -04:00

2.5 KiB

id repo file line status severity complexity pattern
tor-0001 torproject/tor src/feature/nodelist/routerlist.c 2179 unpatched MEDIUM O(R²) — R = number of router descriptors in batch; shrinks as items removed smartlist_contains_string on smartlist_t inside SMARTLIST_FOREACH_BEGIN over same-size list

Description

router_load_routers_from_string() validates received router descriptors against a list of requested fingerprints using smartlist_contains_string:

SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) {
    if (requested_fingerprints) {
        base16_encode(fp, ...);
        if (smartlist_contains_string(requested_fingerprints, fp)) {  // O(|fp_list|)
            smartlist_string_remove(requested_fingerprints, fp);
        }
        ...
    }
} SMARTLIST_FOREACH_END(ri);

smartlist_contains_string (smartlist.c:97) is a linear scan:

for (i=0; i < sl->num_used; i++)
    if (strcmp((const char*)sl->list[i], element) == 0)
        return 1;

requested_fingerprints starts at size R (= len(routers)). Each match removes one entry, so total comparisons = R + (R-1) + ... = O(R²/2). Same pattern occurs in the extrainfo path at line 2263-2295.

Call context

router_load_routers_from_string() is called when processing downloaded router descriptors from directory servers. For directory caches and authorities, R can be in the thousands (7,000-8,000 relays in the full consensus).

Activation: any Tor node that fetches router descriptors — which is every relay and every client at startup and during periodic refresh.

Fix

Replace smartlist_t *requested_fingerprints (hex strings) with digestmap_t * (raw 20-byte digest → dummy pointer). Tor already uses digestmap_t extensively in the same file (lines 2689, 2717, 2802). Fix:

// Before: smartlist_t *requested_fingerprints  (hex strings, O(n) contains)
// After:  digestmap_t *requested_fingerprints  (raw digests, O(1) lookup)

// Lookup:
if (digestmap_get(requested_fingerprints, ri->cache_info.signed_descriptor_digest)) {
    digestmap_remove(requested_fingerprints, ri->cache_info.signed_descriptor_digest);
}

The hex encoding step (base16_encode) can be removed since we key on raw bytes. smartlist_string_remove calls also eliminated. Apply to both routers path (line 2179) and invalid_digests / extrainfo_list paths (lines 2216, 2295).

Work items

  • patch
  • unit test (operation count: contains calls before/after on batch R=1000)
  • integration test (tor relay startup, descriptor fetch)