Add 88 new defect entries to HIGH and MEDIUM tables:
HIGH: mysql-0001/0002, mariadb-0001, redis-0001/0002, valkey-0001/0002, openvpn-0001,
vlc-0001, prometheus-0001, otel-collector-0001, cockroachdb-0001..0004,
tidb-0001..0008, kubernetes-0001/0002, go-0001, kotlin-0002, scala-0001,
allegro5-0001, sdl2-0001, grafana-0001, clickhouse-0001, duckdb-0001,
mongodb-0001, envoy-0001, istio-0001, cilium-0001, linkerd2-0001,
linux-0001/0002/0003, tor-0002/0003, curl-0001, julia-0001, lua-0001,
perl5-0001, nats-0001, spring-0003/0004, tomcat-0001, onos-0002, odl-0002
MEDIUM: helm-0001, mariadb-0002, openssl-0001/0002, memcached-0001,
cassandra-0001..0004, flink-0001, storm-0001/0002, zookeeper-0001..0003,
pip-0001, gradle-0001, nginx-0001, haproxy-0001, caddy-0001, varnish-0001,
ffmpeg-0001, gstreamer-0001, raylib-0001, love2d-0001, php-0001/0002,
r-source-0001, cpython-0002, ruby-0001, rabbitmq-0003/0004, activemq-0001,
ovs-0001, onos-0003, odl-0002, jetty-0001
PDF: 976K
193 lines
7.3 KiB
Java
193 lines
7.3 KiB
Java
package unit;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* Unit test for tor-0002: nodelist_add_node_and_family() CWE-407.
|
|
*
|
|
* Defect: nodelist_add_node_and_family() (nodelist.c:2337) iterates all_nodes
|
|
* (size N) and for each node2 calls nodes_have_common_family_id(node, node2).
|
|
* nodes_have_common_family_id iterates ids_a and for each id calls
|
|
* smartlist_contains_string(ids_b, id) — a full linear scan of ids_b.
|
|
* Total cost when no match: O(N * |ids_a| * |ids_b|) = O(N * F^2).
|
|
*
|
|
* Fix: Before the outer loop, build a HashSet from node's own family IDs.
|
|
* For each node2, iterate node2's ids and do O(1) HashSet.contains().
|
|
* Total cost: O(N * F).
|
|
*
|
|
* Model:
|
|
* DefectiveMatcher — for each node2: nested loop scan of ids_b per id in ids_a (O(F^2))
|
|
* FixedMatcher — HashSet built once from ids_a; per node2: one pass over ids_b (O(F))
|
|
*
|
|
* Worst-case scenario: source node's IDs are disjoint from all candidate nodes' IDs.
|
|
* This is the common case (most relays are NOT in the same family).
|
|
* The full scan always runs in the defective path; the fixed path exits early if found.
|
|
*/
|
|
public class TorNodelistFamilyTest {
|
|
|
|
// ── Membership implementations ────────────────────────────────────────────
|
|
|
|
/**
|
|
* Defective: simulates nodes_have_common_family_id() —
|
|
* outer loop over ids_a, inner smartlist_contains_string scan of ids_b.
|
|
* Returns total comparison count. No short-circuit on IDs in different families.
|
|
*/
|
|
static long defectiveCheck(List<String> ids_a, List<String> ids_b) {
|
|
long ops = 0;
|
|
for (String id : ids_a) {
|
|
for (String candidate : ids_b) {
|
|
ops++;
|
|
if (id.equals(candidate)) {
|
|
return ops; // short-circuit on match (mirrors C code)
|
|
}
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
/**
|
|
* Fixed: build a HashSet from ids_a once (caller does this before outer loop).
|
|
* Per call: iterate ids_b and call HashSet.contains() — O(1) per check.
|
|
*/
|
|
static long fixedCheck(Set<String> id_set, List<String> ids_b) {
|
|
long ops = 0;
|
|
for (String id : ids_b) {
|
|
ops++; // one O(1) hash lookup per id
|
|
if (id_set.contains(id)) {
|
|
return ops;
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// ── Test data generation ──────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Generate F family IDs for a node. IDs are globally unique (no sharing),
|
|
* modelling the worst case: no family members, full scan required every time.
|
|
*/
|
|
static List<String> makeDisjointIds(int nodeIndex, int F) {
|
|
List<String> ids = new ArrayList<>();
|
|
for (int j = 0; j < F; j++) {
|
|
ids.add("fam:" + nodeIndex + ":" + j);
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
/**
|
|
* Generate IDs where the last ID of ids_a matches the last ID of ids_b.
|
|
* Models the worst-case scan depth: match only found at end of both lists.
|
|
*/
|
|
static List<String> makeLastMatchIds(int nodeIndex, int F, String sharedId) {
|
|
List<String> ids = new ArrayList<>();
|
|
for (int j = 0; j < F - 1; j++) {
|
|
ids.add("fam:" + nodeIndex + ":" + j);
|
|
}
|
|
ids.add(sharedId); // shared at end — maximises scan depth
|
|
return ids;
|
|
}
|
|
|
|
// ── Benchmark ────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* All-disjoint case: every pair has no match — maximum scan for slow path.
|
|
* Slow: O(N * F^2). Fast: O(N * F).
|
|
*/
|
|
static long[] runDisjoint(int N, int F) {
|
|
List<String> sourceIds = makeDisjointIds(0, F);
|
|
List<List<String>> allNodes = new ArrayList<>();
|
|
for (int i = 1; i <= N; i++) {
|
|
allNodes.add(makeDisjointIds(i, F));
|
|
}
|
|
|
|
long slowOps = 0;
|
|
for (List<String> otherIds : allNodes) {
|
|
slowOps += defectiveCheck(sourceIds, otherIds);
|
|
}
|
|
|
|
Set<String> id_set = new HashSet<>(sourceIds);
|
|
long fastOps = 0;
|
|
for (List<String> otherIds : allNodes) {
|
|
fastOps += fixedCheck(id_set, otherIds);
|
|
}
|
|
|
|
return new long[]{slowOps, fastOps};
|
|
}
|
|
|
|
/**
|
|
* Last-match case: match only at the end of both id lists.
|
|
* Slow: O(N * F^2) worst-case depth. Fast: O(N * F).
|
|
*/
|
|
static long[] runLastMatch(int N, int F) {
|
|
String shared = "shared:family:id";
|
|
List<String> sourceIds = makeLastMatchIds(0, F, shared);
|
|
List<List<String>> allNodes = new ArrayList<>();
|
|
for (int i = 1; i <= N; i++) {
|
|
allNodes.add(makeLastMatchIds(i, F, shared));
|
|
}
|
|
|
|
long slowOps = 0;
|
|
for (List<String> otherIds : allNodes) {
|
|
slowOps += defectiveCheck(sourceIds, otherIds);
|
|
}
|
|
|
|
Set<String> id_set = new HashSet<>(sourceIds);
|
|
long fastOps = 0;
|
|
for (List<String> otherIds : allNodes) {
|
|
fastOps += fixedCheck(id_set, otherIds);
|
|
}
|
|
|
|
return new long[]{slowOps, fastOps};
|
|
}
|
|
|
|
// ── Main ─────────────────────────────────────────────────────────────────
|
|
|
|
public static void main(String[] args) {
|
|
int passed = 0;
|
|
int total = 0;
|
|
|
|
// Disjoint scenario: no matches, full scan always.
|
|
// Ratio = F (slow scans F ids per pair; fast does 1 hash check per id in ids_b = F total).
|
|
// slow = N*F*F, fast = N*F → ratio = F.
|
|
int[][] disjointCfg = {
|
|
{50, 3, 2}, // ratio=3
|
|
{200, 5, 4}, // ratio=5
|
|
{500, 5, 4}, // ratio=5
|
|
{500, 10, 9}, // ratio=10
|
|
};
|
|
|
|
for (int[] cfg : disjointCfg) {
|
|
int N = cfg[0], F = cfg[1], minFactor = cfg[2];
|
|
total++;
|
|
long[] ops = runDisjoint(N, F);
|
|
long slowOps = ops[0], fastOps = ops[1];
|
|
boolean ok = slowOps > fastOps * minFactor;
|
|
System.out.printf("tor-0002 disjoint N=%4d F=%2d: slow=%7d fast=%5d ratio=%.1fx %s%n",
|
|
N, F, slowOps, fastOps, (double) slowOps / fastOps,
|
|
ok ? "PASS" : "FAIL");
|
|
if (ok) passed++;
|
|
}
|
|
|
|
// Last-match scenario: match at end of both lists
|
|
int[][] lastMatchCfg = {
|
|
{50, 3, 2},
|
|
{200, 5, 4},
|
|
{500, 8, 7},
|
|
};
|
|
|
|
for (int[] cfg : lastMatchCfg) {
|
|
int N = cfg[0], F = cfg[1], minFactor = cfg[2];
|
|
total++;
|
|
long[] ops = runLastMatch(N, F);
|
|
long slowOps = ops[0], fastOps = ops[1];
|
|
boolean ok = slowOps > fastOps * minFactor;
|
|
System.out.printf("tor-0002 lastmatch N=%4d F=%2d: slow=%7d fast=%5d ratio=%.1fx %s%n",
|
|
N, F, slowOps, fastOps, (double) slowOps / fastOps,
|
|
ok ? "PASS" : "FAIL");
|
|
if (ok) passed++;
|
|
}
|
|
|
|
System.out.printf("%d/%d PASS%n", passed, total);
|
|
if (passed != total) System.exit(1);
|
|
}
|
|
}
|