element-web-0003: TextForEvent.tsx textForCanonicalAliasEvent alt_aliases .filter(x => !arr.includes(x)) O(A*B) MEDIUM 19.5x — fix: Set.has() element-web-0004: utils/arrays.ts arrayDiff + arrayIntersection .filter(i => !arr.includes(i)) O(A*B) MEDIUM 18x — fix: Set.has() Used by 15+ callsites: room-list, notifications, spaces, beacons, maps conduit-0001: server_server.rs get_missing_events_route earliest_events.contains() (Vec) in growing BFS loop O(Q*E) MEDIUM-HIGH 7.4x Federation endpoint — remote server controls input size matrix-rust-sdk: could not clone (GitHub HTTPS unreachable)
128 lines
5 KiB
Java
128 lines
5 KiB
Java
import java.util.*;
|
||
|
||
/**
|
||
* CWE-407 unit tests for Conduit (Rust Matrix homeserver) defects.
|
||
*
|
||
* conduit-0001: server_server.rs get_missing_events_route()
|
||
* body.earliest_events.contains() (Vec) inside growing BFS while-loop
|
||
* O(Q*E) where Q = queued events, E = earliest events.
|
||
* Fix: convert earliest_events to HashSet for O(1) membership test.
|
||
* Severity: MEDIUM-HIGH (federation endpoint, remote server controls input)
|
||
*/
|
||
public class ConduitTest {
|
||
|
||
// --- conduit-0001: earliest_events Vec::contains in BFS loop ---
|
||
|
||
/**
|
||
* Simulates the defective path: for each queued event, do a linear scan
|
||
* of earliest_events (ArrayList.contains -> O(E)).
|
||
* As BFS expands queued_events, total work = O(Q * E).
|
||
*/
|
||
static int getMissingEventsVec(List<String> latestEvents, List<String> earliestEvents,
|
||
Map<String, List<String>> prevEventsMap, int limit) {
|
||
List<String> queued = new ArrayList<>(latestEvents);
|
||
List<String> result = new ArrayList<>();
|
||
int i = 0;
|
||
int ops = 0;
|
||
while (i < queued.size() && result.size() < limit) {
|
||
String eventId = queued.get(i);
|
||
// Linear scan of earliestEvents — the defect
|
||
ops++;
|
||
if (earliestEvents.contains(eventId)) { // O(E) each time
|
||
i++;
|
||
continue;
|
||
}
|
||
result.add(eventId);
|
||
// Expand BFS: add prev_events
|
||
List<String> prevEvents = prevEventsMap.getOrDefault(eventId, Collections.emptyList());
|
||
queued.addAll(prevEvents);
|
||
i++;
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/**
|
||
* Simulates the fixed path: convert earliest_events to HashSet once,
|
||
* then O(1) membership test per queued event.
|
||
*/
|
||
static int getMissingEventsSet(List<String> latestEvents, List<String> earliestEvents,
|
||
Map<String, List<String>> prevEventsMap, int limit) {
|
||
List<String> queued = new ArrayList<>(latestEvents);
|
||
Set<String> earliestSet = new HashSet<>(earliestEvents); // O(E) once
|
||
List<String> result = new ArrayList<>();
|
||
int i = 0;
|
||
int ops = 0;
|
||
while (i < queued.size() && result.size() < limit) {
|
||
String eventId = queued.get(i);
|
||
ops++;
|
||
if (earliestSet.contains(eventId)) { // O(1) each time
|
||
i++;
|
||
continue;
|
||
}
|
||
result.add(eventId);
|
||
List<String> prevEvents = prevEventsMap.getOrDefault(eventId, Collections.emptyList());
|
||
queued.addAll(prevEvents);
|
||
i++;
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
static void testConduit0001() throws Exception {
|
||
// Build a synthetic DAG: E earliest events, chain of events leading back to them
|
||
int E = 500; // earliest events count
|
||
int depth = 50; // BFS depth
|
||
int limit = 10000;
|
||
|
||
List<String> earliestEvents = new ArrayList<>();
|
||
for (int i = 0; i < E; i++) {
|
||
earliestEvents.add("$earliest_" + i);
|
||
}
|
||
|
||
// Build a DAG where each event has 2 prev_events, creating a growing BFS frontier
|
||
Map<String, List<String>> prevEventsMap = new HashMap<>();
|
||
List<String> latestEvents = new ArrayList<>();
|
||
latestEvents.add("$latest_0");
|
||
|
||
// Create chain: latest -> intermediate events -> eventually hits earliest
|
||
String current = "$latest_0";
|
||
for (int d = 0; d < depth; d++) {
|
||
List<String> prevs = new ArrayList<>();
|
||
prevs.add("$event_" + d + "_a");
|
||
prevs.add("$event_" + d + "_b");
|
||
prevEventsMap.put(current, prevs);
|
||
current = "$event_" + d + "_a";
|
||
}
|
||
// Terminal events point to earliest events
|
||
List<String> terminalPrevs = new ArrayList<>();
|
||
terminalPrevs.add(earliestEvents.get(0));
|
||
terminalPrevs.add(earliestEvents.get(E / 2));
|
||
prevEventsMap.put(current, terminalPrevs);
|
||
|
||
// Correctness: both paths produce the same op count pattern
|
||
// (they process same events, just with different contains() cost)
|
||
|
||
// Performance
|
||
long t0 = System.nanoTime();
|
||
for (int r = 0; r < 500; r++) {
|
||
getMissingEventsVec(latestEvents, earliestEvents, prevEventsMap, limit);
|
||
}
|
||
long tVec = System.nanoTime() - t0;
|
||
|
||
t0 = System.nanoTime();
|
||
for (int r = 0; r < 500; r++) {
|
||
getMissingEventsSet(latestEvents, earliestEvents, prevEventsMap, limit);
|
||
}
|
||
long tSet = System.nanoTime() - t0;
|
||
|
||
double ratio = (double) tVec / tSet;
|
||
System.out.printf("conduit-0001: vec=%.3fs set=%.3fs ratio=%.1f× (E=%d, depth=%d)%n",
|
||
tVec / 1e9, tSet / 1e9, ratio, E, depth);
|
||
assert ratio > 2 : "Expected >2x speedup, got " + ratio;
|
||
System.out.println("PASS conduit-0001");
|
||
}
|
||
|
||
public static void main(String[] args) throws Exception {
|
||
testConduit0001();
|
||
System.out.println("ALL PASS");
|
||
}
|
||
}
|