diff --git a/defects/hibernate-orm-0001/patch/hibernate-orm-0001-fk-secondpass-list-contains.patch b/defects/hibernate-orm-0001/patch/hibernate-orm-0001-fk-secondpass-list-contains.patch new file mode 100644 index 000000000..075bfe640 --- /dev/null +++ b/defects/hibernate-orm-0001/patch/hibernate-orm-0001-fk-secondpass-list-contains.patch @@ -0,0 +1,35 @@ +# UNDF: +# Defect: hibernate-orm-0001 +# Component: org.hibernate.boot.internal.InFlightMetadataCollectorImpl +# Pattern: CWE-407 — List.contains() in recursive FK ordering +# Severity: HIGH +# Complexity: O(N²) in buildRecursiveOrderedFkSecondPasses → O(N) with LinkedHashSet +# Description: buildRecursiveOrderedFkSecondPasses recursively walks FK +# dependencies and calls orderedFkSecondPasses.contains(fkSecondPass) on +# an ArrayList before inserting at position 0 (also O(N) for the shift). +# With N foreign keys, the contains check alone is O(N²). Fix: maintain +# a companion HashSet for O(1) membership test. The add(0, ...) shift +# cost remains but the contains() becomes O(1). +--- a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java ++++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java +@@ -1835,12 +1835,14 @@ + private void buildRecursiveOrderedFkSecondPasses( + List orderedFkSecondPasses, ++ Set orderedFkSecondPassSet, + Map> isADependencyOf, + String startTable, + String currentTable) { + final Set dependencies = isADependencyOf.get( currentTable ); + if ( dependencies != null ) { + for ( var fkSecondPass : dependencies ) { + final String dependentTable = fkSecondPass.getValue().getTable().getQualifiedTableName().render(); + if ( dependentTable.compareTo( startTable ) != 0 ) { +- buildRecursiveOrderedFkSecondPasses( orderedFkSecondPasses, isADependencyOf, startTable, dependentTable ); ++ buildRecursiveOrderedFkSecondPasses( orderedFkSecondPasses, orderedFkSecondPassSet, isADependencyOf, startTable, dependentTable ); + } +- if ( !orderedFkSecondPasses.contains( fkSecondPass ) ) { ++ if ( !orderedFkSecondPassSet.contains( fkSecondPass ) ) { ++ orderedFkSecondPassSet.add( fkSecondPass ); + orderedFkSecondPasses.add( 0, fkSecondPass ); + } + } diff --git a/defects/hibernate-orm-0001/test/HibernateOrm0001Test.java b/defects/hibernate-orm-0001/test/HibernateOrm0001Test.java new file mode 100644 index 000000000..56dec88c7 --- /dev/null +++ b/defects/hibernate-orm-0001/test/HibernateOrm0001Test.java @@ -0,0 +1,127 @@ +import java.util.*; + +/** + * Unit test for hibernate-orm-0001: InFlightMetadataCollectorImpl + * .buildRecursiveOrderedFkSecondPasses List.contains() O(N²) + * → companion HashSet O(N). + */ +public class HibernateOrm0001Test { + + // Simulate FkSecondPass as a simple wrapper + static class FkSecondPass { + final String table; + final String dependentTable; + FkSecondPass(String table, String dependentTable) { + this.table = table; + this.dependentTable = dependentTable; + } + @Override public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof FkSecondPass)) return false; + FkSecondPass that = (FkSecondPass) o; + return table.equals(that.table) && dependentTable.equals(that.dependentTable); + } + @Override public int hashCode() { + return Objects.hash(table, dependentTable); + } + } + + // --- BEFORE: O(N²) with List.contains() --- + static void buildRecursiveBefore( + List ordered, + Map> deps, + String startTable, String currentTable) { + Set dependencies = deps.get(currentTable); + if (dependencies != null) { + for (FkSecondPass fk : dependencies) { + if (!fk.dependentTable.equals(startTable)) { + buildRecursiveBefore(ordered, deps, startTable, fk.dependentTable); + } + if (!ordered.contains(fk)) { // O(N) linear scan + ordered.add(0, fk); + } + } + } + } + + // --- AFTER: O(N) with companion HashSet --- + static void buildRecursiveAfter( + List ordered, + Set orderedSet, + Map> deps, + String startTable, String currentTable) { + Set dependencies = deps.get(currentTable); + if (dependencies != null) { + for (FkSecondPass fk : dependencies) { + if (!fk.dependentTable.equals(startTable)) { + buildRecursiveAfter(ordered, orderedSet, deps, startTable, fk.dependentTable); + } + if (!orderedSet.contains(fk)) { // O(1) lookup + orderedSet.add(fk); + ordered.add(0, fk); + } + } + } + } + + public static void main(String[] args) { + // Build a chain of N tables: t0 → t1 → t2 → ... → tN + int N = 500; + Map> deps = new HashMap<>(); + for (int i = 0; i < N - 1; i++) { + String table = "t" + i; + String depTable = "t" + (i + 1); + deps.computeIfAbsent(table, k -> new LinkedHashSet<>()) + .add(new FkSecondPass(table, depTable)); + } + + // Correctness + List beforeList = new ArrayList<>(); + buildRecursiveBefore(beforeList, deps, "t0", "t0"); + + List afterList = new ArrayList<>(); + Set afterSet = new HashSet<>(); + buildRecursiveAfter(afterList, afterSet, deps, "t0", "t0"); + + assert beforeList.size() == afterList.size() : + "Size mismatch: " + beforeList.size() + " vs " + afterList.size(); + for (int i = 0; i < beforeList.size(); i++) { + assert beforeList.get(i).equals(afterList.get(i)) : + "Mismatch at index " + i; + } + System.out.println("PASS correctness: " + beforeList.size() + " FK passes ordered identically"); + + // Performance + int iterations = 200; + + // Warmup + for (int w = 0; w < 20; w++) { + List tmp = new ArrayList<>(); + buildRecursiveBefore(tmp, deps, "t0", "t0"); + tmp = new ArrayList<>(); + Set ts = new HashSet<>(); + buildRecursiveAfter(tmp, ts, deps, "t0", "t0"); + } + + long t0 = System.nanoTime(); + for (int i = 0; i < iterations; i++) { + List tmp = new ArrayList<>(); + buildRecursiveBefore(tmp, deps, "t0", "t0"); + } + long beforeNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < iterations; i++) { + List tmp = new ArrayList<>(); + Set ts = new HashSet<>(); + buildRecursiveAfter(tmp, ts, deps, "t0", "t0"); + } + long afterNs = System.nanoTime() - t0; + + double ratio = (double) beforeNs / afterNs; + System.out.printf("PASS performance: before=%dms after=%dms ratio=%.1fx (N=%d)%n", + beforeNs / 1_000_000, afterNs / 1_000_000, ratio, N); + assert ratio > 2.0 : "Expected at least 2x speedup, got " + ratio; + System.out.println("PASS all tests for hibernate-orm-0001"); + } +} diff --git a/defects/netty-0001/patch/netty-0001-alpn-list-contains.patch b/defects/netty-0001/patch/netty-0001-alpn-list-contains.patch new file mode 100644 index 000000000..e89e21754 --- /dev/null +++ b/defects/netty-0001/patch/netty-0001-alpn-list-contains.patch @@ -0,0 +1,46 @@ +# UNDF: +# Defect: netty-0001 +# Component: io.netty.handler.ssl.JdkBaseApplicationProtocolNegotiator +# Pattern: CWE-407 — List.contains() in ALPN protocol negotiation +# Severity: MEDIUM +# Complexity: O(S×P) per TLS handshake → O(S+P) with HashSet +# Description: NoFailProtocolSelector.select() iterates supportedProtocols +# (a Set) and calls protocols.contains(p) where 'protocols' is a +# List. Each contains() is O(P). With S supported and P offered +# protocols, total is O(S×P). Similarly, NoFailProtocolSelectionListener +# .selected() calls supportedProtocols.contains(protocol) on a +# List. Fix: convert the List parameter to a HashSet for O(1) +# lookup in the select() method. +--- a/handler/src/main/java/io/netty/handler/ssl/JdkBaseApplicationProtocolNegotiator.java ++++ b/handler/src/main/java/io/netty/handler/ssl/JdkBaseApplicationProtocolNegotiator.java +@@ -144,7 +144,8 @@ + @Override + public String select(List protocols) throws Exception { ++ Set protocolSet = new HashSet<>(protocols); + for (String p : supportedProtocols) { +- if (protocols.contains(p)) { ++ if (protocolSet.contains(p)) { + engineWrapper.setNegotiatedApplicationProtocol(p); + return p; + } +@@ -171,12 +172,14 @@ + private static class NoFailProtocolSelectionListener implements ProtocolSelectionListener { + private final JdkSslEngine engineWrapper; +- private final List supportedProtocols; ++ private final Set supportedProtocolSet; + +- NoFailProtocolSelectionListener(JdkSslEngine engineWrapper, List supportedProtocols) { ++ NoFailProtocolSelectionListener(JdkSslEngine engineWrapper, List supportedProtocols) { + this.engineWrapper = engineWrapper; +- this.supportedProtocols = supportedProtocols; ++ this.supportedProtocolSet = new HashSet<>(supportedProtocols); + } + +@@ -185,7 +188,7 @@ + @Override + public void selected(String protocol) throws Exception { +- if (supportedProtocols.contains(protocol)) { ++ if (supportedProtocolSet.contains(protocol)) { + engineWrapper.setNegotiatedApplicationProtocol(protocol); + } else { + noSelectedMatchFound(protocol); diff --git a/defects/netty-0001/test/Netty0001Test.java b/defects/netty-0001/test/Netty0001Test.java new file mode 100644 index 000000000..bbf96bfaa --- /dev/null +++ b/defects/netty-0001/test/Netty0001Test.java @@ -0,0 +1,88 @@ +import java.util.*; + +/** + * Unit test for netty-0001: JdkBaseApplicationProtocolNegotiator + * ALPN select() List.contains() O(S×P) → HashSet O(S+P). + */ +public class Netty0001Test { + + // --- BEFORE: O(S×P) linear scan --- + static String selectBefore(Set supportedProtocols, List protocols) { + for (String p : supportedProtocols) { + if (protocols.contains(p)) { // O(P) per iteration + return p; + } + } + return null; + } + + // --- AFTER: O(S+P) with HashSet --- + static String selectAfter(Set supportedProtocols, List protocols) { + Set protocolSet = new HashSet<>(protocols); + for (String p : supportedProtocols) { + if (protocolSet.contains(p)) { // O(1) per iteration + return p; + } + } + return null; + } + + // Simulate the selected() listener path + static boolean selectedBefore(List supportedProtocols, String protocol) { + return supportedProtocols.contains(protocol); // O(S) + } + + static boolean selectedAfter(Set supportedProtocolSet, String protocol) { + return supportedProtocolSet.contains(protocol); // O(1) + } + + public static void main(String[] args) { + // Correctness: select() + Set supported = new LinkedHashSet<>(Arrays.asList("h2", "http/1.1", "spdy/3.1")); + List offered = Arrays.asList("spdy/3.1", "h2", "http/1.1"); + + String beforeResult = selectBefore(supported, offered); + String afterResult = selectAfter(supported, offered); + assert Objects.equals(beforeResult, afterResult) : "Results must match"; + System.out.println("PASS correctness select: '" + beforeResult + "'"); + + // Correctness: selected() + List supportedList = Arrays.asList("h2", "http/1.1"); + Set supportedSet = new HashSet<>(supportedList); + assert selectedBefore(supportedList, "h2") == selectedAfter(supportedSet, "h2"); + assert selectedBefore(supportedList, "spdy") == selectedAfter(supportedSet, "spdy"); + System.out.println("PASS correctness selected"); + + // Performance: S=500 supported, P=500 offered, worst-case no match + int S = 500, P = 500; + Set bigSupported = new LinkedHashSet<>(); + for (int i = 0; i < S; i++) bigSupported.add("supported-" + i); + List bigOffered = new ArrayList<>(); + for (int i = 0; i < P; i++) bigOffered.add("offered-" + i); // no overlap + + // Warmup + for (int i = 0; i < 1000; i++) { + selectBefore(bigSupported, bigOffered); + selectAfter(bigSupported, bigOffered); + } + + int iterations = 5000; + long t0 = System.nanoTime(); + for (int i = 0; i < iterations; i++) { + selectBefore(bigSupported, bigOffered); + } + long beforeNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < iterations; i++) { + selectAfter(bigSupported, bigOffered); + } + long afterNs = System.nanoTime() - t0; + + double ratio = (double) beforeNs / afterNs; + System.out.printf("PASS performance: before=%dms after=%dms ratio=%.1fx (S=%d P=%d)%n", + beforeNs / 1_000_000, afterNs / 1_000_000, ratio, S, P); + assert ratio > 2.0 : "Expected at least 2x speedup, got " + ratio; + System.out.println("PASS all tests for netty-0001"); + } +} diff --git a/defects/netty-0002/patch/netty-0002-dns-resolve-dedup-list-contains.patch b/defects/netty-0002/patch/netty-0002-dns-resolve-dedup-list-contains.patch new file mode 100644 index 000000000..bcdde8a7f --- /dev/null +++ b/defects/netty-0002/patch/netty-0002-dns-resolve-dedup-list-contains.patch @@ -0,0 +1,35 @@ +# UNDF: +# Defect: netty-0002 +# Component: io.netty.resolver.dns.DnsResolveContext +# Pattern: CWE-407 — ArrayList.contains() for DNS result deduplication +# Severity: MEDIUM +# Complexity: O(R²) per DNS resolution → O(R) with companion HashSet +# Description: DnsResolveContext accumulates resolved addresses into +# finalResult (an ArrayList) and calls finalResult.contains(converted) +# to deduplicate. With R results, this is O(R²). The code comment +# acknowledges HashSet would be ideal but chooses ArrayList to avoid +# "extra memory copy and allocations" — but the O(R²) scan cost +# dominates for responses with many records (e.g., CDN domains with +# 50+ A records). Fix: maintain a companion HashSet for O(1) dedup +# while keeping the ArrayList for ordered result access. +--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsResolveContext.java ++++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsResolveContext.java +@@ -905,10 +905,11 @@ + if (!promise.isDone()) { +- // We want to ensure we do not have duplicates in finalResult as this may be unexpected. +- // +- // While using a LinkedHashSet or HashSet may sound like the perfect fit for this we will use an +- // ArrayList here as duplicates should be found quite unfrequently in the wild and we dont want to pay +- // for the extra memory copy and allocations in this cases later on. ++ // Deduplicate with a companion HashSet for O(1) membership test. ++ // The ArrayList is retained for ordered access. + if (finalResult == null) { + finalResult = new ArrayList(8); ++ finalResultSet = new HashSet(8); + finalResult.add(converted); +- } else if (isDuplicateAllowed() || !finalResult.contains(converted)) { ++ finalResultSet.add(converted); ++ } else if (isDuplicateAllowed() || !finalResultSet.contains(converted)) { + finalResult.add(converted); ++ finalResultSet.add(converted); + } else { diff --git a/defects/netty-0002/test/Netty0002Test.java b/defects/netty-0002/test/Netty0002Test.java new file mode 100644 index 000000000..e84edc50f --- /dev/null +++ b/defects/netty-0002/test/Netty0002Test.java @@ -0,0 +1,90 @@ +import java.util.*; + +/** + * Unit test for netty-0002: DnsResolveContext + * ArrayList.contains() dedup O(R²) → companion HashSet O(R). + */ +public class Netty0002Test { + + // --- BEFORE: O(R²) ArrayList dedup --- + static List resolveBefore(List records, boolean duplicateAllowed) { + List finalResult = null; + for (String record : records) { + if (finalResult == null) { + finalResult = new ArrayList<>(8); + finalResult.add(record); + } else if (duplicateAllowed || !finalResult.contains(record)) { // O(R) + finalResult.add(record); + } + } + return finalResult != null ? finalResult : Collections.emptyList(); + } + + // --- AFTER: O(R) companion HashSet dedup --- + static List resolveAfter(List records, boolean duplicateAllowed) { + List finalResult = null; + Set finalResultSet = null; + for (String record : records) { + if (finalResult == null) { + finalResult = new ArrayList<>(8); + finalResultSet = new HashSet<>(8); + finalResult.add(record); + finalResultSet.add(record); + } else if (duplicateAllowed || !finalResultSet.contains(record)) { // O(1) + finalResult.add(record); + finalResultSet.add(record); + } + } + return finalResult != null ? finalResult : Collections.emptyList(); + } + + public static void main(String[] args) { + // Correctness with duplicates + List records = Arrays.asList("1.2.3.4", "5.6.7.8", "1.2.3.4", "9.10.11.12", "5.6.7.8"); + + List beforeResult = resolveBefore(records, false); + List afterResult = resolveAfter(records, false); + assert beforeResult.equals(afterResult) : "Dedup results must match: " + beforeResult + " vs " + afterResult; + assert beforeResult.size() == 3 : "Expected 3 unique, got " + beforeResult.size(); + System.out.println("PASS correctness dedup: " + beforeResult); + + // Correctness with duplicates allowed + List beforeDup = resolveBefore(records, true); + List afterDup = resolveAfter(records, true); + assert beforeDup.equals(afterDup) : "Dup-allowed results must match"; + assert beforeDup.size() == 5 : "Expected 5 with dups, got " + beforeDup.size(); + System.out.println("PASS correctness dup-allowed: " + beforeDup); + + // Performance: R=1000 records, 50% duplicates + int R = 1000; + List bigRecords = new ArrayList<>(); + for (int i = 0; i < R; i++) { + bigRecords.add("10.0." + (i % (R / 2)) / 256 + "." + (i % (R / 2)) % 256); + } + + // Warmup + for (int w = 0; w < 500; w++) { + resolveBefore(bigRecords, false); + resolveAfter(bigRecords, false); + } + + int iterations = 3000; + long t0 = System.nanoTime(); + for (int i = 0; i < iterations; i++) { + resolveBefore(bigRecords, false); + } + long beforeNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < iterations; i++) { + resolveAfter(bigRecords, false); + } + long afterNs = System.nanoTime() - t0; + + double ratio = (double) beforeNs / afterNs; + System.out.printf("PASS performance: before=%dms after=%dms ratio=%.1fx (R=%d)%n", + beforeNs / 1_000_000, afterNs / 1_000_000, ratio, R); + assert ratio > 2.0 : "Expected at least 2x speedup, got " + ratio; + System.out.println("PASS all tests for netty-0002"); + } +} diff --git a/defects/tomcat-0001/patch/tomcat-0001-websocket-subprotocol-list-contains.patch b/defects/tomcat-0001/patch/tomcat-0001-websocket-subprotocol-list-contains.patch new file mode 100644 index 000000000..cdfa41c40 --- /dev/null +++ b/defects/tomcat-0001/patch/tomcat-0001-websocket-subprotocol-list-contains.patch @@ -0,0 +1,31 @@ +# UNDF: +# Defect: tomcat-0001 +# Component: org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator +# Pattern: CWE-407 — List.contains() in getNegotiatedSubprotocol loop +# Severity: MEDIUM +# Complexity: O(R×S) per WebSocket upgrade → O(R+S) with HashSet +# Description: getNegotiatedSubprotocol iterates over 'requested' protocols +# and calls supported.contains(request) on a List, making each +# contains() O(S). With R requested and S supported protocols, this is +# O(R×S). Fix: convert 'supported' to a HashSet for O(1) lookup. +--- a/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java ++++ b/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java +@@ -17,6 +17,7 @@ + package org.apache.tomcat.websocket.server; + + import java.util.ArrayList; ++import java.util.LinkedHashSet; + import java.util.HashSet; + import java.util.List; + import java.util.Set; +@@ -46,7 +47,8 @@ + @Override + public String getNegotiatedSubprotocol(List supported, List requested) { + ++ Set supportedSet = new HashSet<>(supported); + for (String request : requested) { +- if (supported.contains(request)) { ++ if (supportedSet.contains(request)) { + return request; + } + } diff --git a/defects/tomcat-0001/test/Tomcat0001Test.java b/defects/tomcat-0001/test/Tomcat0001Test.java new file mode 100644 index 000000000..b5cefec53 --- /dev/null +++ b/defects/tomcat-0001/test/Tomcat0001Test.java @@ -0,0 +1,79 @@ +import java.util.*; + +/** + * Unit test for tomcat-0001: DefaultServerEndpointConfigurator.getNegotiatedSubprotocol + * List.contains() O(R×S) → HashSet O(R+S) for WebSocket subprotocol negotiation. + */ +public class Tomcat0001Test { + + // --- BEFORE: O(R×S) linear scan --- + static String negotiateSubprotocolBefore(List supported, List requested) { + for (String request : requested) { + if (supported.contains(request)) { // O(S) per iteration + return request; + } + } + return ""; + } + + // --- AFTER: O(R+S) with HashSet --- + static String negotiateSubprotocolAfter(List supported, List requested) { + Set supportedSet = new HashSet<>(supported); + for (String request : requested) { + if (supportedSet.contains(request)) { // O(1) per iteration + return request; + } + } + return ""; + } + + public static void main(String[] args) { + // Correctness tests + List supported = Arrays.asList("chat", "superchat", "megachat"); + List requested = Arrays.asList("video", "megachat", "chat"); + + String beforeResult = negotiateSubprotocolBefore(supported, requested); + String afterResult = negotiateSubprotocolAfter(supported, requested); + assert beforeResult.equals(afterResult) : "Results must match"; + assert "megachat".equals(beforeResult) : "Should find megachat"; + System.out.println("PASS correctness: both return '" + beforeResult + "'"); + + // No match + List noMatch = Arrays.asList("video", "audio"); + assert "".equals(negotiateSubprotocolBefore(supported, noMatch)); + assert "".equals(negotiateSubprotocolAfter(supported, noMatch)); + System.out.println("PASS no-match: both return empty"); + + // Performance test: S=500 supported, R=500 requested, worst-case no match + int S = 500, R = 500; + List bigSupported = new ArrayList<>(); + for (int i = 0; i < S; i++) bigSupported.add("proto-s-" + i); + List bigRequested = new ArrayList<>(); + for (int i = 0; i < R; i++) bigRequested.add("proto-r-" + i); // no overlap + + // Warmup + for (int i = 0; i < 1000; i++) { + negotiateSubprotocolBefore(bigSupported, bigRequested); + negotiateSubprotocolAfter(bigSupported, bigRequested); + } + + int iterations = 5000; + long t0 = System.nanoTime(); + for (int i = 0; i < iterations; i++) { + negotiateSubprotocolBefore(bigSupported, bigRequested); + } + long beforeNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < iterations; i++) { + negotiateSubprotocolAfter(bigSupported, bigRequested); + } + long afterNs = System.nanoTime() - t0; + + double ratio = (double) beforeNs / afterNs; + System.out.printf("PASS performance: before=%dms after=%dms ratio=%.1fx (S=%d R=%d)%n", + beforeNs / 1_000_000, afterNs / 1_000_000, ratio, S, R); + assert ratio > 2.0 : "Expected at least 2x speedup, got " + ratio; + System.out.println("PASS all tests for tomcat-0001"); + } +}