package unit; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * strongswan-0001: CWE-407 O(P²×T×A²) nested linear scans in proposal_select / select_algo * * Models the strongSwan proposal negotiation: * SLOW: nested ArrayList scans for per-transform algorithm matching (O(A₁×A₂) per type) * inside an outer O(P_c × P_s) proposal loop. * FAST: HashMap-indexed algorithm set per transform type → O(A₁+A₂) * * No JUnit — compile and run standalone. */ public class StrongSwanProposalSelectTest { // ------------------------------------------------------------------ // Data model: a "proposal" has transform types, each with a list of algs // Each alg is an int (algorithm id | key_size packed as long) // ------------------------------------------------------------------ static class Proposal { // transform_type -> list of (alg_id << 16 | key_size) longs List> transforms; // indexed by transform type 0..T-1 Proposal(int numTypes) { transforms = new ArrayList<>(); for (int i = 0; i < numTypes; i++) transforms.add(new ArrayList<>()); } void addAlg(int type, long algKey) { transforms.get(type).add(algKey); } } // ------------------------------------------------------------------ // SLOW: select_algo O(A₁ × A₂) — mirrors the C nested enumerator loop // Returns op count. // ------------------------------------------------------------------ static long slowSelectAlgo(List localAlgs, List remoteAlgs) { long ops = 0; for (Long alg1 : localAlgs) { for (Long alg2 : remoteAlgs) { ops++; if (alg1.equals(alg2)) { return ops; // found — mirrors early break } } } return ops; // no match } // SLOW: select_algos — iterate all T transform types static long slowSelectAlgos(Proposal local, Proposal remote) { long ops = 0; int T = local.transforms.size(); for (int type = 0; type < T; type++) { ops += slowSelectAlgo(local.transforms.get(type), remote.transforms.get(type)); } return ops; } // SLOW: proposal_select — O(P_c × P_s) outer loop static long slowProposalSelect(List configured, List supplied) { long ops = 0; for (Proposal local : configured) { for (Proposal remote : supplied) { ops += slowSelectAlgos(local, remote); } } return ops; } // ------------------------------------------------------------------ // FAST: build HashMap per transform type from remote proposal, then O(1) lookup // ------------------------------------------------------------------ static long fastSelectAlgo(List localAlgs, List remoteAlgs) { HashMap remoteSet = new HashMap<>(); long ops = 0; for (Long alg2 : remoteAlgs) { ops++; remoteSet.put(alg2, true); } for (Long alg1 : localAlgs) { ops++; if (remoteSet.containsKey(alg1)) { return ops; // found } } return ops; } static long fastSelectAlgos(Proposal local, Proposal remote) { long ops = 0; int T = local.transforms.size(); for (int type = 0; type < T; type++) { ops += fastSelectAlgo(local.transforms.get(type), remote.transforms.get(type)); } return ops; } static long fastProposalSelect(List configured, List supplied) { long ops = 0; for (Proposal local : configured) { for (Proposal remote : supplied) { ops += fastSelectAlgos(local, remote); } } return ops; } // ------------------------------------------------------------------ // Helpers: build proposal lists // ------------------------------------------------------------------ /** * Build a list of P proposals, each with T transform types, * each type having A algorithms. Algorithms are unique per proposal * and offset so that no match occurs until the last comparison * (worst-case for the slow path). */ static List makeProposals(int P, int T, int A, int baseOffset) { List proposals = new ArrayList<>(); for (int p = 0; p < P; p++) { Proposal prop = new Proposal(T); for (int t = 0; t < T; t++) { for (int a = 0; a < A; a++) { // unique algorithm id per (proposal, type, alg) long algKey = (long)(baseOffset + p * T * A + t * A + a) << 16; prop.addAlg(t, algKey); } } proposals.add(prop); } return proposals; } /** * Build supplied proposals that match on the LAST algorithm of LAST * proposal to force worst-case O(P²×T×A²). */ static List makeMatchingSupplied(List configured, int T, int A) { List supplied = new ArrayList<>(); // clone configured proposals, put the matching alg last in each type for (Proposal cfg : configured) { Proposal sup = new Proposal(T); for (int t = 0; t < T; t++) { List cfgAlgs = cfg.transforms.get(t); // add non-matching alg first, then the matching alg last sup.addAlg(t, Long.MAX_VALUE - t); // no-match filler sup.addAlg(t, cfgAlgs.get(cfgAlgs.size() - 1)); // last cfg alg } supplied.add(sup); } return supplied; } public static void main(String[] args) { int tests = 0, passed = 0; // --- correctness: tiny case --- { tests++; int P = 2, T = 2, A = 3; List configured = makeProposals(P, T, A, 0); List supplied = makeMatchingSupplied(configured, T, A); long slowOps = slowProposalSelect(configured, supplied); long fastOps = fastProposalSelect(configured, supplied); // both should find a match (ops > 0); fast should use fewer ops if (slowOps > 0 && fastOps > 0 && fastOps <= slowOps) { passed++; System.out.println("PASS correctness P=" + P + " T=" + T + " A=" + A + " slow_ops=" + slowOps + " fast_ops=" + fastOps); } else { System.out.println("FAIL correctness P=" + P + " T=" + T + " A=" + A + " slow_ops=" + slowOps + " fast_ops=" + fastOps); } } // --- no-match worst case --- { tests++; int P = 5, T = 3, A = 5; List configured = makeProposals(P, T, A, 0); // supplied has completely different algs — no match at all List supplied = makeProposals(P, T, A, 10000); long slowOps = slowProposalSelect(configured, supplied); long fastOps = fastProposalSelect(configured, supplied); double ratio = (double) slowOps / fastOps; boolean ok = ratio >= 2.0; if (ok) { passed++; System.out.printf("PASS no-match P=%d T=%d A=%d slow_ops=%d fast_ops=%d ratio=%.1fx%n", P, T, A, slowOps, fastOps, ratio); } else { System.out.printf("FAIL no-match P=%d T=%d A=%d slow_ops=%d fast_ops=%d ratio=%.1fx (need >=2x)%n", P, T, A, slowOps, fastOps, ratio); } } // --- speedup benchmarks --- int[][] benchParams = { // {P, T, A} — worst case: no match until last comparison {10, 5, 8}, {20, 5, 8}, {30, 7, 10}, {50, 7, 10}, }; for (int[] param : benchParams) { tests++; int P = param[0], T = param[1], A = param[2]; List configured = makeProposals(P, T, A, 0); // no-match supplied: completely different algorithm ids List supplied = makeProposals(P, T, A, 100000); long slowOps = slowProposalSelect(configured, supplied); long fastOps = fastProposalSelect(configured, supplied); double ratio = (double) slowOps / fastOps; // At A=8 we expect ~A ratio for select_algo alone; with P² multiplied, ratio should be >=3x boolean ok = ratio >= 3.0; if (ok) { passed++; System.out.printf("PASS speedup P=%d T=%d A=%d slow_ops=%d fast_ops=%d ratio=%.1fx%n", P, T, A, slowOps, fastOps, ratio); } else { System.out.printf("FAIL speedup P=%d T=%d A=%d slow_ops=%d fast_ops=%d ratio=%.1fx (need >=3x)%n", P, T, A, slowOps, fastOps, ratio); } } // --- large-scale attack simulation --- // Attacker sends 50 non-matching proposals, server has 30 proposals // This models a DoS amplification scenario { tests++; int Pc = 30, Ps = 50, T = 5, A = 8; List configured = makeProposals(Pc, T, A, 0); List supplied = makeProposals(Ps, T, A, 100000); long slowOps = slowProposalSelect(configured, supplied); long fastOps = fastProposalSelect(configured, supplied); double ratio = (double) slowOps / fastOps; // With A=8: slow does A² ops per transform (64), fast does 2A (16) → ratio ~= A/2 = 4x // The key point is the A² exponent is eliminated; require ratio >= 3x boolean ok = ratio >= 3.0; if (ok) { passed++; System.out.printf("PASS dos-scenario Pc=%d Ps=%d T=%d A=%d slow_ops=%d fast_ops=%d ratio=%.1fx%n", Pc, Ps, T, A, slowOps, fastOps, ratio); } else { System.out.printf("FAIL dos-scenario Pc=%d Ps=%d T=%d A=%d slow_ops=%d fast_ops=%d ratio=%.1fx (need >=3x)%n", Pc, Ps, T, A, slowOps, fastOps, ratio); } } System.out.println("\n" + passed + "/" + tests + " PASS"); if (passed != tests) System.exit(1); } }