feat: add unit, integration, and functional test coverage for all 9 MOADs

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.
This commit is contained in:
russell@unturf.com 2026-04-12 15:43:19 -04:00
parent 1f88aa0374
commit 4888153e40
19 changed files with 3359 additions and 5 deletions

View file

@ -0,0 +1,130 @@
package support;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* MOAD-0006: CWE-257 A Glass Safe.
*
* Defect: credentials stored in recoverable form plaintext, Base64 encoding,
* or reversible encryption. Anyone with read access to the credential store
* (DB dump, log file, backup) can reconstruct the original secret.
*
* Variant: Mailman 2.x actively emails list passwords to members on a monthly
* schedule, because it stores them in recoverable form.
*
* Fix: one-way hashing with salt (SHA-256 + random salt shown here; bcrypt or
* Argon2 preferred in production). The stored value cannot be reversed to
* recover the original credential only verification (hash-and-compare) works.
*
* Scanner detects: Base64.encode/decode on password-shaped values, or direct
* string equality comparison between a stored field and user-supplied input.
*/
public class Moad0006Algorithm {
// Defective: Base64 storage (reversible)
/**
* Credential store that encodes passwords with Base64.
* DEFECT: Base64 is an encoding, not a hash. decode() recovers the original.
*/
public static final class DefectiveCredentialStore {
private final Map<String, String> store = new HashMap<>();
/** Store password encoded as Base64. DEFECT: reversible. */
public void storePassword(String username, String password) {
store.put(username,
Base64.getEncoder().encodeToString(
password.getBytes(StandardCharsets.UTF_8)));
}
/** Verify by encoding the candidate and comparing. */
public boolean verify(String username, String password) {
String stored = store.get(username);
if (stored == null) return false;
String candidate = Base64.getEncoder().encodeToString(
password.getBytes(StandardCharsets.UTF_8));
return stored.equals(candidate);
}
/**
* DEFECT: attacker with DB read access calls this to recover original password.
* Returns the plaintext password the glass safe opens.
*/
public String extractPassword(String username) {
String stored = store.get(username);
if (stored == null) return null;
return new String(Base64.getDecoder().decode(stored), StandardCharsets.UTF_8);
}
/** Exposes raw stored value (what a DB dump would show). */
public String rawStored(String username) {
return store.get(username);
}
}
// Fixed: SHA-256 + salt (one-way)
private static final SecureRandom RNG = new SecureRandom();
/** Generate a fresh 16-byte random salt. */
public static byte[] generateSalt() {
byte[] salt = new byte[16];
RNG.nextBytes(salt);
return salt;
}
/** Hash password with salt using SHA-256. */
public static byte[] sha256(String password, byte[] salt) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(salt);
return md.digest(password.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e); // SHA-256 always available in Java
}
}
/**
* Credential store that hashes passwords with SHA-256 + random salt.
* FIX: no extraction method exists stored value cannot be reversed.
*/
public static final class FixedCredentialStore {
private final Map<String, byte[]> salts = new HashMap<>();
private final Map<String, byte[]> hashes = new HashMap<>();
/** Store password as SHA-256(salt || password). */
public void storePassword(String username, String password) {
byte[] salt = generateSalt();
byte[] hash = sha256(password, salt);
salts.put(username, salt);
hashes.put(username, hash);
}
/** Verify by hashing candidate with stored salt and comparing. */
public boolean verify(String username, String password) {
byte[] salt = salts.get(username);
byte[] hash = hashes.get(username);
if (salt == null || hash == null) return false;
return Arrays.equals(sha256(password, salt), hash);
}
/** Exposes raw stored hash (what a DB dump would show — unrecoverable). */
public byte[] rawHash(String username) {
return hashes.get(username);
}
/** Exposes raw salt (what a DB dump would show). */
public byte[] rawSalt(String username) {
return salts.get(username);
}
// FIX: no extractPassword() method. DB access gives hash+salt, not plaintext.
}
}