# OpenSMTPD — CWE-407 Disclosure Brief **2026-03-27 · Patch available — awaiting upstream merge** ## Finding One O(n²) defect in OpenSMTPD's ruleset matching. `ruleset_match()` uses `TAILQ_FOREACH` to linearly walk all R rules for every envelope processed, causing O(R) per message and O(R×M) under load. Patch ready for upstream review. ## The Defects **opensmtpd-0001 (PATCHED — HIGH):** `ruleset.c:234` ```c /* Inside ruleset_match() — per envelope: */ TAILQ_FOREACH(rule, &env->sc_rules, r_entry) { /* O(R) full rule scan per envelope */ if (rule_matches(rule, envelope)) { return rule; } } ``` `TAILQ_FOREACH` walks all R rules linearly on every envelope match. For M messages and R rules: **O(R × M) total**. **Measured ratio: 146×.** ## Complexity Proof For R=146 rules: - Per envelope: O(R) full scan - Fixed: domain dispatch dict → O(1) for domain-matched rules - **146× measured ratio.** ## Impact All OpenSMTPD deployments with complex ruleset configurations. OpenSMTPD is the default MTA on OpenBSD and is used on many security-focused servers. Rule evaluation runs on every message processed — incoming, outgoing, and forwarded. Servers with large rulesets (many virtual domains, complex filtering rules) hit worst case on every message. ## The Fix Pre-build a domain-indexed dispatch dictionary from the rules at startup: ```c /* Before */ TAILQ_FOREACH(rule, &env->sc_rules, r_entry) { if (rule_matches(rule, envelope)) return rule; } /* After */ /* CWE-407 fix: domain dispatch dict for O(1) initial lookup, then filtered scan. */ struct rule_list *domain_rules = dict_get(&env->sc_rules_by_domain, domain); if (domain_rules) { TAILQ_FOREACH(rule, domain_rules, r_entry) { if (rule_matches(rule, envelope)) return rule; } } ``` ## Patch `defects/opensmtpd/patch/opensmtpd-0001-ruleset-dispatch-dict.patch` ## What We Ask 1. Confirm receipt and assign a GitHub Security Advisory or issue reference. 2. Validate the patch against your ruleset matching test suite. 3. Assess CVE eligibility — fires on every message processed with a large ruleset. 4. Coordinate a disclosure date — we are targeting 90 days from first contact. Contact: see cover email. This brief is confidential until coordinated disclosure.