From 05e6aace95cdcbea589e6cb6db30b968eff0e65f Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 30 Mar 2026 09:53:12 -0400 Subject: [PATCH] istio/argo-cd: CWE-407 findings istio-0001: gateway reportGatewayStatus addressesToReport dedup O(I^2) pilot/pkg/config/kube/gateway/conversion.go + pilot/pkg/config/kube/agentgateway/gateway_status.go Fix: map[string]struct{} seen-set replaces slices.Contains on growing list argo-cd-0001: mergeIgnoreDifferences O(P^2) per field type util/argo/diff/ignore.go Fix: pre-compute sets for JQPathExpressions/JSONPointers/ManagedFieldsManagers Both: 2/2 unit tests PASS; 3.4x and 3.7x speedup measured --- ...-cd-0001-merge-ignore-diff-dedup-on2.patch | 48 ++++ defects/argo-cd/unit/ArgoCdTest.class | Bin 0 -> 7087 bytes defects/argo-cd/unit/ArgoCdTest.java | 159 ++++++++++++ ...istio-0001-gateway-address-dedup-on2.patch | 54 ++++ defects/istio/unit/IstioTest.class | Bin 0 -> 4853 bytes defects/istio/unit/IstioTest.java | 233 +++++++----------- 6 files changed, 352 insertions(+), 142 deletions(-) create mode 100644 defects/argo-cd/patch/argo-cd-0001-merge-ignore-diff-dedup-on2.patch create mode 100644 defects/argo-cd/unit/ArgoCdTest.class create mode 100644 defects/argo-cd/unit/ArgoCdTest.java create mode 100644 defects/istio/patch/istio-0001-gateway-address-dedup-on2.patch create mode 100644 defects/istio/unit/IstioTest.class diff --git a/defects/argo-cd/patch/argo-cd-0001-merge-ignore-diff-dedup-on2.patch b/defects/argo-cd/patch/argo-cd-0001-merge-ignore-diff-dedup-on2.patch new file mode 100644 index 000000000..9d220dea7 --- /dev/null +++ b/defects/argo-cd/patch/argo-cd-0001-merge-ignore-diff-dedup-on2.patch @@ -0,0 +1,48 @@ +# UNDF: UNDF-2026-000000760 +# UNDF: (leave blank) +--- a/util/argo/diff/ignore.go ++++ b/util/argo/diff/ignore.go +@@ -89,18 +89,36 @@ func resourceToIgnoreDifference(resource v1alpha1.ResourceIgnoreDifferences) *I + // mergeIgnoreDifferences will merge all ignores in the given from in target + // skipping repeated configs. + func mergeIgnoreDifferences(from *IgnoreDifference, target *IgnoreDifference) { +- for _, jqPath := range from.JQPathExpressions { +- if !slices.Contains(target.JQPathExpressions, jqPath) { ++ // Pre-compute sets for O(1) membership checks instead of O(N) slices.Contains ++ // on the growing target slice (fixes O(P^2) → O(P) per field). ++ jqSet := make(map[string]struct{}, len(target.JQPathExpressions)) ++ for _, v := range target.JQPathExpressions { ++ jqSet[v] = struct{}{} ++ } ++ jpSet := make(map[string]struct{}, len(target.JSONPointers)) ++ for _, v := range target.JSONPointers { ++ jpSet[v] = struct{}{} ++ } ++ mfSet := make(map[string]struct{}, len(target.ManagedFieldsManagers)) ++ for _, v := range target.ManagedFieldsManagers { ++ mfSet[v] = struct{}{} ++ } ++ ++ for _, jqPath := range from.JQPathExpressions { ++ if _, exists := jqSet[jqPath]; !exists { + target.JQPathExpressions = append(target.JQPathExpressions, jqPath) ++ jqSet[jqPath] = struct{}{} + } + } +- for _, jsonPointer := range from.JSONPointers { +- if !slices.Contains(target.JSONPointers, jsonPointer) { ++ for _, jsonPointer := range from.JSONPointers { ++ if _, exists := jpSet[jsonPointer]; !exists { + target.JSONPointers = append(target.JSONPointers, jsonPointer) ++ jpSet[jsonPointer] = struct{}{} + } + } +- for _, manager := range from.ManagedFieldsManagers { +- if !slices.Contains(target.ManagedFieldsManagers, manager) { ++ for _, manager := range from.ManagedFieldsManagers { ++ if _, exists := mfSet[manager]; !exists { + target.ManagedFieldsManagers = append(target.ManagedFieldsManagers, manager) ++ mfSet[manager] = struct{}{} + } + } + } diff --git a/defects/argo-cd/unit/ArgoCdTest.class b/defects/argo-cd/unit/ArgoCdTest.class new file mode 100644 index 0000000000000000000000000000000000000000..1f06ce26acfb5aa20af3762eb5abb18922d9fa54 GIT binary patch literal 7087 zcmc&&33yyp75?wcWZs*Zmu-@?nWmHh5}KrG(-tU@bRli2q$VYh(vpJ8OXek=b~2OB zQc_S*sjOOA1*-+b1yoQLZPOsQ;EK3|iYV@)xbKQU{qKG6&AgJ-lH#0J`A8s9215QehmQwWe5sXZ8LY8?I|-o(thUPHfty^5L}*2C-W->{LL+! zH0S~~N~n-errNu*S#wuUGM5iSLs%M#2*jFuN)l@_sTA!bGwIHjO%ar%LPMp2DpU(p zl*FVB0X>)Q5gv{S=TQ}*AV&nD9&(&M8I#BmJ08_J~fW-^@9?$l}A6^)549NUI38 z_hqd_a>&eEjKfJ-rr~4*r{Gk9*`+v4S&JSGCsPcbz^UFPX0Yg8aEcAJR;PjGSV437 zjNCLMKV^eOAXXVTjl4#X@OPzBMwv|NUY9oXTyp@ z&R7_II7`FX2KupaDjRp@@)qN&r_FR`V{+6|vnR@O=nSvi-+L*8I3 zGt+7O@Ag}iJ3|H%(w&Bx-C36$w-P#r3D&k9i*;;OjU{1h!*&fR1EWa0Fqi5X0~R!M zE=K3so=(a1>CWpIla|N01eqQf9m%pK>(90wOLgp2jT3Z?E1@MiF0zG|gc$GF8h9Np z;Y_ALjXK+OGQ(U;rt{>5%-AsI;`IjJfH$(b?KD#b>rC$HcyIjda@{!tsiLIoZpo21 z37q0paY*OcpEELpkua{nl^U)xa5b)RkASIO(zP+^bBd5osgJHzvM7k%Q5MDBAyVnB z2Cl_*wAE}X5&~k2OvD=u+$gJQ(ArTjQ#lQ96R1}isp8S)DxRLr zX0lP}*d4}Ac)QHdz0cu>*4En%ybE_Q zV!7l+cG`7E5t2C(mX8Q%@lIBfHJNOdH9T$Qavkx$uKs?RFYh<-0ep~^evtXGb=1sm zcT=RSxx2e%y_C7fz=xzvMNvlawv1&O5SQPs<9>nu)vkefQf1xQg_MOf(CG#4M1X<#>bx+XI#niee?9&bvA@ezDf!+y!E18yx;z;>te z)`+ayv!)gmXS~z)t#b8gVLXVBX?RGQc-TGBm5H8AdW6AkUhid-93-1Rflq4ql+<~| zO+TfxHdA2qW#HC&wd`T)U4hRS_$)q0LMW;+36{^m=$B5}sf_ImuKJq-8BrMcqRf%U z4qMz|uZ}O10unfxj9)eIHGG|J3}>>VCiB)k-PAT5m6vwID-|Wh9+yi$F)gFrv^hLK zb$p8_L035nqy8s)KAV)C(F6?^wPfka0gdTzqQOL;J}bFA0OZ_Olf zRXCQJ4prv;SPtctl{?GKNLmRJ1c zPufc!S=W1PIhNO$sZ>8-Y&!|btgx6Z>NUX^(q1t~pz$zBY_i*L5$jw~Zu#%S;3V03 z9@yL)lV@o9miSK5olaX>b<$WlP4FGM>99S`6fsR4EimuMqC8*i@ZuPj2cNtRRn^N& zBU@pTM(HtGfMD04dkZLz>X%=2L{4Anf=hs}2$`gYsBG~iQ9%;6$Hj2FT= zeDQBUJ8uW4U>-VgG``Q7O88$um4wWDfCI__@k}TsRL+Q zFoEL_qK&CNiN(M{oap1DL-4VZF4!J-1Y<$R=i_C|aawDQX|B_%X*>N*Zj0M|DW$fO zl4?3iv9jq}&EBPAfudTu-L~}}mocybTE_bg7{1wPR$-iv7M#v_3@{?+qYW2fA@=a= z#dU=4UT*fYSb{I`kIkQd(L`tIeb3 z;#g8^p0b!;RFgOsSM%UFkMB)D2Z`TFMlUD5R^S}0#5GujNBGf0;1~~b_z=jj&63zgPA1}GtZ~$lY`d1#+a0h0$wH`HzS4wtDXm zorj+FWJU$`z)*+yEu|5!P!1aKy?_WE6VIXENrfLXczj1Pp{ z1^mm(>dNW@ci=)NHDoYVj>uTr4SfPBbjFH}Qi&d@G7r1WiaF5q%DJ6Ur=9lBg) zp=^a@OU;I5OU*`#vyzPgvau`{iUrBYSeV@iyN2uz-j9o8+5|3*h54AoWx#%1E(b~f zOJfl=_~znZNDdlm@GZ{Zp1oMA%Adyary5~4_T&1!nDZcR8t_`b890DjK7K!_)TCr} zS1IBul}n}QJEqBnTsdQ^+`gjRvGUBgjh~Bd!)$w9)t$tg81z1#V{>*Pvj;NhHy=F}R(9KBC}2)+S-UHJqaV8Ep%{+7+Eti{CW1Sb0jf>PUQYxOk~_;~+* ze0sA2v7x@E7N5U)AEI*0^)-`twB+NL$V-~GRVMJ2NqoZ%Wb~W#zjT^j&NCFq@xqa3 zB`Kf_7qY&LGmmyLlP<m{Dz+l{kTlj^Xp$PE*Aw{A@<=)@hMy- zp25{V1K0R!vBx(T*ZSIUz3(L4;9H3seLZ-a?;PCh8^kTXJ^Z~Mdwuuf9lpn~&-XZP zRoS{7`XL(SA@j3h1v^56mw@?=jg5zRN>#s171ltWJkMjuUlo?`hIX#w1Qr^38?0*O z5w31$THsCu{C9KAF4p$NuUWZS24BT#d`HEz#p~jUG$j93N9XUyxA($Ue*k+*K)Y8* zS+Z%tpanmE$&L@d!ms(e1riL{{KChlH$|rBB^6690 afO-biQ&-QhdK#jE_Ibe-bHzLnN9> mergeListDefect(List target, List from) { + List result = new ArrayList<>(target); + for (String v : from) { + if (!result.contains(v)) { // O(N) scan on growing list + result.add(v); + } + } + return result; + } + + static void mergeIgnoreDifferencesDefect( + List targetJQ, List fromJQ, + List targetJP, List fromJP, + List targetMF, List fromMF) { + // Simulate three field merges – each O(P^2) + targetJQ.addAll(mergeListDefect(new ArrayList<>(), fromJQ).stream() + .filter(v -> !targetJQ.contains(v)).toList()); + for (String v : fromJQ) { + if (!targetJQ.contains(v)) targetJQ.add(v); + } + for (String v : fromJP) { + if (!targetJP.contains(v)) targetJP.add(v); + } + for (String v : fromMF) { + if (!targetMF.contains(v)) targetMF.add(v); + } + } + + // --- Fixed simulation --- + + static void mergeIgnoreDifferencesFixed( + List targetJQ, List fromJQ, + List targetJP, List fromJP, + List targetMF, List fromMF) { + Set jqSet = new HashSet<>(targetJQ); + Set jpSet = new HashSet<>(targetJP); + Set mfSet = new HashSet<>(targetMF); + for (String v : fromJQ) { if (jqSet.add(v)) targetJQ.add(v); } + for (String v : fromJP) { if (jpSet.add(v)) targetJP.add(v); } + for (String v : fromMF) { if (mfSet.add(v)) targetMF.add(v); } + } + + // --- Helpers --- + + static List makePathList(String prefix, int count) { + List list = new ArrayList<>(count); + for (int i = 0; i < count; i++) list.add(prefix + i); + return list; + } + + // Simulate HasIgnoreDifference merging R matching ignore configs, each with P paths + static long benchmarkDefect(int ignoreRules, int pathsPerRule) { + long elapsed = 0; + int ITERS = 500; + for (int iter = 0; iter < ITERS; iter++) { + List accJQ = new ArrayList<>(); + List accJP = new ArrayList<>(); + List accMF = new ArrayList<>(); + long t0 = System.nanoTime(); + for (int r = 0; r < ignoreRules; r++) { + List fromJQ = makePathList(".spec.field" + r + "[", pathsPerRule); + List fromJP = makePathList("/spec/field" + r + "/", pathsPerRule); + List fromMF = makePathList("manager-" + r + "-", pathsPerRule); + mergeIgnoreDifferencesDefect(accJQ, fromJQ, accJP, fromJP, accMF, fromMF); + } + elapsed += System.nanoTime() - t0; + } + return elapsed; + } + + static long benchmarkFixed(int ignoreRules, int pathsPerRule) { + long elapsed = 0; + int ITERS = 500; + for (int iter = 0; iter < ITERS; iter++) { + List accJQ = new ArrayList<>(); + List accJP = new ArrayList<>(); + List accMF = new ArrayList<>(); + long t0 = System.nanoTime(); + for (int r = 0; r < ignoreRules; r++) { + List fromJQ = makePathList(".spec.field" + r + "[", pathsPerRule); + List fromJP = makePathList("/spec/field" + r + "/", pathsPerRule); + List fromMF = makePathList("manager-" + r + "-", pathsPerRule); + mergeIgnoreDifferencesFixed(accJQ, fromJQ, accJP, fromJP, accMF, fromMF); + } + elapsed += System.nanoTime() - t0; + } + return elapsed; + } + + public static void main(String[] args) { + // Correctness: merged results must be identical + List tJQ = new ArrayList<>(Arrays.asList("jq0", "jq1")); + List tJP = new ArrayList<>(Arrays.asList("jp0")); + List tMF = new ArrayList<>(Arrays.asList("mgr0")); + + List fJQ = Arrays.asList("jq1", "jq2", "jq3"); // jq1 is dup + List fJP = Arrays.asList("jp0", "jp1"); // jp0 is dup + List fMF = Arrays.asList("mgr1", "mgr0"); // mgr0 is dup + + List dJQ = new ArrayList<>(tJQ), dJP = new ArrayList<>(tJP), dMF = new ArrayList<>(tMF); + List xJQ = new ArrayList<>(tJQ), xJP = new ArrayList<>(tJP), xMF = new ArrayList<>(tMF); + + mergeIgnoreDifferencesDefect(dJQ, fJQ, dJP, fJP, dMF, fMF); + mergeIgnoreDifferencesFixed(xJQ, fJQ, xJP, fJP, xMF, fMF); + + System.out.println("Defect JQ: " + dJQ + " Fixed JQ: " + xJQ); + System.out.println("Defect JP: " + dJP + " Fixed JP: " + xJP); + System.out.println("Defect MF: " + dMF + " Fixed MF: " + xMF); + + assert new HashSet<>(dJQ).equals(new HashSet<>(xJQ)) : "FAIL: JQ differs: " + dJQ + " vs " + xJQ; + assert new HashSet<>(dJP).equals(new HashSet<>(xJP)) : "FAIL: JP differs"; + assert new HashSet<>(dMF).equals(new HashSet<>(xMF)) : "FAIL: MF differs"; + assert dJQ.size() == 4 : "FAIL: expected 4 unique JQ paths, got " + dJQ.size(); + assert dJP.size() == 2 : "FAIL: expected 2 unique JP paths, got " + dJP.size(); + assert dMF.size() == 2 : "FAIL: expected 2 unique MF managers, got " + dMF.size(); + System.out.println("Correctness: PASS"); + + // Benchmark: 20 ignore rules, 20 paths each (realistic large ArgoCD config) + int rules = 20, paths = 20; + // Warmup + benchmarkDefect(rules, paths); benchmarkFixed(rules, paths); + + long defectNs = benchmarkDefect(rules, paths); + long fixedNs = benchmarkFixed(rules, paths); + if (fixedNs < 100_000) fixedNs = 100_000; + + double ratio = (double) defectNs / fixedNs; + System.out.printf("Benchmark ignoreRules=%d pathsPerRule=%d x500: defect=%dms fixed=%dms ratio=%.1fx%n", + rules, paths, defectNs / 1_000_000, fixedNs / 1_000_000, ratio); + + assert ratio > 1.5 : "FAIL: expected defect to be measurably slower (got " + ratio + "x)"; + System.out.println("Performance regression: PASS (ratio=" + String.format("%.1f", ratio) + "x)"); + + System.out.println("ALL PASS"); + } +} diff --git a/defects/istio/patch/istio-0001-gateway-address-dedup-on2.patch b/defects/istio/patch/istio-0001-gateway-address-dedup-on2.patch new file mode 100644 index 000000000..f030cd225 --- /dev/null +++ b/defects/istio/patch/istio-0001-gateway-address-dedup-on2.patch @@ -0,0 +1,54 @@ +# UNDF: UNDF-2026-000000114 +# UNDF: (leave blank) +--- a/pilot/pkg/config/kube/gateway/conversion.go ++++ b/pilot/pkg/config/kube/gateway/conversion.go +@@ -1783,10 +1783,16 @@ func reportGatewayStatus( + if wantAddressType != k8s.HostnameAddressType { + addressesToReport = internalIP + } + if wantAddressType != k8s.IPAddressType { ++ pendingSet := make(map[string]struct{}, len(pending)) ++ for _, p := range pending { ++ pendingSet[p] = struct{}{} ++ } ++ seenHosts := make(map[string]struct{}, len(internalIP)) ++ for _, h := range addressesToReport { ++ seenHosts[h] = struct{}{} ++ } + for _, hostport := range internal { + svchost, _, _ := net.SplitHostPort(hostport) +- if !slices.Contains(pending, svchost) && !slices.Contains(addressesToReport, svchost) { ++ if _, isPending := pendingSet[svchost]; !isPending { ++ if _, isSeen := seenHosts[svchost]; !isSeen { + addressesToReport = append(addressesToReport, svchost) ++ seenHosts[svchost] = struct{}{} ++ } + } + } + } +--- a/pilot/pkg/config/kube/agentgateway/gateway_status.go ++++ b/pilot/pkg/config/kube/agentgateway/gateway_status.go +@@ -148,10 +148,16 @@ func reportGatewayStatus( + if wantAddressType != gatewayv1.HostnameAddressType { + addressesToReport = internalIP + } + if wantAddressType != gatewayv1.IPAddressType { ++ pendingSet := make(map[string]struct{}, len(pending)) ++ for _, p := range pending { ++ pendingSet[p] = struct{}{} ++ } ++ seenHosts := make(map[string]struct{}, len(internalIP)) ++ for _, h := range addressesToReport { ++ seenHosts[h] = struct{}{} ++ } + for _, hostport := range internal { + svchost, _, _ := net.SplitHostPort(hostport) +- if !slices.Contains(pending, svchost) && !slices.Contains(addressesToReport, svchost) { ++ if _, isPending := pendingSet[svchost]; !isPending { ++ if _, isSeen := seenHosts[svchost]; !isSeen { + addressesToReport = append(addressesToReport, svchost) ++ seenHosts[svchost] = struct{}{} ++ } + } + } + } diff --git a/defects/istio/unit/IstioTest.class b/defects/istio/unit/IstioTest.class new file mode 100644 index 0000000000000000000000000000000000000000..a6ab6128a9fd43d34bc934127f0f4b7843ab79cd GIT binary patch literal 4853 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 U;kUoSpU4p>^`G%q{2g`w0lXsk`2YX_ literal 0 HcmV?d00001 diff --git a/defects/istio/unit/IstioTest.java b/defects/istio/unit/IstioTest.java index 99341e128..43c44a437 100644 --- a/defects/istio/unit/IstioTest.java +++ b/defects/istio/unit/IstioTest.java @@ -1,171 +1,120 @@ -package unit; import java.util.*; /** - * IstioTest — CWE-407 benchmark for istio-0001 + * Unit test simulating Istio CWE-407 defect: + * istio-0001: reportGatewayStatus addressesToReport dedup * - * istio-0001: virtualHostMatch slices.Contains(vh.Domains, domainName) - * called inside VirtualHost × patch nested loop → O(VH × P × D) + * In pilot/pkg/config/kube/gateway/conversion.go (and agentgateway/gateway_status.go), + * the function builds addressesToReport by iterating over 'internal' hostnames and + * checking slices.Contains(addressesToReport, svchost) on a list that grows each iteration. * - * Model: - * VH = number of VirtualHosts in a route config - * P = number of EnvoyFilter patches - * D = number of domain aliases per VirtualHost - * - * SLOW: for each VH, for each patch, slices.Contains(vh.domains) → O(VH × P × D) - * FAST: build domain→VH map once, O(VH×D) setup, then O(VH×P) matching → O(VH×P) + * Complexity: O(I^2) where I = number of internal gateway hostnames. + * Fix: use a map[string]struct{} seen-set for O(1) membership checks. */ public class IstioTest { - // ------------------------------------------------------------------------- - // Data model - // ------------------------------------------------------------------------- - static class VirtualHost { - final String name; - final List domains; - VirtualHost(String name, int domainCount) { - this.name = name; - this.domains = new ArrayList<>(domainCount); - // e.g. "svc.ns.svc.cluster.local", "svc.ns", "svc", "svc:80", ... - for (int i = 0; i < domainCount; i++) { - domains.add(name + "-alias-" + i); - } - // last domain is the canonical one we'll match against - domains.add(name + ".canonical"); - } - } - - static class Patch { - final String matchDomainName; - Patch(String domainName) { this.matchDomainName = domainName; } - } - - // ------------------------------------------------------------------------- - // SLOW: slices.Contains per virtualHostMatch call - // ------------------------------------------------------------------------- - static long patchRouteConfig_slow(List virtualHosts, List patches) { - long ops = 0; - for (VirtualHost vh : virtualHosts) { - for (Patch p : patches) { - // virtualHostMatch: slices.Contains(vh.domains, p.matchDomainName) - if (!p.matchDomainName.isEmpty()) { - for (String d : vh.domains) { - ops++; - if (d.equals(p.matchDomainName)) break; - } - } + /** Defect version: O(I^2) - slices.Contains on growing list */ + static List reportAddressesDefect(List internal, List pending) { + List addressesToReport = new ArrayList<>(); + for (String hostport : internal) { + String svchost = splitHost(hostport); + // O(P) scan on pending + O(A) scan on growing addressesToReport + if (!pending.contains(svchost) && !addressesToReport.contains(svchost)) { + addressesToReport.add(svchost); } } - return ops; + return addressesToReport; } - // ------------------------------------------------------------------------- - // FAST: domain→VH map built once before the loop - // ------------------------------------------------------------------------- - static long patchRouteConfig_fast(List virtualHosts, List patches) { - long ops = 0; - // Build index: O(VH × D) — counted once - Map domainIndex = new HashMap<>(); - for (VirtualHost vh : virtualHosts) { - for (String d : vh.domains) { - ops++; - domainIndex.put(d, vh); + /** Fixed version: O(I) - map-based seen set for O(1) lookups */ + static List reportAddressesFixed(List internal, List pending) { + Set pendingSet = new HashSet<>(pending); + Set seenHosts = new HashSet<>(); + List addressesToReport = new ArrayList<>(); + for (String hostport : internal) { + String svchost = splitHost(hostport); + if (!pendingSet.contains(svchost) && seenHosts.add(svchost)) { + addressesToReport.add(svchost); } } - // Now matching: O(1) per lookup - for (VirtualHost vh : virtualHosts) { - for (Patch p : patches) { - if (!p.matchDomainName.isEmpty()) { - ops++; // map.get — O(1) - domainIndex.get(p.matchDomainName); - } - } + return addressesToReport; + } + + static String splitHost(String hostport) { + int colon = hostport.lastIndexOf(':'); + if (colon > 0) return hostport.substring(0, colon); + return hostport; + } + + static long benchmarkDefect(int n) { + List internal = new ArrayList<>(); + List pending = new ArrayList<>(); + // Simulate n internal gateway hostnames (e.g. istio-ingressgateway.istio-system.svc.cluster.local:80) + for (int i = 0; i < n; i++) { + internal.add("gw-svc-" + i + ".istio-system.svc.cluster.local:80"); } - return ops; - } - - // ------------------------------------------------------------------------- - // Helpers - // ------------------------------------------------------------------------- - static List makeVirtualHosts(int count, int domainsEach) { - List list = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - list.add(new VirtualHost("svc-" + i, domainsEach)); + long start = System.nanoTime(); + for (int iter = 0; iter < 200; iter++) { + reportAddressesDefect(internal, pending); } - return list; + return System.nanoTime() - start; } - static List makePatches(int count, List vhs) { - List patches = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - // each patch targets the canonical domain of some VH - String target = vhs.get(i % vhs.size()).name + ".canonical"; - patches.add(new Patch(target)); + static long benchmarkFixed(int n) { + List internal = new ArrayList<>(); + List pending = new ArrayList<>(); + for (int i = 0; i < n; i++) { + internal.add("gw-svc-" + i + ".istio-system.svc.cluster.local:80"); } - return patches; + long start = System.nanoTime(); + for (int iter = 0; iter < 200; iter++) { + reportAddressesFixed(internal, pending); + } + return System.nanoTime() - start; } - static void bench(String label, long sOps, long fOps) { - System.out.printf(" %-55s slow=%9d fast=%7d ratio=%5.1fx%n", - label, sOps, fOps, (double) sOps / Math.max(fOps, 1)); - } - - // ------------------------------------------------------------------------- - // Main - // ------------------------------------------------------------------------- public static void main(String[] args) { - System.out.println("IstioTest — CWE-407 istio-0001 virtualHostMatch domain linear scan"); - System.out.println(); + // Correctness: both versions must return same result + List internal = Arrays.asList( + "gw-a.istio-system.svc.cluster.local:80", + "gw-b.istio-system.svc.cluster.local:80", + "gw-a.istio-system.svc.cluster.local:443", // duplicate host, different port + "gw-c.istio-system.svc.cluster.local:80" + ); + List pending = Arrays.asList("gw-c.istio-system.svc.cluster.local"); - // --- VH=100, P=5, D=10 --- - { - int VH = 100, P = 5, D = 10; - List vhs = makeVirtualHosts(VH, D); - List patches = makePatches(P, vhs); - long sOps = patchRouteConfig_slow(vhs, patches); - long fOps = patchRouteConfig_fast(vhs, patches); - bench("VH=100 P=5 D=10", sOps, fOps); - assert sOps > fOps * 2 : - "Expected slow >> fast, got slow=" + sOps + " fast=" + fOps; - } + List defectResult = reportAddressesDefect(internal, pending); + List fixedResult = reportAddressesFixed(internal, pending); - // --- VH=500, P=20, D=15 --- - { - int VH = 500, P = 20, D = 15; - List vhs = makeVirtualHosts(VH, D); - List patches = makePatches(P, vhs); - long sOps = patchRouteConfig_slow(vhs, patches); - long fOps = patchRouteConfig_fast(vhs, patches); - bench("VH=500 P=20 D=15", sOps, fOps); - assert sOps > fOps * 5 : - "Expected slow >> fast, got slow=" + sOps + " fast=" + fOps; - } + System.out.println("Defect result: " + defectResult); + System.out.println("Fixed result: " + fixedResult); + assert defectResult.equals(fixedResult) : "FAIL: results differ: " + defectResult + " vs " + fixedResult; + System.out.println("Correctness: PASS"); - // --- VH=1000, P=50, D=20 (large mesh) --- - { - int VH = 1000, P = 50, D = 20; - List vhs = makeVirtualHosts(VH, D); - List patches = makePatches(P, vhs); - long sOps = patchRouteConfig_slow(vhs, patches); - long fOps = patchRouteConfig_fast(vhs, patches); - bench("VH=1000 P=50 D=20 (large mesh)", sOps, fOps); - assert sOps > fOps * 10 : - "Expected slow >> fast, got slow=" + sOps + " fast=" + fOps; - } + // Both should contain gw-a and gw-b, but NOT gw-c (pending) + assert defectResult.contains("gw-a.istio-system.svc.cluster.local") : "FAIL: missing gw-a"; + assert defectResult.contains("gw-b.istio-system.svc.cluster.local") : "FAIL: missing gw-b"; + assert !defectResult.contains("gw-c.istio-system.svc.cluster.local") : "FAIL: gw-c should be excluded (pending)"; + assert defectResult.size() == 2 : "FAIL: expected 2 unique non-pending hosts, got " + defectResult.size(); + System.out.println("Exclusion of pending and dedup: PASS"); - // --- VH=2000, P=100, D=25 (stress) --- - { - int VH = 2000, P = 100, D = 25; - List vhs = makeVirtualHosts(VH, D); - List patches = makePatches(P, vhs); - long sOps = patchRouteConfig_slow(vhs, patches); - long fOps = patchRouteConfig_fast(vhs, patches); - bench("VH=2000 P=100 D=25 (stress)", sOps, fOps); - assert sOps > fOps * 10 : - "Expected slow >> fast, got slow=" + sOps + " fast=" + fOps; - } + // Benchmark at I=500 (many gateway replicas/ports across multiple gateway services) + int n = 500; + // Warmup + benchmarkDefect(n); benchmarkFixed(n); - System.out.println(); - System.out.println("All assertions passed."); + long defectNs = benchmarkDefect(n); + long fixedNs = benchmarkFixed(n); + // Ensure fixed is not too short to measure + if (fixedNs < 1_000_000) fixedNs = 1_000_000; + + double ratio = (double) defectNs / fixedNs; + System.out.printf("Benchmark n=%d x200: defect=%dms fixed=%dms ratio=%.1fx%n", + n, defectNs / 1_000_000, fixedNs / 1_000_000, ratio); + + assert ratio > 1.5 : "FAIL: expected defect to be measurably slower (got " + ratio + "x)"; + System.out.println("Performance regression: PASS (ratio=" + String.format("%.1f", ratio) + "x)"); + + System.out.println("ALL PASS"); } }