From 2b12adc0441f827ec2a8aee6d9f4ad3d0f2d6fa2 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 30 Mar 2026 08:45:32 -0400 Subject: [PATCH] =?UTF-8?q?grpc-java:=203=20CWE-407=20defects=20=E2=80=94?= =?UTF-8?q?=20priority-lb=20list.contains=20O(C=C3=97P),=20xds-client=20au?= =?UTF-8?q?thorities=20list=20O(A=C3=97S=C3=97T),=20okhttp=20intersect=20O?= =?UTF-8?q?(N=C3=97M)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...rity-lb-priority-names-list-contains.patch | 31 +++ ...get-active-authorities-list-contains.patch | 19 ++ ...03-okhttp-util-intersect-nested-loop.patch | 37 +++ defects/grpc-java/unit/GrpcJavaTest.class | Bin 0 -> 5890 bytes defects/grpc-java/unit/GrpcJavaTest.java | 226 ++++++++++++++++++ 5 files changed, 313 insertions(+) create mode 100644 defects/grpc-java/patch/grpc-java-0001-priority-lb-priority-names-list-contains.patch create mode 100644 defects/grpc-java/patch/grpc-java-0002-xds-client-get-active-authorities-list-contains.patch create mode 100644 defects/grpc-java/patch/grpc-java-0003-okhttp-util-intersect-nested-loop.patch create mode 100644 defects/grpc-java/unit/GrpcJavaTest.class create mode 100644 defects/grpc-java/unit/GrpcJavaTest.java 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 0000000000000000000000000000000000000000..2d20a062deb219f7d3525f608e26813d042899f8 GIT binary patch literal 5890 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 literal 0 HcmV?d00001 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"); + } +}