package unit; import java.util.*; /** * zeek-0001 — CWE-407: O(n²) rule deduplication in per-packet signature matching * * Models Zeek src/RuleMatcher.cc is_member_of(): * Slow: std::ranges::find(matched_rules, idx) — O(R) per call, 6 calls per packet * Fast: unordered_set::count(idx) — O(1) per call * * As a connection accumulates matched rules over P packets, * each packet costs O(R_matched) per check. Over P packets with R total rules: * Slow: O(P * R_matched_avg * 6) = O(P * R) with fully loaded state * Fast: O(P * 1 * 6) = O(P) */ public class ZeekRuleMatcherTest { // --- SLOW: vector-based matched_rules (defective) --- static boolean isMemberOfSlow(List matched, long ruleIdx) { for (long r : matched) { if (r == ruleIdx) return true; } return false; } /** * Simulate P packets across a connection, each triggering up to batchSize rules. * Per packet: 6 is_member_of checks per candidate rule. * Returns total operations (inner loop iterations). */ static long simulateSlowConnection(int totalRules, int packetsPerConn, int rulesMatchedPerPacket) { List matchedRules = new ArrayList<>(); long ops = 0; Random rng = new Random(7); for (int pkt = 0; pkt < packetsPerConn; pkt++) { // Select rulesMatchedPerPacket candidates to check for (int i = 0; i < rulesMatchedPerPacket; i++) { long ruleIdx = rng.nextInt(totalRules); // 6 is_member_of calls model the 6 call sites in RuleMatcher.cc for (int call = 0; call < 6; call++) { for (long r : matchedRules) { // O(R) scan ops++; if (r == ruleIdx) break; } } // Add to matched_rules if not already present (ExecRuleActions) if (!isMemberOfSlow(matchedRules, ruleIdx)) { matchedRules.add(ruleIdx); } } } return ops; } // --- FAST: hash set matched_rules (fixed) --- static long simulateFastConnection(int totalRules, int packetsPerConn, int rulesMatchedPerPacket) { Set matchedRules = new HashSet<>(); long ops = 0; Random rng = new Random(7); for (int pkt = 0; pkt < packetsPerConn; pkt++) { for (int i = 0; i < rulesMatchedPerPacket; i++) { long ruleIdx = rng.nextInt(totalRules); // 6 O(1) hash lookups for (int call = 0; call < 6; call++) { ops++; // single hash lookup matchedRules.contains(ruleIdx); } matchedRules.add(ruleIdx); } } return ops; } public static void main(String[] args) { // R = total rules in IDS ruleset (large enterprise deployment) // P = packets per connection // B = rules fired per packet (typically low, but state accumulates) final int R = 500; final int P = 1000; final int B = 5; // rules that trigger per packet (accumulates in matched_rules) // Correctness: same rule indices should be found/not-found by both List slowList = new ArrayList<>(Arrays.asList(10L, 20L, 30L, 50L, 99L)); Set fastSet = new HashSet<>(Arrays.asList(10L, 20L, 30L, 50L, 99L)); assert isMemberOfSlow(slowList, 30L) : "slow: should find 30"; assert !isMemberOfSlow(slowList, 40L) : "slow: should miss 40"; assert fastSet.contains(30L) : "fast: should find 30"; assert !fastSet.contains(40L) : "fast: should miss 40"; long slowOps = simulateSlowConnection(R, P, B); long fastOps = simulateFastConnection(R, P, B); assert slowOps > fastOps : "slow should cost more ops, got slow=" + slowOps + " fast=" + fastOps; double speedup = (double) slowOps / fastOps; System.out.println("zeek-0001 CWE-407: is_member_of O(R) vector scan vs O(1) hash set"); System.out.println(" R (rules) = " + R + ", P (packets/conn) = " + P + ", B (rules/pkt) = " + B); System.out.println(" Slow ops (vector linear scan, 6x per pkt): " + slowOps); System.out.println(" Fast ops (hash set, 6x per pkt): " + fastOps); System.out.printf (" Speedup: %.0fx%n", speedup); System.out.println(); // Verify the quadratic growth: at 2× rules the slow cost should ~4× (quadratic) long slowOpsHalfR = simulateSlowConnection(R/2, P, B); long slowOpsDoubleR= simulateSlowConnection(R*2, P, B); double ratio = (double) slowOpsDoubleR / slowOpsHalfR; // For a truly quadratic structure ratio approaches 4.0; assert it's super-linear assert ratio > 2.0 : "expected super-linear growth, got ratio=" + ratio; long fastOpsHalfR = simulateFastConnection(R/2, P, B); long fastOpsDoubleR = simulateFastConnection(R*2, P, B); double fastRatio = (double) fastOpsDoubleR / fastOpsHalfR; // Fast should be roughly linear (ratio ~2) or even flat (constant O(1) per call) assert fastRatio < ratio : "fast should scale better than slow"; System.out.println("Quadratic growth check (R/2 → 2R):"); System.out.println(" Slow ops ratio: " + String.format("%.2f", ratio) + "x (>2 confirms super-linear)"); System.out.println(" Fast ops ratio: " + String.format("%.2f", fastRatio) + "x (near-linear)"); System.out.println(); System.out.println("2/2 PASS"); } }