package unit; import java.util.*; /** * PostfixTest — CWE-407 benchmarks for postfix-0001 and postfix-0002 * * postfix-0001: resolve_addr() calls string_list_match(virt_alias_doms/relay_domains) * O(K) per RCPT-TO, O(K×M) total for M recipients. * Fix: pre-build HashMap from inline domain patterns → O(M). * * postfix-0002: cleanup_masquerade_external() calls string_list_match(masq_exceptions) * O(E) per message address, O(N×E) total for N addresses. * Fix: pre-build HashMap from exceptions → O(N). * * Run: javac -d . PostfixTest.java && java -ea unit.PostfixTest */ public class PostfixTest { // ── Shared bench harness ────────────────────────────────────────────────── static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) { slow.run(); fast.run(); // warm-up long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000; double r = fOps > 0 ? (double) sOps / fOps : 0; System.out.printf(" %-56s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n", label, sMs, sOps, fMs, fOps, r); } // ── postfix-0001: resolve_addr() domain list scan ───────────────────────── /** * SLOW: string_list_match — iterate ARGV of K inline domain patterns * for each of M recipient domains. Models the Postfix ARGV-based match_list. * * Returns the exact number of strcmp operations performed. */ static long resolveSlow(int domainPatterns, int recipients) { // Build ARGV-equivalent: list of domain strings List argv = new ArrayList<>(domainPatterns); for (int i = 0; i < domainPatterns; i++) argv.add("hosted-" + i + ".example.com"); long ops = 0; for (int rcpt = 0; rcpt < recipients; rcpt++) { // Each rcpt is from a different domain; worst-case: not in list String domain = "sender-" + rcpt + ".example.com"; // match_list_match: linear scan of ARGV for (String pat : argv) { ops++; if (pat.equalsIgnoreCase(domain)) break; } } return ops; } /** * FAST: HashMap lookup — O(1) per recipient domain. * Models the patched string_cache HTABLE inside match_list_match. */ static long resolveFast(int domainPatterns, int recipients) { // Build hash map from inline patterns Set hashCache = new HashSet<>(domainPatterns * 2); for (int i = 0; i < domainPatterns; i++) hashCache.add("hosted-" + i + ".example.com"); long ops = 0; for (int rcpt = 0; rcpt < recipients; rcpt++) { String domain = "sender-" + rcpt + ".example.com"; ops++; // O(1) hash lookup hashCache.contains(domain); } return ops; } // ── postfix-0002: cleanup_masquerade_external() exception scan ──────────── /** * SLOW: string_list_match(masq_exceptions, username) — O(E) scan per address. * Models the ARGV iteration in cleanup_masquerade_external(). */ static long masqExceptionSlow(int exceptions, int addresses) { List exceptionList = new ArrayList<>(exceptions); for (int i = 0; i < exceptions; i++) exceptionList.add("root" + i); long ops = 0; for (int addr = 0; addr < addresses; addr++) { // Each address has a unique username; worst-case: not in exceptions String username = "user" + addr; for (String exc : exceptionList) { ops++; if (exc.equalsIgnoreCase(username)) break; } } return ops; } /** * FAST: HashMap lookup for masquerade exceptions — O(1) per address. * Models the patched htable_find() in cleanup_masquerade_external(). */ static long masqExceptionFast(int exceptions, int addresses) { Set exceptionSet = new HashSet<>(exceptions * 2); for (int i = 0; i < exceptions; i++) exceptionSet.add("root" + i); long ops = 0; for (int addr = 0; addr < addresses; addr++) { String username = "user" + addr; ops++; // O(1) hash lookup exceptionSet.contains(username); } return ops; } /** * SLOW: masquerade domain ARGV scan — O(D) per address. * Models the for (masqp = masq_domains->argv; ...) loop in * cleanup_masquerade_external(). */ static long masqDomainSlow(int masqDomains, int addresses) { List domainList = new ArrayList<>(masqDomains); for (int i = 0; i < masqDomains; i++) domainList.add("corp" + i + ".example.com"); long ops = 0; for (int addr = 0; addr < addresses; addr++) { // Address is in a sub-domain of one of the masq domains (find last match) String addrDomain = "mail.corp" + (addr % masqDomains) + ".example.com"; for (String masq : domainList) { ops++; if (addrDomain.endsWith("." + masq) || addrDomain.equals(masq)) break; } } return ops; } /** * FAST: HashMap for exact-match masquerade domains — O(1) per address. * Models the htable_find(masq_domain_cache, domain) fast path. */ static long masqDomainFast(int masqDomains, int addresses) { Map domainMap = new HashMap<>(masqDomains * 2); for (int i = 0; i < masqDomains; i++) { String d = "corp" + i + ".example.com"; domainMap.put(d, d); } long ops = 0; for (int addr = 0; addr < addresses; addr++) { String addrDomain = "mail.corp" + (addr % masqDomains) + ".example.com"; ops++; // O(1) hash lookup (exact parent domain) // Real patch also handles subdomain stripping, but lookup is O(1) domainMap.containsKey(addrDomain); } return ops; } // ── main ────────────────────────────────────────────────────────────────── public static void main(String[] args) { final int K = 500; // inline domain patterns in virt_alias/relay_domains final int M = 2000; // recipients per mailing-list message final int E = 200; // masquerade_exceptions entries final int N = 1000; // header addresses in a large message final int D = 100; // masquerade_domains entries System.out.println("PostfixTest — CWE-407"); System.out.println(); System.out.println("postfix-0001: resolve_addr() domain list scan"); final long[] sOps1 = new long[1], fOps1 = new long[1]; bench(String.format("resolve K=%d patterns, M=%d recipients", K, M), () -> { sOps1[0] = resolveSlow(K, M); }, () -> { fOps1[0] = resolveFast(K, M); }, resolveSlow(K, M), resolveFast(K, M)); assert sOps1[0] > fOps1[0] * 100 : "postfix-0001: expected >100x more ops slow vs fast"; System.out.println(); System.out.println("postfix-0002: cleanup_masquerade_external() scans"); final long[] sOps2 = new long[1], fOps2 = new long[1]; bench(String.format("masq-exceptions E=%d, N=%d addresses", E, N), () -> { sOps2[0] = masqExceptionSlow(E, N); }, () -> { fOps2[0] = masqExceptionFast(E, N); }, masqExceptionSlow(E, N), masqExceptionFast(E, N)); assert sOps2[0] > fOps2[0] * 50 : "postfix-0002a: expected >50x more ops slow vs fast"; final long[] sOps3 = new long[1], fOps3 = new long[1]; bench(String.format("masq-domains D=%d, N=%d addresses", D, N), () -> { sOps3[0] = masqDomainSlow(D, N); }, () -> { fOps3[0] = masqDomainFast(D, N); }, masqDomainSlow(D, N), masqDomainFast(D, N)); assert sOps3[0] > fOps3[0] * 10 : "postfix-0002b: expected >10x more ops slow vs fast"; System.out.println(); System.out.println("All assertions passed."); } }