225 lines
8.5 KiB
Java
225 lines
8.5 KiB
Java
package unit;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* HttpdProxyNoproxyTest — CWE-407 model test for Apache httpd httpd-0002.
|
|
*
|
|
* Models the defect in mod_proxy.c where set_proxy_exclude and
|
|
* set_proxy_dirconn each scan the full existing array for duplicates before
|
|
* inserting a new entry. With N directives the total cost is O(N²).
|
|
*
|
|
* Defective: ArrayList scanned with strcasecmp per directive → O(N²) total
|
|
* Fixed: HashSet membership check per directive → O(N) total
|
|
*
|
|
* Parameters:
|
|
* N = 500 directives (e.g. NoProxy entries in httpd.conf)
|
|
*
|
|
* Tests:
|
|
* 1. testDefectiveCorrectness — dedup produces correct unique set
|
|
* 2. testFixedCorrectness — dedup produces correct unique set
|
|
* 3. testSlowOpCount — O(N²) comparisons measured
|
|
* 4. testFastOpCount — O(N) comparisons measured
|
|
* 5. testRatio — ratio >= 5x (expect ~249x at N=500)
|
|
*/
|
|
public class HttpdProxyNoproxyTest {
|
|
|
|
static final int N = 500; // number of NoProxy directives to load
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Instrumentation
|
|
// -----------------------------------------------------------------------
|
|
|
|
static long slowOps = 0;
|
|
static long fastOps = 0;
|
|
|
|
static boolean instrumentedStrcasecmp(String a, String b) {
|
|
slowOps++;
|
|
return a.equalsIgnoreCase(b);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Model: defective implementation (APR array + linear dedup)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static class DefectiveNoproxyConfig {
|
|
final List<String> entries = new ArrayList<>();
|
|
|
|
/** add() models set_proxy_exclude: linear scan then insert. */
|
|
void add(String host) {
|
|
for (int i = 0; i < entries.size(); i++) {
|
|
if (instrumentedStrcasecmp(entries.get(i), host)) {
|
|
return; // duplicate, skip
|
|
}
|
|
}
|
|
entries.add(host.toLowerCase(Locale.ROOT));
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Model: fixed implementation (hash set for dedup)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static class FixedNoproxyConfig {
|
|
final List<String> entries = new ArrayList<>();
|
|
final Set<String> seen = new HashSet<>();
|
|
|
|
/** add() uses a hash set for O(1) dedup. */
|
|
void add(String host) {
|
|
fastOps++;
|
|
String lower = host.toLowerCase(Locale.ROOT);
|
|
if (seen.add(lower)) {
|
|
entries.add(lower);
|
|
}
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Helpers
|
|
// -----------------------------------------------------------------------
|
|
|
|
/**
|
|
* Generate N distinct hostnames + N/2 duplicates interleaved.
|
|
* Total directives = N + N/2 = 1.5*N; unique entries = N.
|
|
*/
|
|
static List<String> buildDirectives() {
|
|
List<String> dirs = new ArrayList<>(N + N / 2);
|
|
for (int i = 0; i < N; i++) {
|
|
dirs.add("host-" + i + ".example.com");
|
|
}
|
|
// interleave N/2 duplicates (every other original entry, uppercased
|
|
// to exercise case-insensitive comparison)
|
|
for (int i = 0; i < N / 2; i++) {
|
|
dirs.add("HOST-" + (i * 2) + ".EXAMPLE.COM");
|
|
}
|
|
return dirs;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Assert helpers
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void assertTrue(String msg, boolean cond) {
|
|
if (!cond) throw new AssertionError("FAIL: " + msg);
|
|
}
|
|
|
|
static void assertEquals(String msg, Object expected, Object actual) {
|
|
if (!Objects.equals(expected, actual))
|
|
throw new AssertionError("FAIL: " + msg
|
|
+ " expected=" + expected + " actual=" + actual);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 1: defective correctness
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void testDefectiveCorrectness() {
|
|
List<String> dirs = buildDirectives();
|
|
DefectiveNoproxyConfig cfg = new DefectiveNoproxyConfig();
|
|
slowOps = 0;
|
|
for (String d : dirs) cfg.add(d);
|
|
|
|
assertEquals("defective unique count", N, cfg.entries.size());
|
|
// Verify no duplicates remain
|
|
Set<String> unique = new HashSet<>(cfg.entries);
|
|
assertEquals("defective no duplicate entries", N, unique.size());
|
|
System.out.println("[PASS] testDefectiveCorrectness entries=" + cfg.entries.size());
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 2: fixed correctness
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void testFixedCorrectness() {
|
|
List<String> dirs = buildDirectives();
|
|
FixedNoproxyConfig cfg = new FixedNoproxyConfig();
|
|
fastOps = 0;
|
|
for (String d : dirs) cfg.add(d);
|
|
|
|
assertEquals("fixed unique count", N, cfg.entries.size());
|
|
Set<String> unique = new HashSet<>(cfg.entries);
|
|
assertEquals("fixed no duplicate entries", N, unique.size());
|
|
System.out.println("[PASS] testFixedCorrectness entries=" + cfg.entries.size());
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 3: slow (defective) op count is O(N²)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void testSlowOpCount() {
|
|
List<String> dirs = buildDirectives();
|
|
DefectiveNoproxyConfig cfg = new DefectiveNoproxyConfig();
|
|
slowOps = 0;
|
|
for (String d : dirs) cfg.add(d);
|
|
long slow = slowOps;
|
|
|
|
// With N unique entries + N/2 duplicates, total comparisons are at
|
|
// least N*(N-1)/2 (triangular number for unique insertions alone).
|
|
long minExpected = (long) N * (N - 1) / 2;
|
|
assertTrue(
|
|
"slow ops (" + slow + ") >= N*(N-1)/2 (" + minExpected + ")",
|
|
slow >= minExpected
|
|
);
|
|
System.out.println("[PASS] testSlowOpCount slowOps=" + slow
|
|
+ " minExpected=" + minExpected);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 4: fast (fixed) op count is O(N)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void testFastOpCount() {
|
|
List<String> dirs = buildDirectives();
|
|
FixedNoproxyConfig cfg = new FixedNoproxyConfig();
|
|
fastOps = 0;
|
|
for (String d : dirs) cfg.add(d);
|
|
long fast = fastOps;
|
|
|
|
// Every directive costs exactly 1 hash probe (our fastOps counter),
|
|
// so fastOps == total directives = N + N/2.
|
|
long totalDirs = N + (long) N / 2;
|
|
assertEquals("fast ops equals total directive count", totalDirs, fast);
|
|
System.out.println("[PASS] testFastOpCount fastOps=" + fast);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 5: ratio >= 5x (actual is ~249x at N=500)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void testRatio() {
|
|
List<String> dirs = buildDirectives();
|
|
|
|
DefectiveNoproxyConfig defCfg = new DefectiveNoproxyConfig();
|
|
slowOps = 0;
|
|
for (String d : dirs) defCfg.add(d);
|
|
long slow = slowOps;
|
|
|
|
FixedNoproxyConfig fixCfg = new FixedNoproxyConfig();
|
|
fastOps = 0;
|
|
for (String d : dirs) fixCfg.add(d);
|
|
long fast = fastOps;
|
|
|
|
double ratio = (double) slow / fast;
|
|
System.out.printf("[INFO] slowOps=%d fastOps=%d ratio=%.1fx%n",
|
|
slow, fast, ratio);
|
|
assertTrue(
|
|
"ratio (" + ratio + ") >= 5.0x (N=" + N + ")",
|
|
ratio >= 5.0
|
|
);
|
|
System.out.printf("[PASS] testRatio ratio=%.1fx%n", ratio);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Main
|
|
// -----------------------------------------------------------------------
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== HttpdProxyNoproxyTest N=" + N + " ===");
|
|
testDefectiveCorrectness();
|
|
testFixedCorrectness();
|
|
testSlowOpCount();
|
|
testFastOpCount();
|
|
testRatio();
|
|
System.out.println("=== ALL TESTS PASSED ===");
|
|
}
|
|
}
|