diff --git a/SCAN-TODO.md b/SCAN-TODO.md index 06f442416..7da456430 100644 --- a/SCAN-TODO.md +++ b/SCAN-TODO.md @@ -10,7 +10,7 @@ Rule: clone, scan, delete clone after. Keep disk under 90%. - [x] PgBouncer (C, PostgreSQL connection pooler) — pgbouncer-0001 MOAD-0004 CWE-312 SCRAM verifier logged at slog_debug; pgbouncer-0002 MOAD-0001 CWE-407 find_database() O(D) linear scan per connection 100x at D=100; MOADs 0002/0003/0005 CLEAN (single-threaded libevent) - [x] Suricata (C, IDS/IPS) — suricata-0001 (CWE-407 threshold SID lookup O(T×S), CWE-312 auth header logging); suricata-0002 (CWE-407 EveHttpLogJSONHeaders O(H×F=53) per tx, 53x); MOADs 0003/0005 CLEAN - [x] ClamAV (C, antivirus engine) — clamav-0001 MOAD-0004 CWE-312 proxy password logged verbatim on curl failure; MOAD-0001/0002/0003/0005 CLEAN (AC trie, BM hash, mutex-protected cache) -- [ ] Snort (C, IDS/IPS) +- [x] Snort (C++, IDS/IPS) — snort3-0001 MOAD-0001 CWE-407 service_candidates std::find dedup O(M*C) per packet in AppID ServiceDiscovery 72x at M=10000; snort3-0002 MOAD-0001 CWE-407 CHP match_tally std::find_if O(M*T) per HTTP packet 48x at T=100; MOADs 0002/0003/0004/0005 CLEAN - [ ] WireGuard (deeper, Go userspace tools) - [ ] Thunderbird (C++, email client, undo/history) - [ ] Wine (C, Windows compatibility layer) diff --git a/defects/snort3-0001/TICKET.md b/defects/snort3-0001/TICKET.md new file mode 100644 index 000000000..f3b2d7ef7 --- /dev/null +++ b/defects/snort3-0001/TICKET.md @@ -0,0 +1,60 @@ +# snort3-0001: CWE-407 — ServiceDiscovery service_candidates std::find dedup O(M*C) + +## Target + +Snort 3 network IDS/IPS: `src/network_inspectors/appid/service_plugins/service_discovery.cc` + +## Defect + +`match_by_pattern()` and `get_port_based_services()` both use `std::find` to dedup +`asd.service_candidates` (a `std::vector`). These functions are called +from `get_next_service()`, invoked once per packet during the AppID service discovery +phase while `asd.service_disco_state != APPID_DISCO_STATE_FINISHED`. + +In `match_by_pattern()`: for each of M pattern match callbacks returned by the +Aho-Corasick engine, `std::find` walks all C existing candidates to check for +duplicates. Total work is O(M * C) per packet. + +In `get_port_based_services()` (two-port midstream variant): for each detector in +port2's list, `std::find` walks port1's candidates. Total work is O(P1 * P2) where P1, +P2 are detector counts for each port. + +With enterprise OpenAppID deployments shipping ~2900 Lua detectors, ports with many +registered detectors and packets matching many patterns produce measurable overhead on +every AppID-inspected packet. + +## Fix + +Seed an `std::unordered_set seen` from the existing candidates before +the loop, then use `seen.insert().second` for O(1) amortized dedup. Replaces both +`std::find` call sites. + +## Complexity + +O(M*C) -> O(M + C) per packet in match_by_pattern; O(P1*P2) -> O(P1 + P2) in +get_port_based_services. Measured: 72x op-count reduction at M=10000. + +## Severity + +HIGH. AppID service discovery runs on every packet of every flow until the service is +identified. In high-traffic environments (>10 Gbps), per-packet overhead compounds across +millions of concurrent flows. + +## Patch + +`defects/snort3-0001/patch/snort3-0001.patch` + +## Test + +`defects/snort3-0001/test/Snort3ServiceCandidateDedupTest.java` — Java simulation. +N=10000: defective 1069.7ms, fixed 14.8ms, ratio 72x. ALL PASS. + +## All 5 MOAD Results for Snort 3 + +| MOAD | Status | Notes | +|------|--------|-------| +| 0001 (CWE-407) | DEFECT x2 | snort3-0001 (service_candidates, this); snort3-0002 (CHP match_tally) | +| 0002 (Intertangle) | CLEAN | SnortConfig immutable at runtime; per-thread flow state; no god object | +| 0003 (Leaked Context) | CLEAN | THREAD_LOCAL used correctly for per-thread packet stats; cleared per-packet | +| 0004 (CWE-312) | CLEAN | No sensitive headers logged verbatim; HTTP extractor omits Authorization field | +| 0005 (Thundering Herd) | CLEAN | LruCacheShared uses std::mutex lock_guard; LruCacheLocal is per-thread |