201 lines
8.4 KiB
Java
201 lines
8.4 KiB
Java
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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String, String> 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.");
|
||
}
|
||
}
|