164 lines
5.9 KiB
Java
164 lines
5.9 KiB
Java
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<cipher_id> 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<id> from oldList before loops
|
||
// -------------------------------------------------------------------------
|
||
|
||
static class FastCipherCheck {
|
||
long scanOps = 0;
|
||
|
||
boolean needsRenegotiate(long[] newList, long[] oldList) {
|
||
// Build old set — O(M)
|
||
Set<Long> oldSet = new HashSet<>();
|
||
for (long id : oldList) {
|
||
oldSet.add(id);
|
||
scanOps++;
|
||
}
|
||
// Build new set — O(N)
|
||
Set<Long> 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);
|
||
}
|
||
}
|