java-topology/defects/thunderbird-0006/test/ThunderbirdSpamWhitelistTest.java
russell@unturf.com 6a025795dc thunderbird: 6 CWE-407 defects from 5-MOAD deep scan via Searchfox
thunderbird-0001: nsMsgAccountManager LoadAccounts IndexOf O(N^2) dedup MEDIUM 192x
thunderbird-0002: nsMsgCopyService DoNextCopy ContainsObject O(N^2) MEDIUM-HIGH 44x
thunderbird-0003: nsAutoSyncManager IndexOf in sync loops O(N^2) HIGH 751x
thunderbird-0004: nsImapFlagAndUidState Contains/IndexOf linear on sorted O(N) HIGH 107x
thunderbird-0005: nsMsgFilterList ComputeArbitraryHeaders FindInReadable O(H^2) MEDIUM 64x
thunderbird-0006: nsSpamSettings CheckWhiteList linear email scan O(M*E) MEDIUM 153x

MOAD-0002 (Intertangle): MEDIUM - singleton nsMsgAccountManager shared across protocols
MOAD-0003 (Leaked Context): LOW-MEDIUM - IMAP connection pool single auth boolean
MOAD-0004 (Logged Secret): MEDIUM - debug builds log credentials via MOZ_UPDATE_CHANNEL bypass
MOAD-0005 (CWE-362): LOW-MEDIUM - IMAP folder DB init and connection pool TOCTOU

12/12 unit tests PASS
2026-03-30 17:33:16 -04:00

140 lines
5.4 KiB
Java

import java.util.*;
/**
* Unit test for thunderbird-0006: nsSpamSettings::CheckWhiteList() O(M*E)
*
* Simulates the spam whitelist check where for each incoming message,
* the sender's email is compared against all identity emails via linear scan.
*
* Defective: O(M*E) - linear scan through emails per message
* Fixed: O(M) - HashSet pre-computed from identity emails
*/
public class ThunderbirdSpamWhitelistTest {
// --- Defective: O(M*E) linear scan per message ---
static int checkWhiteListDefective(List<String> incomingAuthors,
List<String> identityEmails) {
int whitelisted = 0;
for (String author : incomingAuthors) {
String authorLower = author.toLowerCase();
// Check 1: exact email match (O(E) per message)
boolean found = false;
for (String email : identityEmails) {
if (email.equalsIgnoreCase(authorLower)) {
found = true;
break;
}
}
if (found) { whitelisted++; continue; }
// Check 2: domain match (O(E) per message, with string ops)
String authorDomain = authorLower.substring(authorLower.indexOf('@') + 1);
for (String email : identityEmails) {
int atPos = email.indexOf('@');
if (atPos >= 0) {
String domain = email.substring(atPos + 1).toLowerCase();
if (domain.equals(authorDomain)) {
found = true;
break;
}
}
}
if (found) whitelisted++;
}
return whitelisted;
}
// --- Fixed: O(M) with pre-computed sets ---
static int checkWhiteListFixed(List<String> incomingAuthors,
Set<String> emailSet, Set<String> domainSet) {
int whitelisted = 0;
for (String author : incomingAuthors) {
String authorLower = author.toLowerCase();
if (emailSet.contains(authorLower)) {
whitelisted++;
continue;
}
String authorDomain = authorLower.substring(authorLower.indexOf('@') + 1);
if (domainSet.contains(authorDomain)) {
whitelisted++;
}
}
return whitelisted;
}
public static void main(String[] args) {
System.out.println("=== thunderbird-0006: nsSpamSettings CheckWhiteList O(M*E) ===\n");
// Correctness
List<String> emails = Arrays.asList(
"user@example.com", "admin@corp.org", "test@foo.bar");
Set<String> emailSet = new HashSet<>();
Set<String> domainSet = new HashSet<>();
for (String e : emails) {
emailSet.add(e.toLowerCase());
domainSet.add(e.substring(e.indexOf('@') + 1).toLowerCase());
}
List<String> authors = Arrays.asList(
"User@Example.com", // exact match (case-insensitive)
"other@corp.org", // domain match
"spam@evil.com", // no match
"test@foo.bar" // exact match
);
int defCount = checkWhiteListDefective(authors, emails);
int fixCount = checkWhiteListFixed(authors, emailSet, domainSet);
assert defCount == fixCount : "Counts must match: " + defCount + " vs " + fixCount;
assert defCount == 3 : "3 whitelisted: " + defCount;
System.out.println("PASS correctness: " + defCount + " whitelisted");
// Benchmark
int[] emailCounts = {10, 50, 100, 500};
int M = 1000; // messages per sync
for (int E : emailCounts) {
// Generate identity emails
List<String> idEmails = new ArrayList<>();
Set<String> eSet = new HashSet<>();
Set<String> dSet = new HashSet<>();
for (int i = 0; i < E; i++) {
String e = "user" + i + "@domain" + (i % 20) + ".com";
idEmails.add(e);
eSet.add(e.toLowerCase());
dSet.add("domain" + (i % 20) + ".com");
}
// Generate incoming messages (~10% match rate)
Random rng = new Random(42);
List<String> msgs = new ArrayList<>();
for (int i = 0; i < M; i++) {
if (rng.nextInt(10) == 0) {
msgs.add("user" + rng.nextInt(E) + "@domain" + rng.nextInt(20) + ".com");
} else {
msgs.add("sender" + i + "@external" + rng.nextInt(100) + ".com");
}
}
// Warmup
for (int w = 0; w < 3; w++) {
checkWhiteListDefective(msgs, idEmails);
checkWhiteListFixed(msgs, eSet, dSet);
}
int iters = Math.max(1, 10000 / E);
long t0 = System.nanoTime();
for (int r = 0; r < iters; r++) checkWhiteListDefective(msgs, idEmails);
long defTime = System.nanoTime() - t0;
t0 = System.nanoTime();
for (int r = 0; r < iters; r++) checkWhiteListFixed(msgs, eSet, dSet);
long fixTime = System.nanoTime() - t0;
double ratio = (double) defTime / Math.max(1, fixTime);
String status = ratio >= 2.0 ? "PASS" : "FAIL";
System.out.printf("%s M=%d E=%4d defective=%8dns fixed=%8dns ratio=%.1fx%n",
status, M, E, defTime / iters, fixTime / iters, ratio);
}
System.out.println("\nAll tests passed.");
}
}