Each MOAD now has a synthetic defective specimen and fixed specimen proven from first principles across three test tiers: Unit (tests/unit/Moad000X*.java): - Correctness: defective and fixed produce identical functional output - Defect behavior: defective specimen exhibits the defect (measurable) - Fix behavior: fixed specimen eliminates the defect Integration (tests/integration/AllMoadsIntegrationTest.java): - All 9 MOADs proven at medium scale (N=500-2000) - MOAD-0001: O(N^2) vs O(N) list scan at N=1000 - MOAD-0002: 500 sessions trample each other (defective) vs coexist (fixed) - MOAD-0003: 250 anonymous requests leak auth identity (defective) vs zero (fixed) - MOAD-0004: 3000 credential exposures across 1000 requests (defective) vs zero (fixed) - MOAD-0005: 500 computes for 500 concurrent misses vs exactly 1 - MOAD-0006: all 500 passwords extractable from DB (defective) vs unextractable (fixed) - MOAD-0007: N=2000 spatial objects, defective visits all 2000 vs O(log N + k) - MOAD-0009: 990 wasted firings for 1000 ticks / 10 events vs zero waste - MOAD-0011: 10240 NFA steps vs 13 steps on N=12 adversarial input (788x) Functional (tests/functional/AllMoadsFunctionalTest.java): - MOAD-0005: real-thread contention proves herd (defective >1 compute, fixed exactly 1) - MOAD-0007: N=50000 spatial objects, 50M defective probes vs 516K fixed (97x speedup) - MOAD-0009: 10000 ticks / 10 events, 9990 wasted firings vs zero (1000x ratio) - MOAD-0011: N=16 adversarial, 163840 defective steps vs 17 fixed (9638x ratio) Support algorithms (tests/support/Moad000X*.java): - Moad0002Algorithm: shared mutable global state (DefectiveAudioSystem / FixedAudioSystem + Context) - Moad0003Algorithm: ThreadLocal not cleared (handleDefective / handleFixed with finally) - Moad0004Algorithm: HTTP headers logged verbatim (logDefective / logFixed with CREDENTIAL_HEADERS denylist) - Moad0005Algorithm: get+null+compute+put (DefectiveCache HashMap / FixedCache ConcurrentHashMap.computeIfAbsent) - Moad0006Algorithm: Base64 password storage (DefectiveCredentialStore / FixedCredentialStore SHA-256+salt) - Moad0007Algorithm: linear spatial scan (queryDefective list / queryFixed sorted array + binary search) - Moad0009Algorithm: timer-driven polling (runDefectiveScheduler / runFixedEventDriven) - Moad0011Algorithm: PCRE nested quantifiers (matchDefective backtracking NFA / matchFixed linear NFA) Makefile: added unit-moad-0002 through unit-moad-0011 targets, integration-all-moads, functional-all-moads. integration and functional targets now depend on all-MOADs variants.
210 lines
10 KiB
Java
210 lines
10 KiB
Java
package unit;
|
|
|
|
import support.Moad0011Algorithm;
|
|
import support.Moad0011Algorithm.Result;
|
|
|
|
/**
|
|
* Unit tests for MOAD-0011: CWE-1333 — A Catastrophic Inheritance.
|
|
*
|
|
* Proves from first principles:
|
|
* 1. Structural: hasCatastrophicStructure() detects nested quantifiers in
|
|
* known-bad patterns and passes known-safe patterns.
|
|
* 2. Behavioral (step counting): defective NFA for (a+)+b explores
|
|
* exponentially many states on adversarial input; fixed NFA is linear.
|
|
* 3. Growth: doubling adversarial input length more than doubles defective
|
|
* steps (super-linear / exponential growth); fixed steps grow exactly linearly.
|
|
* 4. Both produce identical match results on matching and non-matching inputs.
|
|
*
|
|
* The step-counting NFA proves the algorithmic complexity without relying on
|
|
* wall-clock timing — suitable for deterministic unit testing. Functional tests
|
|
* use a timed match on a moderate adversarial input to prove behavioral impact.
|
|
*
|
|
* No build tool required. Compile and run:
|
|
*
|
|
* cd tests
|
|
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
|
|
* support/Moad0011Algorithm.java unit/Moad0011UnitTest.java
|
|
* java -cp . unit.Moad0011UnitTest
|
|
*/
|
|
public class Moad0011UnitTest {
|
|
|
|
private static int passed = 0;
|
|
private static int failed = 0;
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== Moad0011UnitTest (A Catastrophic Inheritance) ===\n");
|
|
|
|
System.out.println("-- Structural detection: hasCatastrophicStructure() --");
|
|
testDetectsKnownBadPatterns();
|
|
testPassesKnownSafePatterns();
|
|
|
|
System.out.println("\n-- Correctness: both produce identical match results --");
|
|
testBothMatchMatchingInput();
|
|
testBothRejectNonMatchingInput();
|
|
testBothMatchSingleA();
|
|
|
|
System.out.println("\n-- Defect: step count grows super-linearly on adversarial input --");
|
|
testDefectiveStepCountExponentialGrowth();
|
|
testDefectiveAdversarialN8IsLarge();
|
|
|
|
System.out.println("\n-- Fix: step count grows linearly on adversarial input --");
|
|
testFixedStepCountLinearGrowth();
|
|
testFixedAdversarialN8IsSmall();
|
|
|
|
System.out.println("\n-- Ratio: defective steps >> fixed steps on adversarial input --");
|
|
testStepRatioAdvantageForFixed();
|
|
|
|
System.out.printf("\n%d passed, %d failed%n", passed, failed);
|
|
if (failed > 0) System.exit(1);
|
|
}
|
|
|
|
// ── Structural detection ──────────────────────────────────────────────────
|
|
|
|
static void testDetectsKnownBadPatterns() {
|
|
String[] bad = {
|
|
"(a+)+b", // classic ReDoS — outer + wraps inner a+
|
|
"(a+)+", // same, no terminal
|
|
"(a*)+", // inner * outer +
|
|
"(ab+c*)+", // complex inner group with quantifiers
|
|
Moad0011Algorithm.DEFECTIVE_PATTERN_BLEACH,
|
|
Moad0011Algorithm.DEFECTIVE_PATTERN_CSS,
|
|
};
|
|
for (String p : bad) {
|
|
assertTrue("detects bad pattern: " + p,
|
|
Moad0011Algorithm.hasCatastrophicStructure(p),
|
|
"expected catastrophic, got safe");
|
|
}
|
|
}
|
|
|
|
static void testPassesKnownSafePatterns() {
|
|
String[] safe = {
|
|
"a+b", // simple concatenation — no nesting
|
|
"a+", // single quantifier
|
|
"(a|b)+", // alternation with outer +, no inner quantifier
|
|
"[a-z]+", // character class, single quantifier
|
|
"(abc)+", // fixed group, no inner quantifier
|
|
Moad0011Algorithm.FIXED_PATTERN_BLEACH,
|
|
Moad0011Algorithm.FIXED_PATTERN_CSS,
|
|
};
|
|
for (String p : safe) {
|
|
assertFalse("passes safe pattern: " + p,
|
|
Moad0011Algorithm.hasCatastrophicStructure(p),
|
|
"expected safe, got catastrophic");
|
|
}
|
|
}
|
|
|
|
// ── Correctness ───────────────────────────────────────────────────────────
|
|
|
|
static void testBothMatchMatchingInput() {
|
|
String input = Moad0011Algorithm.matchingInput(5); // "aaaaab"
|
|
Result def = Moad0011Algorithm.matchDefective(input);
|
|
Result fix = Moad0011Algorithm.matchFixed(input);
|
|
assertTrue("defective: matches 'aaaaab'", def.matched, "expected match");
|
|
assertTrue("fixed: matches 'aaaaab'", fix.matched, "expected match");
|
|
}
|
|
|
|
static void testBothRejectNonMatchingInput() {
|
|
String input = Moad0011Algorithm.adversarialInput(5); // "aaaaac" — no 'b'
|
|
Result def = Moad0011Algorithm.matchDefective(input);
|
|
Result fix = Moad0011Algorithm.matchFixed(input);
|
|
assertFalse("defective: rejects 'aaaaac'", def.matched, "expected no match");
|
|
assertFalse("fixed: rejects 'aaaaac'", fix.matched, "expected no match");
|
|
}
|
|
|
|
static void testBothMatchSingleA() {
|
|
String input = "ab";
|
|
Result def = Moad0011Algorithm.matchDefective(input);
|
|
Result fix = Moad0011Algorithm.matchFixed(input);
|
|
assertTrue("defective: matches 'ab'", def.matched, "expected match");
|
|
assertTrue("fixed: matches 'ab'", fix.matched, "expected match");
|
|
}
|
|
|
|
// ── Defect ────────────────────────────────────────────────────────────────
|
|
|
|
static void testDefectiveStepCountExponentialGrowth() {
|
|
// Adversarial input: N a's + 'c' — non-matching, forces maximum backtracking
|
|
long steps4 = Moad0011Algorithm.matchDefective(Moad0011Algorithm.adversarialInput(4)).steps;
|
|
long steps6 = Moad0011Algorithm.matchDefective(Moad0011Algorithm.adversarialInput(6)).steps;
|
|
long steps8 = Moad0011Algorithm.matchDefective(Moad0011Algorithm.adversarialInput(8)).steps;
|
|
|
|
// Exponential growth: steps8 >> steps6 >> steps4
|
|
// Ratio should be super-linear (greater than linear doubling)
|
|
double ratio64 = (double) steps6 / steps4;
|
|
double ratio86 = (double) steps8 / steps6;
|
|
|
|
assertTrue("defective: steps grow super-linearly from N=4 to N=6 (ratio > 2.0)",
|
|
ratio64 > 2.0, "ratio64=" + ratio64);
|
|
assertTrue("defective: steps grow super-linearly from N=6 to N=8 (ratio > 2.0)",
|
|
ratio86 > 2.0, "ratio86=" + ratio86);
|
|
|
|
System.out.printf(" steps N=4: %d, N=6: %d, N=8: %d (ratios: %.1fx, %.1fx)%n",
|
|
steps4, steps6, steps8, ratio64, ratio86);
|
|
}
|
|
|
|
static void testDefectiveAdversarialN8IsLarge() {
|
|
long steps = Moad0011Algorithm.matchDefective(Moad0011Algorithm.adversarialInput(8)).steps;
|
|
// N=8 adversarial input: expect hundreds or thousands of steps (super-linear)
|
|
assertTrue("defective: N=8 adversarial input requires many steps (>50)",
|
|
steps > 50, "steps=" + steps);
|
|
}
|
|
|
|
// ── Fix ───────────────────────────────────────────────────────────────────
|
|
|
|
static void testFixedStepCountLinearGrowth() {
|
|
long steps4 = Moad0011Algorithm.matchFixed(Moad0011Algorithm.adversarialInput(4)).steps;
|
|
long steps8 = Moad0011Algorithm.matchFixed(Moad0011Algorithm.adversarialInput(8)).steps;
|
|
long steps16 = Moad0011Algorithm.matchFixed(Moad0011Algorithm.adversarialInput(16)).steps;
|
|
|
|
// Linear growth: doubling N should roughly double steps (ratio ~2x)
|
|
double ratio84 = (double) steps8 / steps4;
|
|
double ratio168 = (double) steps16 / steps8;
|
|
|
|
// Allow ratio up to 3.0 to be generous with small constants, but it must not be exponential
|
|
assertTrue("fixed: doubling N from 4 to 8 roughly doubles steps (ratio 1.0-3.0)",
|
|
ratio84 >= 1.0 && ratio84 <= 3.0, "ratio84=" + ratio84);
|
|
assertTrue("fixed: doubling N from 8 to 16 roughly doubles steps (ratio 1.0-3.0)",
|
|
ratio168 >= 1.0 && ratio168 <= 3.0, "ratio168=" + ratio168);
|
|
|
|
System.out.printf(" steps N=4: %d, N=8: %d, N=16: %d (ratios: %.1fx, %.1fx)%n",
|
|
steps4, steps8, steps16, ratio84, ratio168);
|
|
}
|
|
|
|
static void testFixedAdversarialN8IsSmall() {
|
|
long steps = Moad0011Algorithm.matchFixed(Moad0011Algorithm.adversarialInput(8)).steps;
|
|
// Fixed NFA: O(N) steps — N=8 means steps should be a small multiple of 8
|
|
assertTrue("fixed: N=8 adversarial requires <= 20 steps (linear)",
|
|
steps <= 20, "steps=" + steps);
|
|
}
|
|
|
|
// ── Ratio ─────────────────────────────────────────────────────────────────
|
|
|
|
static void testStepRatioAdvantageForFixed() {
|
|
String adversarial = Moad0011Algorithm.adversarialInput(10);
|
|
long defSteps = Moad0011Algorithm.matchDefective(adversarial).steps;
|
|
long fixSteps = Moad0011Algorithm.matchFixed(adversarial).steps;
|
|
|
|
// At N=10, defective explores exponentially many states vs fixed linear scan
|
|
double ratio = (double) defSteps / fixSteps;
|
|
assertTrue("defective steps >> fixed steps at N=10 adversarial (ratio > 10x)",
|
|
ratio > 10.0, "defSteps=" + defSteps + " fixSteps=" + fixSteps + " ratio=" + ratio);
|
|
|
|
System.out.printf(" N=10 adversarial: defective=%d steps, fixed=%d steps (%.0fx advantage)%n",
|
|
defSteps, fixSteps, ratio);
|
|
}
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
|
|
static void assertTrue(String label, boolean condition, String detail) {
|
|
if (condition) {
|
|
System.out.printf(" PASS: %s%n", label);
|
|
passed++;
|
|
} else {
|
|
System.out.printf(" FAIL: %s — %s%n", label, detail);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
static void assertFalse(String label, boolean condition, String detail) {
|
|
assertTrue(label, !condition, detail);
|
|
}
|
|
}
|