diff --git a/defects/vertx/patch/vertx-0001-hamanger-nodelist-contains.patch b/defects/vertx/patch/vertx-0001-hamanger-nodelist-contains.patch new file mode 100644 index 000000000..9c49f6cb1 --- /dev/null +++ b/defects/vertx/patch/vertx-0001-hamanger-nodelist-contains.patch @@ -0,0 +1,24 @@ +# UNDF: UNDF-2026-000000570 +--- a/vertx-core/src/main/java/io/vertx/core/impl/HAManager.java ++++ b/vertx-core/src/main/java/io/vertx/core/impl/HAManager.java +@@ -304,15 +304,15 @@ public class HAManager { + // We also check for and potentially resume any previous failovers that might have failed + // We can determine this if there any ids in the cluster map which aren't in the node list +- List nodes = clusterManager.getNodes(); ++ Set nodes = new HashSet<>(clusterManager.getNodes()); + + for (Map.Entry entry: clusterMap.entrySet()) { + if (!leftNodeID.equals(entry.getKey()) && !nodes.contains(entry.getKey())) { + JsonObject haInfo = new JsonObject(entry.getValue()); + checkFailover(entry.getKey(), haInfo); + } + } + } + } + + private void addHaInfoIfLost() { +- if (clusterManager.getNodes().contains(nodeID) && !clusterMap.containsKey(nodeID)) { ++ if (new HashSet<>(clusterManager.getNodes()).contains(nodeID) && !clusterMap.containsKey(nodeID)) { + synchronized (haInfo) { + clusterMap.put(nodeID, haInfo.encode()); + } diff --git a/defects/vertx/unit/VertxTest.class b/defects/vertx/unit/VertxTest.class new file mode 100644 index 000000000..a2b431da5 Binary files /dev/null and b/defects/vertx/unit/VertxTest.class differ diff --git a/defects/vertx/unit/VertxTest.java b/defects/vertx/unit/VertxTest.java new file mode 100644 index 000000000..a54d77102 --- /dev/null +++ b/defects/vertx/unit/VertxTest.java @@ -0,0 +1,225 @@ +import java.util.*; + +/** + * CWE-407 unit tests for Vert.x core defects. + * + * vertx-0001: HAManager.nodeLeft() uses List.contains() inside a loop + * over clusterMap.entrySet() → O(N×M) where N = clusterMap size, M = node count. + * Fix: convert List to HashSet before the loop → O(N+M). + * + * Additionally: HAManager.addHaInfoIfLost() calls + * clusterManager.getNodes().contains(nodeID) on every nodeAdded/nodeLeft event + * → O(M) per event with List, O(1) with Set. + */ +public class VertxTest { + + // ----------------------------------------------------------------------- + // vertx-0001 helpers — simulate the HAManager.nodeLeft inner loop + // ----------------------------------------------------------------------- + + /** + * Defective: List.contains() called inside a for-each over + * clusterMapKeys → O(N × M) total. + * + * Mirrors HAManager lines 307-314: + * List nodes = clusterManager.getNodes(); + * for (Map.Entry entry : clusterMap.entrySet()) { + * if (!leftNodeID.equals(entry.getKey()) && !nodes.contains(entry.getKey())) { + * checkFailover(...) + * } + * } + */ + static long nodeLeftDefect(List clusterMapKeys, List nodes, String leftNodeID) { + long ops = 0; + for (String key : clusterMapKeys) { + ops++; + if (!leftNodeID.equals(key)) { + // List.contains = linear scan, O(M) worst case + boolean found = nodes.contains(key); + ops += nodes.size(); // account for full scan (worst case) + } + } + return ops; + } + + /** + * Fixed: wrap nodes in HashSet once, then O(1) membership test per entry. + */ + static long nodeLeftFixed(List clusterMapKeys, List nodes, String leftNodeID) { + Set nodeSet = new HashSet<>(nodes); // O(M) once + long ops = nodes.size(); // cost of building the set + for (String key : clusterMapKeys) { + ops++; + if (!leftNodeID.equals(key)) { + boolean found = nodeSet.contains(key); // O(1) + ops++; + } + } + return ops; + } + + // ----------------------------------------------------------------------- + // vertx-0001 test A — nodeLeft loop op-count ratio + // ----------------------------------------------------------------------- + + static void testVertx0001_opCount() { + int N = 1000; // clusterMap size + int M = 1000; // node count + + List clusterMapKeys = new ArrayList<>(N); + List nodes = new ArrayList<>(M); + + for (int i = 0; i < N; i++) clusterMapKeys.add("node-" + i); + for (int i = 0; i < M; i++) nodes.add("node-" + i); + + String leftNode = "node-0"; + + long defectOps = nodeLeftDefect(clusterMapKeys, nodes, leftNode); + long fixedOps = nodeLeftFixed(clusterMapKeys, nodes, leftNode); + double ratio = (double) defectOps / fixedOps; + + System.out.printf( + "vertx-0001 nodeLeft op-count: defect=%d fixed=%d ratio=%.1fx%n", + defectOps, fixedOps, ratio + ); + + if (ratio < 50.0) { + throw new AssertionError( + "vertx-0001: expected op-count ratio >= 50x at N=M=1000, got " + ratio); + } + System.out.println("vertx-0001 op-count: PASS"); + } + + // ----------------------------------------------------------------------- + // vertx-0001 test B — wall-clock timing + // ----------------------------------------------------------------------- + + static void testVertx0001_timing() { + int N = 500; + int M = 500; + int REPS = 200; + + List clusterMapKeys = new ArrayList<>(N); + List nodes = new ArrayList<>(M); + for (int i = 0; i < N; i++) clusterMapKeys.add("node-" + i); + for (int i = 0; i < M; i++) nodes.add("node-" + i); + + String leftNode = "node-0"; + + // Warm up + for (int r = 0; r < 10; r++) { + simulateNodeLeftDefect(clusterMapKeys, nodes, leftNode); + simulateNodeLeftFixed(clusterMapKeys, nodes, leftNode); + } + + long t0 = System.nanoTime(); + for (int r = 0; r < REPS; r++) simulateNodeLeftDefect(clusterMapKeys, nodes, leftNode); + long defectNs = System.nanoTime() - t0; + + long t1 = System.nanoTime(); + for (int r = 0; r < REPS; r++) simulateNodeLeftFixed(clusterMapKeys, nodes, leftNode); + long fixedNs = System.nanoTime() - t1; + + double ratio = (double) defectNs / fixedNs; + System.out.printf( + "vertx-0001 nodeLeft timing: defect=%.2fms fixed=%.2fms ratio=%.1fx%n", + defectNs / 1e6, fixedNs / 1e6, ratio + ); + + if (ratio < 5.0) { + throw new AssertionError( + "vertx-0001: expected wall-clock ratio >= 5x at N=M=500, got " + ratio); + } + System.out.println("vertx-0001 timing: PASS"); + } + + // Real simulation (no op counting) for timing test + static int simulateNodeLeftDefect(List clusterMapKeys, List nodes, String leftNodeID) { + int failovers = 0; + for (String key : clusterMapKeys) { + if (!leftNodeID.equals(key) && !nodes.contains(key)) { + failovers++; + } + } + return failovers; + } + + static int simulateNodeLeftFixed(List clusterMapKeys, List nodes, String leftNodeID) { + Set nodeSet = new HashSet<>(nodes); + int failovers = 0; + for (String key : clusterMapKeys) { + if (!leftNodeID.equals(key) && !nodeSet.contains(key)) { + failovers++; + } + } + return failovers; + } + + // ----------------------------------------------------------------------- + // vertx-0001 test C — correctness + // ----------------------------------------------------------------------- + + static void testVertx0001_correctness() { + List nodes = Arrays.asList("alpha", "beta", "gamma", "delta"); + + // HashSet must agree with List for all membership queries + Set nodeSet = new HashSet<>(nodes); + for (String n : nodes) { + if (nodes.contains(n) != nodeSet.contains(n)) { + throw new AssertionError("Membership mismatch for: " + n); + } + } + if (nodes.contains("omega") != nodeSet.contains("omega")) { + throw new AssertionError("Membership mismatch for omega"); + } + + // nodeLeft logic must produce identical failover sets + List clusterMapKeys = Arrays.asList("alpha", "beta", "gamma", "delta", "epsilon"); + String leftNode = "alpha"; + + // nodes = {alpha, beta, gamma, delta} clusterMap has "epsilon" extra + List defectiveFailovers = new ArrayList<>(); + for (String key : clusterMapKeys) { + if (!leftNode.equals(key) && !nodes.contains(key)) { + defectiveFailovers.add(key); + } + } + + Set fixedNodeSet = new HashSet<>(nodes); + List fixedFailovers = new ArrayList<>(); + for (String key : clusterMapKeys) { + if (!leftNode.equals(key) && !fixedNodeSet.contains(key)) { + fixedFailovers.add(key); + } + } + + if (!defectiveFailovers.equals(fixedFailovers)) { + throw new AssertionError( + "Failover lists differ: defect=" + defectiveFailovers + " fixed=" + fixedFailovers); + } + if (fixedFailovers.size() != 1 || !fixedFailovers.get(0).equals("epsilon")) { + throw new AssertionError("Expected [epsilon], got: " + fixedFailovers); + } + + // addHaInfoIfLost pattern: single .contains() call on list vs set + String targetNodeId = "delta"; + boolean listResult = nodes.contains(targetNodeId); + boolean setResult = nodeSet.contains(targetNodeId); + if (listResult != setResult) { + throw new AssertionError("addHaInfoIfLost: list/set mismatch for " + targetNodeId); + } + + System.out.println("vertx-0001 correctness: PASS"); + } + + // ----------------------------------------------------------------------- + // Main + // ----------------------------------------------------------------------- + + public static void main(String[] args) { + testVertx0001_correctness(); + testVertx0001_opCount(); + testVertx0001_timing(); + System.out.println("ALL PASS"); + } +}