2.7 KiB
postfix-0001 — Quadratic recipient domain resolution via string_list_match
Target: Postfix (vdukhovni/postfix mirror of postfix.org) Severity: MEDIUM CWE: CWE-407 (Algorithmic Complexity — Quadratic) Status: PATCHED (patch/postfix-0001.patch)
Summary
resolve_addr() and resolve_class() in trivial-rewrite/resolve.c call
string_list_match() for each of virtual_alias_domains, virtual_mailbox_domains,
and relay_domains on every RCPT-TO command and every queued recipient during
delivery. string_list_match() iterates linearly through an ARGV of inline domain
patterns (O(K) per call). With K inline domain patterns and M recipients per message,
the total cost is O(K × M).
At a shared hosting provider with K=500 inline virtual domains and a mailing list message with M=1000 recipients, this is 500 000 string comparisons per message, all on the critical-path of the trivial-rewrite daemon.
Location
postfix/src/trivial-rewrite/resolve.c
line 161 string_list_match(virt_alias_doms, domain)
line 167 string_list_match(virt_mailbox_doms, domain)
line 173 string_list_match(relay_domains, domain)
line 495 string_list_match(virt_alias_doms, rcpt_domain)
line 498 string_list_match(virt_mailbox_doms, rcpt_domain)
line 528 string_list_match(virt_mailbox_doms, rcpt_domain)
line 631 string_list_match(virt_alias_doms, rcpt_domain)
line 635 string_list_match(virt_mailbox_doms, rcpt_domain)
postfix/src/global/match_list.c
match_list_match() — iterates list->patterns->argv linearly O(K)
Root Cause
string_list_match is an alias for match_list_match. When all patterns are
inline strings (not type:table references), match_list_match does a
for (cpp = list->patterns->argv; ...; cpp++) linear scan — a strcmp per element.
The MATCH_LIST structure stores patterns in an ARGV (plain pointer array) with
no hash index.
Fix
Pre-build a HTABLE (Postfix's hash table) from the MATCH_LIST patterns on
match_list_init() for the inline-string subset. On match_list_match(), check
the hash table first (O(1)); fall through to the linear scan only for patterns that
are wildcards, files, or type:table references.
Alternatively: convert virt_alias_doms, virt_mailbox_doms, and relay_domains
to hash: or inline: table type in the config, which already gives O(1) via
dict_get(). Document this as a required configuration for high-recipient-count sites.
Complexity
- Slow: O(K × M) — K patterns × M recipients
- Fast: O(M) — one O(1) hash lookup per recipient
- Speedup at K=500, M=1000: ~500×
Patch
See defects/postfix/patch/postfix-0001.patch
Unit Test
See defects/postfix/unit/PostfixTest.java