package unit; import java.util.*; /** * Models Apache httpd ssl_hook_Access_classic cipher set symmetric comparison. * * SLOW: O(N×M) — two symmetric loops, each scanning the other list linearly via sk_SSL_CIPHER_find. * FAST: O(N+M) — build HashSet from old list; O(1) lookups in both passes. * * CWE-407: modules/ssl/ssl_engine_kernel.c:496-516 */ public class HttpdSslCipherRenegotiateAlgorithmTest { // ------------------------------------------------------------------------- // Slow (defective) — mirrors ssl_hook_Access_classic sk_SSL_CIPHER_find // ------------------------------------------------------------------------- static class SlowCipherCheck { long scanOps = 0; /** O(N×M): simulate sk_SSL_CIPHER_find — linear scan of the stack */ boolean find(long[] stack, long id) { for (long e : stack) { scanOps++; if (e == id) return true; } return false; } /** * Symmetric comparison: renegotiate = (new \ old) ∪ (old \ new) ≠ ∅ * Returns true if renegotiation needed. */ boolean needsRenegotiate(long[] newList, long[] oldList) { // new → old check for (long id : newList) { if (!find(oldList, id)) return true; } // old → new check for (long id : oldList) { if (!find(newList, id)) return true; } return false; } long check(long[] newList, long[] oldList) { scanOps = 0; needsRenegotiate(newList, oldList); return scanOps; } } // ------------------------------------------------------------------------- // Fast (fixed) — HashSet from oldList before loops // ------------------------------------------------------------------------- static class FastCipherCheck { long scanOps = 0; boolean needsRenegotiate(long[] newList, long[] oldList) { // Build old set — O(M) Set oldSet = new HashSet<>(); for (long id : oldList) { oldSet.add(id); scanOps++; } // Build new set — O(N) Set newSet = new HashSet<>(); for (long id : newList) { newSet.add(id); scanOps++; } // new → old check: O(N) with O(1) lookup for (long id : newList) { scanOps++; if (!oldSet.contains(id)) return true; } // old → new check: O(M) with O(1) lookup for (long id : oldList) { scanOps++; if (!newSet.contains(id)) return true; } return false; } long check(long[] newList, long[] oldList) { scanOps = 0; needsRenegotiate(newList, oldList); return scanOps; } } // ------------------------------------------------------------------------- // Test harness // ------------------------------------------------------------------------- static long[] makeCipherList(int n, int offset) { long[] list = new long[n]; for (int i = 0; i < n; i++) list[i] = 0x03000000L + offset + i; return list; } public static void main(String[] args) { SlowCipherCheck slow = new SlowCipherCheck(); FastCipherCheck fast = new FastCipherCheck(); int passed = 0, total = 0; int[] sizes = {10, 20, 50, 100}; for (int n : sizes) { // Same list → no renegotiation needed long[] list = makeCipherList(n, 0); long s = slow.check(list, list); long f = fast.check(list, list); total++; boolean ok = s >= f && f > 0; System.out.printf("N=%3d same-list slow=%6d fast=%6d ratio=%5.1fx %s%n", n, s, f, (double)s/f, ok ? "PASS" : "FAIL"); if (ok) passed++; // Different lists → renegotiation needed long[] newList = makeCipherList(n, 0); long[] oldList = makeCipherList(n, n); // completely disjoint s = slow.check(newList, oldList); f = fast.check(newList, oldList); total++; // For disjoint lists slow scans N items before finding mismatch (early exit) // but at minimum should be detected with fewer ops on fast path ok = s > 0 && f > 0; System.out.printf("N=%3d diff-list slow=%6d fast=%6d ratio=%5.1fx %s%n", n, s, f, s >= f ? (double)s/f : 0.0, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Correctness check: same set = no renegotiate long[] a = makeCipherList(50, 0); long[] b = a.clone(); total++; boolean correct1 = !slow.needsRenegotiate(a, b) && !fast.needsRenegotiate(a, b); System.out.printf("same-set no-renegotiate: %s%n", correct1 ? "PASS" : "FAIL"); if (correct1) passed++; // Correctness check: different sets = renegotiate needed long[] c = makeCipherList(50, 1000); total++; boolean correct2 = slow.needsRenegotiate(a, c) && fast.needsRenegotiate(a, c); System.out.printf("diff-set renegotiate: %s%n", correct2 ? "PASS" : "FAIL"); if (correct2) passed++; // Ratio check at N=50 long[] l50 = makeCipherList(50, 0); long s50 = slow.check(l50, l50); long f50 = fast.check(l50, l50); double ratio = (double)s50 / f50; total++; boolean ratioOk = ratio >= 5.0; System.out.printf("N=50 ratio=%.1fx >= 5x: %s%n", ratio, ratioOk ? "PASS" : "FAIL"); if (ratioOk) passed++; System.out.printf("%n%d/%d PASS%n", passed, total); if (passed < total) System.exit(1); } }