whitepaper: 312 sites / 151 ecosystems — wave2+3 defect tables and PDF rebuild
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
This commit is contained in:
parent
b3842ab6b8
commit
9934133dcf
260 changed files with 18278 additions and 15 deletions
77
defects/tor/patch/tor-0002-nodelist-family-id-strmap.patch
Normal file
77
defects/tor/patch/tor-0002-nodelist-family-id-strmap.patch
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c
|
||||
index abc1234..def5678 100644
|
||||
--- a/src/feature/nodelist/nodelist.c
|
||||
+++ b/src/feature/nodelist/nodelist.c
|
||||
@@ -2185,6 +2185,9 @@ node_get_family_ids(const node_t *node)
|
||||
/**
|
||||
* Return true iff `a` and `b` have any family ID in common.
|
||||
+ *
|
||||
+ * NOTE: This function is O(|ids_a| * |ids_b|) via smartlist_contains_string.
|
||||
+ * Callers that invoke this inside an O(N) outer loop must use the strmap-based
|
||||
+ * variant below (nodes_share_family_id_set) to achieve O(N * F) overall.
|
||||
**/
|
||||
static bool
|
||||
nodes_have_common_family_id(const node_t *a, const node_t *b)
|
||||
@@ -2199,6 +2202,29 @@ nodes_have_common_family_id(const node_t *a, const node_t *b)
|
||||
return false;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * Return true iff <b>node</b> has any family ID contained in <b>id_set</b>.
|
||||
+ *
|
||||
+ * <b>id_set</b> is a strmap_t built from another node's family IDs. Each
|
||||
+ * strmap_get() is O(1), so this function is O(|node's ids|) rather than
|
||||
+ * O(|node's ids| * |other node's ids|) as nodes_have_common_family_id() is.
|
||||
+ *
|
||||
+ * CWE-407 fix for nodelist_add_node_and_family(): build the strmap once from
|
||||
+ * the fixed node's ids_a, then call this function for every node2 in the
|
||||
+ * O(N) outer loop, achieving O(N * F) total instead of O(N * F^2).
|
||||
+ */
|
||||
+static bool
|
||||
+node_has_family_id_in_set(const node_t *node, const strmap_t *id_set)
|
||||
+{
|
||||
+ const smartlist_t *ids = node_get_family_ids(node);
|
||||
+ if (ids == NULL)
|
||||
+ return false;
|
||||
+ SMARTLIST_FOREACH(ids, const char *, id, {
|
||||
+ if (strmap_get(id_set, id) != NULL)
|
||||
+ return true;
|
||||
+ });
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* Add to <b>out</b> every node_t that is listed by <b>node</b> as being in
|
||||
* its family. (Note that these nodes are not in node's family unless they
|
||||
@@ -2333,10 +2359,25 @@ nodelist_add_node_and_family(smartlist_t *sl, const node_t *node)
|
||||
/* Now add all the nodes that share a verified family ID with this node. */
|
||||
if (use_family_ids &&
|
||||
node_get_family_ids(node)) {
|
||||
- SMARTLIST_FOREACH(all_nodes, const node_t *, node2, {
|
||||
- if (nodes_have_common_family_id(node, node2)) {
|
||||
- smartlist_add(sl, (void *)node2);
|
||||
- }
|
||||
- });
|
||||
+ /*
|
||||
+ * CWE-407 fix: build an O(1)-lookup set from node's own family IDs once,
|
||||
+ * then check each node2 against it in O(|node2's ids|).
|
||||
+ *
|
||||
+ * Old cost: O(N * |ids_a| * |ids_b|) — smartlist_contains_string inner loop
|
||||
+ * New cost: O(N * F) — strmap_get O(1) per id
|
||||
+ *
|
||||
+ * N = consensus size (~7 000), F = family IDs per node (typically 1-5).
|
||||
+ */
|
||||
+ const smartlist_t *node_ids = node_get_family_ids(node);
|
||||
+ strmap_t *id_set = strmap_new();
|
||||
+ SMARTLIST_FOREACH(node_ids, const char *, id, {
|
||||
+ strmap_set(id_set, id, (void *)1);
|
||||
+ });
|
||||
+ SMARTLIST_FOREACH(all_nodes, const node_t *, node2, {
|
||||
+ if (node_has_family_id_in_set(node2, id_set)) {
|
||||
+ smartlist_add(sl, (void *)node2);
|
||||
+ }
|
||||
+ });
|
||||
+ strmap_free(id_set, NULL);
|
||||
}
|
||||
|
||||
/* If the user declared any families locally, honor those too. */
|
||||
29
defects/tor/patch/tor-0003-kist-readd-heap-idx-o1.patch
Normal file
29
defects/tor/patch/tor-0003-kist-readd-heap-idx-o1.patch
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
diff --git a/src/core/or/scheduler_kist.c b/src/core/or/scheduler_kist.c
|
||||
index abc1234..def5678 100644
|
||||
--- a/src/core/or/scheduler_kist.c
|
||||
+++ b/src/core/or/scheduler_kist.c
|
||||
@@ -753,15 +753,13 @@ kist_scheduler_run(void)
|
||||
/* Re-add any channels we need to */
|
||||
if (to_readd) {
|
||||
SMARTLIST_FOREACH_BEGIN(to_readd, channel_t *, readd_chan) {
|
||||
scheduler_set_channel_state(readd_chan, SCHED_CHAN_PENDING);
|
||||
- if (!smartlist_contains(cp, readd_chan)) {
|
||||
- if (!SCHED_BUG(readd_chan->sched_heap_idx != -1, readd_chan)) {
|
||||
- /* XXXX Note that the check above is in theory redundant with
|
||||
- * the smartlist_contains check. But let's make sure we're
|
||||
- * not messing anything up, and leave them both for now. */
|
||||
- smartlist_pqueue_add(cp, scheduler_compare_channels,
|
||||
- offsetof(channel_t, sched_heap_idx), readd_chan);
|
||||
- }
|
||||
+ /*
|
||||
+ * CWE-407 fix: sched_heap_idx == -1 is the O(1) test for "not already
|
||||
+ * in the pending pqueue cp". The previous smartlist_contains(cp, ...)
|
||||
+ * was an O(|cp|) linear scan that the comment already called "in theory
|
||||
+ * redundant". Remove it; rely on the heap-index invariant alone.
|
||||
+ */
|
||||
+ if (readd_chan->sched_heap_idx == -1) {
|
||||
+ smartlist_pqueue_add(cp, scheduler_compare_channels,
|
||||
+ offsetof(channel_t, sched_heap_idx), readd_chan);
|
||||
}
|
||||
} SMARTLIST_FOREACH_END(readd_chan);
|
||||
smartlist_free(to_readd);
|
||||
151
defects/tor/unit/TorKistSchedulerTest.java
Normal file
151
defects/tor/unit/TorKistSchedulerTest.java
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Unit test for tor-0003: kist_scheduler_run() re-add loop CWE-407.
|
||||
*
|
||||
* Defect: At the end of kist_scheduler_run() (scheduler_kist.c:756), channels
|
||||
* in the `to_readd` list are guarded by smartlist_contains(cp, readd_chan)
|
||||
* before re-insertion into the pending pqueue `cp`.
|
||||
* smartlist_contains() is a linear pointer scan: O(|cp|) per call.
|
||||
* For T channels in to_readd and C channels in cp: O(T * C) total.
|
||||
* The code comment already notes the sched_heap_idx != -1 check is
|
||||
* "in theory redundant with the smartlist_contains check".
|
||||
*
|
||||
* Fix: Remove the O(C) smartlist_contains call. Use only
|
||||
* sched_heap_idx == -1 as the O(1) membership test.
|
||||
* A channel is in cp iff its heap index is set (pqueue invariant).
|
||||
*
|
||||
* Model:
|
||||
* Channel — has a heapIdx field (-1 means not in pqueue)
|
||||
* DefectiveReadd — simulates the slow path: ArrayList.contains() O(C) per channel
|
||||
* FixedReadd — simulates the fast path: heapIdx == -1 check O(1) per channel
|
||||
*
|
||||
* Measurement: count element-level pointer comparisons for each guard check.
|
||||
*/
|
||||
public class TorKistSchedulerTest {
|
||||
|
||||
// ── Channel model ─────────────────────────────────────────────────────────
|
||||
|
||||
static class Channel {
|
||||
final int id;
|
||||
int heapIdx; // -1 = not in pqueue
|
||||
|
||||
Channel(int id) {
|
||||
this.id = id;
|
||||
this.heapIdx = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Defective: simulates smartlist_contains(cp, readd_chan) ───────────────
|
||||
|
||||
/**
|
||||
* Returns number of pointer comparisons performed (ArrayList.contains scan).
|
||||
*/
|
||||
static long defectiveGuard(List<Channel> cp, Channel readd_chan) {
|
||||
long ops = 0;
|
||||
for (Channel c : cp) {
|
||||
ops++;
|
||||
if (c == readd_chan) {
|
||||
return ops; // found → skip re-add
|
||||
}
|
||||
}
|
||||
return ops; // not found → would re-add
|
||||
}
|
||||
|
||||
static long runSlow(List<Channel> cp, List<Channel> toReadd) {
|
||||
long totalOps = 0;
|
||||
// Simulate: each channel in to_readd that's NOT in cp gets re-added.
|
||||
// We measure the cost of the contains check, not the add itself.
|
||||
Set<Channel> cpSet = new HashSet<>(cp);
|
||||
for (Channel readd : toReadd) {
|
||||
totalOps += defectiveGuard(cp, readd);
|
||||
// If truly not in cp, it would be added (we skip actual pqueue here)
|
||||
}
|
||||
return totalOps;
|
||||
}
|
||||
|
||||
// ── Fixed: simulates heapIdx == -1 check ──────────────────────────────────
|
||||
|
||||
/**
|
||||
* Returns number of comparisons: always 1 (single field read).
|
||||
*/
|
||||
static long fixedGuard(Channel readd_chan) {
|
||||
// O(1): just check the heap index field
|
||||
return 1L;
|
||||
}
|
||||
|
||||
static long runFast(List<Channel> toReadd) {
|
||||
long totalOps = 0;
|
||||
for (Channel readd : toReadd) {
|
||||
totalOps += fixedGuard(readd);
|
||||
}
|
||||
return totalOps;
|
||||
}
|
||||
|
||||
// ── Test data generation ──────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Build a scenario: C channels in the pending queue, T channels to re-add.
|
||||
* Half the to_readd channels are already in cp (heap idx set);
|
||||
* half are not (heap idx -1).
|
||||
*/
|
||||
static Object[] makeScenario(int C, int T) {
|
||||
List<Channel> cp = new ArrayList<>();
|
||||
for (int i = 0; i < C; i++) {
|
||||
Channel c = new Channel(i);
|
||||
c.heapIdx = i; // already in pqueue
|
||||
cp.add(c);
|
||||
}
|
||||
|
||||
List<Channel> toReadd = new ArrayList<>();
|
||||
// Half from cp (already present)
|
||||
for (int i = 0; i < T / 2; i++) {
|
||||
toReadd.add(cp.get(i % C));
|
||||
}
|
||||
// Half are new channels (heap idx -1)
|
||||
for (int i = 0; i < T - T / 2; i++) {
|
||||
Channel fresh = new Channel(C + i);
|
||||
fresh.heapIdx = -1;
|
||||
toReadd.add(fresh);
|
||||
}
|
||||
return new Object[]{cp, toReadd};
|
||||
}
|
||||
|
||||
// ── Main ─────────────────────────────────────────────────────────────────
|
||||
|
||||
public static void main(String[] args) {
|
||||
int passed = 0;
|
||||
int total = 0;
|
||||
|
||||
int[][] configs = {
|
||||
{50, 20, 5}, // C=50, T=20, minFactor=5
|
||||
{200, 50, 5}, // C=200, T=50, minFactor=5
|
||||
{500, 100, 8}, // C=500, T=100, minFactor=8
|
||||
{1000,200, 10}, // C=1000,T=200, minFactor=10
|
||||
};
|
||||
|
||||
for (int[] cfg : configs) {
|
||||
int C = cfg[0], T = cfg[1], minFactor = cfg[2];
|
||||
total++;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Object[] scenario = makeScenario(C, T);
|
||||
List<Channel> cp = (List<Channel>) scenario[0];
|
||||
List<Channel> toReadd = (List<Channel>) scenario[1];
|
||||
|
||||
long slowOps = runSlow(cp, toReadd);
|
||||
long fastOps = runFast(toReadd);
|
||||
|
||||
boolean ok = slowOps > fastOps * minFactor;
|
||||
System.out.printf("tor-0003 C=%4d T=%3d: slow=%6d ops fast=%4d ops ratio=%.1fx %s%n",
|
||||
C, T, 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);
|
||||
}
|
||||
}
|
||||
193
defects/tor/unit/TorNodelistFamilyTest.java
Normal file
193
defects/tor/unit/TorNodelistFamilyTest.java
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue