java-topology/docs/tickets/postfix-0001-resolve-domain-list-linear-scan.md

2.7 KiB
Raw Blame History

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