java-topology/defects/httpd/unit/HttpdSslCipherRenegotiateAlgorithmTest.java

164 lines
5.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}