java-topology/defects/openssh/unit/OpenSshSigAlgsTest.java

142 lines
5.2 KiB
Java

package unit;
import java.util.HashSet;
/**
* openssh-0001 / openssh-0002: CWE-407 O(N²) dedup via match_list inside loop
*
* Models kex_assemble_server_sig_algs() and kex_names_cat():
* SLOW: dedup by scanning a growing comma-separated string (match_list pattern)
* FAST: dedup using a HashSet with O(1) contains
*
* No JUnit — compile and run standalone.
*/
public class OpenSshSigAlgsTest {
// -----------------------------------------------------------------------
// SLOW: mirrors C kex_assemble_server_sig_algs / kex_names_cat dedup
// For each new alg, scan the entire accumulated string for a match.
// Returns op count (triangular sum proxy).
// -----------------------------------------------------------------------
static long slowDedup(String[] algs) {
String accumulated = "";
long ops = 0;
for (String alg : algs) {
// match_list scan: split accumulated on comma, strcmp each token
if (!accumulated.isEmpty()) {
String[] tokens = accumulated.split(",");
for (String t : tokens) {
ops++;
if (t.equals(alg)) {
// duplicate — skip
alg = null;
break;
}
}
}
if (alg != null) {
accumulated = accumulated.isEmpty() ? alg : accumulated + "," + alg;
}
}
return ops;
}
// -----------------------------------------------------------------------
// FAST: HashSet O(1) contains — the fix
// -----------------------------------------------------------------------
static long fastDedup(String[] algs) {
HashSet<String> seen = new HashSet<>();
StringBuilder result = new StringBuilder();
long ops = 0;
for (String alg : algs) {
ops++;
if (!seen.contains(alg)) {
seen.add(alg);
if (result.length() > 0) result.append(',');
result.append(alg);
}
}
return ops;
}
// -----------------------------------------------------------------------
// Helper: generate N unique algorithm name strings
// -----------------------------------------------------------------------
static String[] makeAlgs(int n) {
String[] algs = new String[n];
for (int i = 0; i < n; i++) {
algs[i] = "alg-" + i;
}
return algs;
}
public static void main(String[] args) {
int tests = 0, passed = 0;
// --- correctness tests ---
int[] correctnessSizes = {1, 5, 10, 20};
for (int n : correctnessSizes) {
tests++;
String[] algs = makeAlgs(n);
long slowOps = slowDedup(algs);
long fastOps = fastDedup(algs);
// Both should produce same logical result; fast ops == n (one per alg)
if (fastOps == n) {
passed++;
System.out.println("PASS correctness n=" + n
+ " slow_ops=" + slowOps + " fast_ops=" + fastOps);
} else {
System.out.println("FAIL correctness n=" + n
+ " expected fast_ops=" + n + " got=" + fastOps);
}
}
// --- dedup correctness: all-duplicate input ---
{
tests++;
int n = 20;
String[] algs = new String[n];
for (int i = 0; i < n; i++) algs[i] = "same-alg";
long slowOps = slowDedup(algs);
long fastOps = fastDedup(algs);
// fast: n ops; slow: 0+1+1+...+1 = n-1 (first hit terminates each scan)
// key check: fast == n, slow >= n-1
if (fastOps == n && slowOps >= n - 1) {
passed++;
System.out.println("PASS dedup-all-same n=" + n
+ " slow_ops=" + slowOps + " fast_ops=" + fastOps);
} else {
System.out.println("FAIL dedup-all-same n=" + n
+ " slow_ops=" + slowOps + " fast_ops=" + fastOps);
}
}
// --- speedup benchmark ---
int[] benchSizes = {100, 200, 500, 1000};
for (int n : benchSizes) {
tests++;
String[] algs = makeAlgs(n);
long slowOps = slowDedup(algs);
long fastOps = fastDedup(algs);
// triangular: slow = 0+1+2+...+(n-1) = n*(n-1)/2
long expected_slow = (long) n * (n - 1) / 2;
double ratio = (double) slowOps / fastOps;
// Require ratio >= 5x at n=500
boolean ok = (n < 500) ? (ratio >= 2.0) : (ratio >= 5.0);
if (ok) {
passed++;
System.out.printf("PASS speedup n=%d slow_ops=%d fast_ops=%d ratio=%.1fx%n",
n, slowOps, fastOps, ratio);
} else {
System.out.printf("FAIL speedup n=%d slow_ops=%d fast_ops=%d ratio=%.1fx (need >=5x at n>=500)%n",
n, slowOps, fastOps, ratio);
}
}
System.out.println("\n" + passed + "/" + tests + " PASS");
if (passed != tests) System.exit(1);
}
}