diff --git a/defects/grpc-java/patch/grpc-java-0001-priority-lb-priority-names-list-contains.patch b/defects/grpc-java/patch/grpc-java-0001-priority-lb-priority-names-list-contains.patch new file mode 100644 index 000000000..87b4a2e3b --- /dev/null +++ b/defects/grpc-java/patch/grpc-java-0001-priority-lb-priority-names-list-contains.patch @@ -0,0 +1,31 @@ +# UNDF: UNDF-2026-000000742 +# UNDF: (leave blank) +--- a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java ++++ b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java +@@ -65,7 +65,7 @@ final class PriorityLoadBalancer extends LoadBalancer { + private ResolvedAddresses resolvedAddresses; + // List of priority names in order. + private List priorityNames; ++ private Set priorityNamesSet = new HashSet<>(); + // Config for each priority. + private Map priorityConfigs; + +@@ -89,6 +89,7 @@ final class PriorityLoadBalancer extends LoadBalancer { + checkNotNull(config, "missing priority lb config"); + priorityNames = config.priorities; ++ priorityNamesSet = new HashSet<>(config.priorities); + priorityConfigs = config.childConfigs; + Status status = Status.OK; +- Set prioritySet = new HashSet<>(config.priorities); ++ Set prioritySet = priorityNamesSet; + ArrayList childKeys = new ArrayList<>(children.keySet()); + for (String priority : childKeys) { + if (!prioritySet.contains(priority)) { +@@ -122,7 +126,7 @@ final class PriorityLoadBalancer extends LoadBalancer { + Collection childValues = new ArrayList<>(children.values()); + for (ChildLbState child : childValues) { +- if (priorityNames.contains(child.priority)) { ++ if (priorityNamesSet.contains(child.priority)) { + child.lb.handleNameResolutionError(error); + gotoTransientFailure = false; + } diff --git a/defects/grpc-java/patch/grpc-java-0002-xds-client-get-active-authorities-list-contains.patch b/defects/grpc-java/patch/grpc-java-0002-xds-client-get-active-authorities-list-contains.patch new file mode 100644 index 000000000..bb2ad15bc --- /dev/null +++ b/defects/grpc-java/patch/grpc-java-0002-xds-client-get-active-authorities-list-contains.patch @@ -0,0 +1,19 @@ +# UNDF: UNDF-2026-000000743 +# UNDF: (leave blank) +--- a/xds/src/main/java/io/grpc/xds/client/XdsClientImpl.java ++++ b/xds/src/main/java/io/grpc/xds/client/XdsClientImpl.java +@@ -1091,9 +1091,7 @@ public final class XdsClientImpl extends XdsClient { + private Collection getActiveAuthorities(ControlPlaneClient cpc) { +- List asList = activatedCpClients.entrySet().stream() ++ // Always return a HashSet for O(1) contains(); linear scan over asList was O(A×S×T) ++ // inside double-loops in cleanUpResourceTimers and onControlPlaneClientError. ++ return activatedCpClients.entrySet().stream() + .filter(entry -> !entry.getValue().isEmpty() + && cpc == entry.getValue().get(entry.getValue().size() - 1)) + .map(Map.Entry::getKey) +- .collect(Collectors.toList()); +- +- // Since this is usually used for contains, use a set when the list is large +- return (asList.size() < 100) ? asList : new HashSet<>(asList); ++ .collect(Collectors.toCollection(HashSet::new)); + } diff --git a/defects/grpc-java/patch/grpc-java-0003-okhttp-util-intersect-nested-loop.patch b/defects/grpc-java/patch/grpc-java-0003-okhttp-util-intersect-nested-loop.patch new file mode 100644 index 000000000..ca539f478 --- /dev/null +++ b/defects/grpc-java/patch/grpc-java-0003-okhttp-util-intersect-nested-loop.patch @@ -0,0 +1,37 @@ +# UNDF: UNDF-2026-000000744 +# UNDF: (leave blank) +--- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java ++++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java +@@ -20,6 +20,7 @@ package io.grpc.okhttp.internal; + import java.lang.reflect.Array; + import java.nio.charset.Charset; + import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.LinkedHashSet; + import java.util.Arrays; + import java.util.Collections; + import java.util.List; +@@ -59,13 +60,16 @@ public final class Util { + /** + * Returns a list containing containing only elements found in {@code first} and also in + * {@code second}. The returned elements are in the same order as in {@code first}. ++ * Previously O(|first|×|second|) nested loop; now O(|first|+|second|) via HashSet. + */ + private static List intersect(T[] first, T[] second) { + List result = new ArrayList<>(); +- for (T a : first) { +- for (T b : second) { +- if (a.equals(b)) { +- result.add(b); +- break; +- } +- } ++ // Build a hash-set of second for O(1) membership test. ++ LinkedHashSet secondSet = new LinkedHashSet<>(Arrays.asList(second)); ++ for (T a : first) { ++ if (secondSet.contains(a)) { ++ result.add(a); ++ } + } + return result; + } diff --git a/defects/grpc-java/unit/GrpcJavaTest.class b/defects/grpc-java/unit/GrpcJavaTest.class new file mode 100644 index 000000000..2d20a062d Binary files /dev/null and b/defects/grpc-java/unit/GrpcJavaTest.class differ diff --git a/defects/grpc-java/unit/GrpcJavaTest.java b/defects/grpc-java/unit/GrpcJavaTest.java new file mode 100644 index 000000000..1567f76da --- /dev/null +++ b/defects/grpc-java/unit/GrpcJavaTest.java @@ -0,0 +1,226 @@ +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +/** + * Unit tests for grpc-java CWE-407 defects. + * + * Defects: + * grpc-java-0001: PriorityLoadBalancer.handleNameResolutionError — priorityNames List.contains() + * O(C×P) inside child loop. Fix: maintain a parallel HashSet. + * grpc-java-0002: XdsClientImpl.getActiveAuthorities — returns List when size < 100, + * used in .contains() inside double-loop over subscribers. Fix: always HashSet. + * grpc-java-0003: Util.intersect (OkHttp) — O(|first|×|second|) nested loop for cipher suite + * intersection per TLS handshake. Fix: build HashSet of second, scan first once. + * + * Each test simulates the defect using the same data-structure logic, + * measures op-count ratio (defect vs fix), and asserts ratio > threshold. + */ +public class GrpcJavaTest { + + // ----------------------------------------------------------------------- + // grpc-java-0001: priorityNames List.contains() in handleNameResolutionError + // ----------------------------------------------------------------------- + + /** Defect: O(C × P) — List.contains per child */ + static long priorityLbHandleErrorDefect(List priorityNames, List children) { + long ops = 0; + for (String child : children) { + // List.contains is O(P) + for (String p : priorityNames) { + ops++; + if (p.equals(child)) break; + } + } + return ops; + } + + /** Fix: O(C + P) — HashSet.contains per child */ + static long priorityLbHandleErrorFixed(List priorityNames, List children) { + long ops = 0; + Set prioritySet = new HashSet<>(priorityNames); // O(P) + ops += priorityNames.size(); + for (String child : children) { + ops++; // O(1) hash lookup + } + return ops; + } + + static void testGrpcJava0001() { + int P = 200; // priorities (e.g., localities in a cluster) + int C = 200; // children (same order of magnitude) + + List priorityNames = new ArrayList<>(); + for (int i = 0; i < P; i++) priorityNames.add("priority-" + i); + + // children match at end (worst case for list scan) + List children = new ArrayList<>(priorityNames); + Collections.shuffle(children); + + long defectOps = priorityLbHandleErrorDefect(priorityNames, children); + long fixedOps = priorityLbHandleErrorFixed(priorityNames, children); + + double ratio = (double) defectOps / fixedOps; + System.out.printf("grpc-java-0001: P=%d C=%d defect_ops=%d fixed_ops=%d ratio=%.1fx%n", + P, C, defectOps, fixedOps, ratio); + + if (ratio < 10.0) { + throw new AssertionError("grpc-java-0001: expected ratio >= 10x, got " + ratio); + } + System.out.println("grpc-java-0001: PASS"); + } + + // ----------------------------------------------------------------------- + // grpc-java-0002: getActiveAuthorities returns List, used in .contains() + // inside double-loop over resourceTypes × subscribers + // ----------------------------------------------------------------------- + + /** Defect: authorities is a List — contains() is O(A) */ + static long xdsActiveAuthoritiesDefect( + int resourceTypes, int subscribersPerType, List authoritiesList) { + long ops = 0; + // Outer double-loop: resourceTypes × subscribersPerType + for (int t = 0; t < resourceTypes; t++) { + for (int s = 0; s < subscribersPerType; s++) { + String authority = "auth-" + (s % authoritiesList.size()); + // List.contains is O(A) + for (String a : authoritiesList) { + ops++; + if (a.equals(authority)) break; + } + } + } + return ops; + } + + /** Fix: authorities is always a HashSet — contains() is O(1) */ + static long xdsActiveAuthoritiesFixed( + int resourceTypes, int subscribersPerType, List authoritiesList) { + long ops = 0; + Set authoritiesSet = new HashSet<>(authoritiesList); // O(A) once + ops += authoritiesList.size(); + // Outer double-loop: same structure but O(1) contains + for (int t = 0; t < resourceTypes; t++) { + for (int s = 0; s < subscribersPerType; s++) { + String authority = "auth-" + (s % authoritiesList.size()); + ops++; // O(1) hash lookup + } + } + return ops; + } + + static void testGrpcJava0002() { + int A = 90; // authorities (< 100, in the code's "use list" branch) + int T = 5; // resource types (LDS/RDS/CDS/EDS/SRDS) + int S = 1000; // subscribers per type + + List authorities = new ArrayList<>(); + for (int i = 0; i < A; i++) authorities.add("auth-" + i); + + long defectOps = xdsActiveAuthoritiesDefect(T, S, authorities); + long fixedOps = xdsActiveAuthoritiesFixed(T, S, authorities); + + double ratio = (double) defectOps / fixedOps; + System.out.printf("grpc-java-0002: A=%d T=%d S=%d defect_ops=%d fixed_ops=%d ratio=%.1fx%n", + A, T, S, defectOps, fixedOps, ratio); + + if (ratio < 10.0) { + throw new AssertionError("grpc-java-0002: expected ratio >= 10x, got " + ratio); + } + System.out.println("grpc-java-0002: PASS"); + } + + // ----------------------------------------------------------------------- + // grpc-java-0003: Util.intersect O(|first|×|second|) per TLS handshake + // ----------------------------------------------------------------------- + + /** Defect: nested loop intersection — O(N×M) */ + static List intersectDefect(T[] first, T[] second) { + List result = new ArrayList<>(); + for (T a : first) { + for (T b : second) { + if (a.equals(b)) { + result.add(b); + break; + } + } + } + return result; + } + + static long countIntersectDefectOps(T[] first, T[] second) { + long ops = 0; + for (T a : first) { + for (T b : second) { + ops++; + if (a.equals(b)) break; + } + } + return ops; + } + + /** Fix: build HashSet of second, single scan of first — O(N+M) */ + static List intersectFixed(T[] first, T[] second) { + List result = new ArrayList<>(); + Set secondSet = new LinkedHashSet<>(Arrays.asList(second)); + for (T a : first) { + if (secondSet.contains(a)) { + result.add(a); + } + } + return result; + } + + static long countIntersectFixedOps(T[] first, T[] second) { + long ops = second.length; // build set + ops += first.length; // scan first (O(1) per lookup) + return ops; + } + + static void testGrpcJava0003() { + // Realistic TLS cipher suite sizes: + // 'first' = configured grpc cipher list (~25 entries) + // 'second' = socket.getEnabledCipherSuites (~50 entries) + int CONFIGURED = 25; + int ENABLED = 50; + int HANDSHAKES = 10000; // number of TLS handshakes + + String[] configured = new String[CONFIGURED]; + String[] enabled = new String[ENABLED]; + + for (int i = 0; i < CONFIGURED; i++) configured[i] = "TLS_CIPHER_" + i; + for (int i = 0; i < ENABLED; i++) enabled[i] = "TLS_CIPHER_" + (i * 2); // overlap ~12 + + // Verify both produce same result + List r1 = intersectDefect(configured, enabled); + List r2 = intersectFixed(configured, enabled); + if (!r1.equals(r2)) { + throw new AssertionError("grpc-java-0003: results differ: " + r1 + " vs " + r2); + } + + long defectOps = countIntersectDefectOps(configured, enabled) * HANDSHAKES; + long fixedOps = countIntersectFixedOps(configured, enabled) * HANDSHAKES; + + double ratio = (double) defectOps / fixedOps; + System.out.printf( + "grpc-java-0003: configured=%d enabled=%d handshakes=%d " + + "defect_ops=%d fixed_ops=%d ratio=%.1fx%n", + CONFIGURED, ENABLED, HANDSHAKES, defectOps, fixedOps, ratio); + + if (ratio < 5.0) { + throw new AssertionError("grpc-java-0003: expected ratio >= 5x, got " + ratio); + } + System.out.println("grpc-java-0003: PASS"); + } + + // ----------------------------------------------------------------------- + // Main + // ----------------------------------------------------------------------- + + public static void main(String[] args) { + testGrpcJava0001(); + testGrpcJava0002(); + testGrpcJava0003(); + System.out.println("ALL PASS"); + } +}