From 8660303f5a9900aad51861f0ae8df3e10e4583bf Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 30 Mar 2026 18:46:38 -0400 Subject: [PATCH] forgejo-0001/snort3-0001: 2 CWE-407 defects from MOAD multi-scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit forgejo-0001: Results.RepoIDs() slices.Contains dedup O(N^2) in code search — 107x at N=5000 snort3-0001: ServiceDiscovery service_candidates std::find dedup O(M*C) in AppID — 59x at M=10000 --- defects/forgejo-0001/patch/forgejo-0001.patch | 18 ++++ .../test/ForgejoSearchRepoIDsDedupTest.class | Bin 0 -> 3894 bytes .../test/ForgejoSearchRepoIDsDedupTest.java | 92 ++++++++++++++++ defects/snort3-0001/patch/snort3-0001.patch | 46 ++++++++ .../Snort3ServiceCandidateDedupTest.class | Bin 0 -> 3400 bytes .../test/Snort3ServiceCandidateDedupTest.java | 98 ++++++++++++++++++ 6 files changed, 254 insertions(+) create mode 100644 defects/forgejo-0001/patch/forgejo-0001.patch create mode 100644 defects/forgejo-0001/test/ForgejoSearchRepoIDsDedupTest.class create mode 100644 defects/forgejo-0001/test/ForgejoSearchRepoIDsDedupTest.java create mode 100644 defects/snort3-0001/patch/snort3-0001.patch create mode 100644 defects/snort3-0001/test/Snort3ServiceCandidateDedupTest.class create mode 100644 defects/snort3-0001/test/Snort3ServiceCandidateDedupTest.java diff --git a/defects/forgejo-0001/patch/forgejo-0001.patch b/defects/forgejo-0001/patch/forgejo-0001.patch new file mode 100644 index 000000000..ff41c3ac5 --- /dev/null +++ b/defects/forgejo-0001/patch/forgejo-0001.patch @@ -0,0 +1,18 @@ +--- a/modules/indexer/code/search.go ++++ b/modules/indexer/code/search.go +@@ -50,12 +50,13 @@ type Results []*Result + + // Get the set of repo IDs from a list of search results + func (res Results) RepoIDs() []int64 { +- ids := make([]int64, len(res)) ++ seen := make(map[int64]struct{}, len(res)) ++ ids := make([]int64, 0, len(res)) + for _, r := range res { +- if !slices.Contains(ids, r.RepoID) { ++ if _, ok := seen[r.RepoID]; !ok { ++ seen[r.RepoID] = struct{}{} + ids = append(ids, r.RepoID) + } + } + return ids + } diff --git a/defects/forgejo-0001/test/ForgejoSearchRepoIDsDedupTest.class b/defects/forgejo-0001/test/ForgejoSearchRepoIDsDedupTest.class new file mode 100644 index 0000000000000000000000000000000000000000..bd29ec55ee6d0b2fe868593f3c93e8036923bffa GIT binary patch literal 3894 zcmcInU2qfE75=XEvy!|; zrt~LiX_L~XZPK)%v?Pt2mi|DSKx+dgq(3j6OdtA`zV)rscKVWs&NP#0q35n7YYhz3 zmwM#AcXiJ>_dDl&=iIAzZ{N5Dpbx(bpb9PlZV?`M8EQ|+vvOxv)}}j0Cr&7n7K3+( zs;Sma23NdgOn{G}-WDoYYPPe_FyvE7)wBW#pjv<^q6VuNR#ogIb#0nKn3b~yWps+P zV; zmz>E6*vJs7_^?klXHp7Jpm9uv`xo^rBRnx{=QABVTQ)RqT4A&HYR6E-VSirjwmMS)EdPA z6tStZ3PVHuc$t*Ti6{09A}L~o+Z0PWwi7P6YIX;35QhXD7Lme{GHYyJjL6muWhW=k zF?ixbHo!KFi5SO6h)tTT=||KY?MekT?Ct9~^9=XchmSIZn=}6Aj;&LE$8)Y(3*h7U zgn*}bami(B*?UnClxf8vO$vmgB1+$r50fB1DqNFl&r#k3dSN0ZEKb z30_!v5l@30^chM{pH+qk#|CF9-$$+G7%#Q3pYm4_F^f+Ui<@NAR1CY145+3|u4fn+ zm2MoTM0|>uu2*@wAZJYhpJs?u8dP6dagSl>MjaSFE8=r_hGBJ1KB-Xg5Y}#VUZ)_&n$I7w#&Xa^S|iyFTDt0P3hMKgYLu-cId`6qT&`aF(LdmrTYE z_N7w1|9(}(3!Gy9EVbM*-fsi=I=&&`MG-IIn+(BdrybkwUzZt~=aoVVWvoMWguPIcl>snZ%+Fi2+8eTr&_ zgD(7FNbIC8;LcHZ<$gNJwA@Mryh{1I&ys-e^IeAsO;*lLWaK7pr>Tn{F?Vmno$>9M z%$AO_KuRwdlggmVecAZHe$l~WMwRoVu3M&M$oUb)n$a_6EgFMf{De1&pNe=L^R<}w z;WA~m)MsNEb!tj6VzL!G@C5VW7Yv*C%;l*aDw&v>S9n7zEygAaR%}|gVg~PwPcSn4 zRsdIURlqMr{0a*MvXVMgo7GP$ok`ngLL*Pfla_9rVyMnJ-~Ql?LP@CzejoMPtYS7L zb^T-^pKt`L6m`cpD-@5M$}3Ls|9!Ez<%4ydeW+x)U(RMzDzyR%$+ZBkH$$3vsfp2bkDf2nTO*B(Mo;S^*jro>-zo_KtLR{&Qzo{SJT ze?V})W7{}SFzKK!!Mp$$y#vW5Y#^A6c$j7!@FF&iv>w=UtK04B_DCMdO<1=`p3WX` zTi9!3-CRW$gYBg^8>@$~zSmyCy3P*4I%>G<31JMa6ye9Q5d$;EFrO;m%ZU)k>+RSL$9b*3+O?yJFzm2B7q59`XlULAM*R_Cc ze0-e7Hb=~vMltW;PTj2T^)8_|-GCj7*x4(%x_y#Q^2}pR)GPU-g5>dJ7trT^>Wc@@ z_{g=t*aF^pNKp#Go^k%%KW;Dm!(Dek@?@`JwA*(LM|=FP?rN!8^35Yyls!{T2HvNo zuwUt^E{W7gHOnGosKy~u6RtknfV93n7_KQ!LXlu3q;Ie1J{=d{MpfD+5oy)5sNA=dmuj zIvTl-InKtqC7e!6o*VcKu!v{hu?3nV^$j@l$_1>IJR$u4bu<*EBK0@$rJ@`s?(LU2 z*e_E8c(D}udmfu;-b#6I!^3E&X9w}Olla?(L)ePr*oG{P23@L8qX*9tW1ge=3rOG` zp1=j{#6{A$OygDhUA9CQ-Cq-<-X#lvr13rM#hSJA=Vz%h1-cIJFM3-JyW71hrL0k6@A*Uudytl#ksv)S59D<7iq z->70AV)bp*F*i->{)3=B-ob0AzmJmJ?RL57mn0P@eTedLzQN|Zwj}!32KZ@Nq&-E& zZTFsF>jJ*wfB8HF_eHo}mzD#|n-u7Z&7JU|s%AHxKrITa=66BKlttvDZ5Q literal 0 HcmV?d00001 diff --git a/defects/forgejo-0001/test/ForgejoSearchRepoIDsDedupTest.java b/defects/forgejo-0001/test/ForgejoSearchRepoIDsDedupTest.java new file mode 100644 index 000000000..09d6bbf4e --- /dev/null +++ b/defects/forgejo-0001/test/ForgejoSearchRepoIDsDedupTest.java @@ -0,0 +1,92 @@ +import java.util.*; + +/** + * Unit test for Forgejo CWE-407 defect: Results.RepoIDs() in + * modules/indexer/code/search.go uses slices.Contains() for dedup, + * creating O(N^2) complexity on code search results. + * + * Additionally, the original code uses make([]int64, len(res)) which + * pre-fills the slice with N zeros, making Contains scan even more + * data than necessary. + * + * Fix: replace slices.Contains with a map[int64]struct{} set lookup. + */ +public class ForgejoSearchRepoIDsDedupTest { + + // --- DEFECTIVE: slices.Contains O(N^2) --- + static List repoIDsDefective(long[] repoIDs) { + // Simulates: ids := make([]int64, len(res)) — pre-filled with zeros! + List ids = new ArrayList<>(Collections.nCopies(repoIDs.length, 0L)); + for (long repoID : repoIDs) { + if (!ids.contains(repoID)) { + ids.add(repoID); + } + } + return ids; + } + + // --- FIXED: map-based O(N) --- + static List repoIDsFixed(long[] repoIDs) { + Set seen = new HashSet<>(repoIDs.length); + List ids = new ArrayList<>(repoIDs.length); + for (long repoID : repoIDs) { + if (seen.add(repoID)) { + ids.add(repoID); + } + } + return ids; + } + + public static void main(String[] args) { + // Simulate code search returning N results from N/2 distinct repos + int[] sizes = {100, 500, 1000, 5000}; + System.out.println("forgejo-0001: Results.RepoIDs() slices.Contains dedup"); + System.out.println("N\tDefect(ms)\tFixed(ms)\tRatio"); + + for (int N : sizes) { + long[] repoIDs = new long[N]; + Random rng = new Random(42); + for (int i = 0; i < N; i++) { + repoIDs[i] = rng.nextInt(N / 2) + 1; + } + + // Warmup + for (int w = 0; w < 3; w++) { + repoIDsDefective(repoIDs); + repoIDsFixed(repoIDs); + } + + int iters = Math.max(1, 200000 / N); + + long t0 = System.nanoTime(); + for (int i = 0; i < iters; i++) { + repoIDsDefective(repoIDs); + } + long defectNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < iters; i++) { + repoIDsFixed(repoIDs); + } + long fixedNs = System.nanoTime() - t0; + + double defectMs = defectNs / 1e6; + double fixedMs = fixedNs / 1e6; + double ratio = defectMs / fixedMs; + + System.out.printf("%d\t%.1f\t\t%.1f\t\t%.1fx%n", N, defectMs, fixedMs, ratio); + + // Correctness: both must produce same unique set + List dResult = repoIDsDefective(repoIDs); + List fResult = repoIDsFixed(repoIDs); + // Remove the leading zeros from defective version + dResult.removeIf(id -> id == 0L); + Set dSet = new HashSet<>(dResult); + Set fSet = new HashSet<>(fResult); + assert dSet.equals(fSet) : "Results differ at N=" + N; + assert ratio > 1.5 || N < 200 : "Expected speedup at N=" + N + " but got ratio=" + ratio; + } + + System.out.println("ALL PASS"); + } +} diff --git a/defects/snort3-0001/patch/snort3-0001.patch b/defects/snort3-0001/patch/snort3-0001.patch new file mode 100644 index 000000000..084210ca2 --- /dev/null +++ b/defects/snort3-0001/patch/snort3-0001.patch @@ -0,0 +1,46 @@ +--- a/src/network_inspectors/appid/service_plugins/service_discovery.cc ++++ b/src/network_inspectors/appid/service_plugins/service_discovery.cc +@@ -1,5 +1,6 @@ + #include + #include ++#include + #include + + // ... (includes) +@@ -262,13 +263,15 @@ + ServiceMatch* match_list = nullptr; + patterns->find_all((const char*)pkt->data, pkt->dsize, &pattern_match, false, + (void*)&match_list); + + std::vector smOrderedList; + for (ServiceMatch* sm = match_list; sm; sm = sm->next) + smOrderedList.emplace_back(sm); + + if (!smOrderedList.empty() ) + { + std::sort(smOrderedList.begin(), smOrderedList.end(), AppIdPatternPrecedence); ++ std::unordered_set seen(asd.service_candidates.begin(), ++ asd.service_candidates.end()); + for ( auto& sm : smOrderedList ) + { +- if ( std::find(asd.service_candidates.begin(), asd.service_candidates.end(), +- sm->service) == asd.service_candidates.end() ) ++ if ( seen.insert(sm->service).second ) + { + asd.service_candidates.emplace_back(sm->service); + } +@@ -348,9 +351,11 @@ + { + asd.service_candidates = it1->second; + if (it2 != services.end() && it2 != it1) + { ++ std::unordered_set seen(asd.service_candidates.begin(), ++ asd.service_candidates.end()); + for (ServiceDetector* candidate : it2->second) + { +- if (std::find(asd.service_candidates.begin(), asd.service_candidates.end(), +- candidate) == asd.service_candidates.end()) ++ if (seen.insert(candidate).second) + asd.service_candidates.push_back(candidate); + } + } diff --git a/defects/snort3-0001/test/Snort3ServiceCandidateDedupTest.class b/defects/snort3-0001/test/Snort3ServiceCandidateDedupTest.class new file mode 100644 index 0000000000000000000000000000000000000000..8e767caeeb5b2c710d6734d53a7e2d1ccc35e7b4 GIT binary patch literal 3400 zcmai0TTmO<8UBuLE6HmvLJLwbAje>H2VX{xC5*vfVq65&!cak)WRVtFBX-H|ih_G{ z8``GnCFv~}+SD~Ke&|EnbOOXq-M;jtFYQa)m-eMIoy@p1eab^7lbZf#B^lu`tseHA zJ?G#5eE>tBhj;G-=*Mk89B>M7iEzWiP;p&ek-M_8Hs3WidtFIe44xiUQ>|VG zXLHM>053z8ZIrXrY*)Ww$ZI32Y55_*&z&4%2se-HSq$jeEcsD&E#5Ne$6=HSC>K$I zN`}h4(}p!mnO6)3VMWg7l(9L6>gM5=ydxV=!6JSLRR{`@L{y`Op=|Fcj)TFQ)-_93 zHIqRq04a$l-ZIS{ghf2Tzjn%*jDRCWD0WPjWpg2^P(-!xA3_7d0wN5D_s*eBAD+Z9 z0gWP>aC~3dl53`=ERknDM_{CU0IJ?KZm61-v}w}xAW{piI70evFT?;=A((oRDm z{()k&gFM0sg%`ADNw(4p7uUvR%Tf$2k5pYz7#d57#;YHnDN@EpX)p2f21Q)rhgI!& zWk>3S^KhRZmoY5hiil5Pq(~~8HVN5UplUA3s|@buVLN~)F)CsV<3u)1*7ON=iNKIN zu6X)8*KhKScrif)Zp!$YI#15|N}j7tnje#x67U%=f>bf9_F02^ZZ=DR0S~y$4YHU^_yyMD349UP^>ViWl%QRr`P~%12K+TWI$Fq@FX<%8<&VX#77K?d16) zCSTNb%d`x6IiXk!ddBqPRjT7v#mr?bGm=r~<`g3$Tam95N`l9bCZVX z5g4P zQMcrlT$^a=kldY7PrKyVf%q;04s4*j_!e?tYTeE5?EaxoAg@1Zu; zzKy!kwwNpIx*v5rqaMj4xi`@oc1fPjsL&w^Hr{#%-sE`mZ#G^J;r(+7-g-L$b%eK` zU^So(5yE?v05s4)N~4Xro@1_qP=dLEz;oCqN@4r(4ZK8Yz6t6sl5=k1cKGwSAH zZ{uXlHT{+l?vj_Hli`Ide0y8I-@?x$I~K(y^=3Xt6MlX zw(cYA2lk$46XCEY?A^insr)!FWpBUl3Jgi^><*re`gV{w>vu*)NtAq>s4UvAi{$3t zv{~&}&WZ)2GO4V@h@6#`7?o9vF9-2y{ai(LS$+}-RfGcixgZZeDf|YGlv8q3;gTs= zsA31#k|}p6V2_?qkdIrKE&x3rqa1ML1F@R7@$3d#IV-}|L<@UTLz6AcZXj?EN=kCy z#XPW$#oyY-$HLX`f*NhO#vX3%Ss5i(jiF!==Id{vR&rNlZX=j?6$;)(E^oH(Vs{>1 z+ zMV5C+qraf}I~c_8FofUZY5b8cI)A0{@3?}0VgwJ6zy}z`$4D>--Dv!{%BqlLN9Y1` z43lh}t|%!?vsuirSMUsbhi)H#qdVh2@H~4!ZG(&PMU?Ln9JhdP+G4aR{g5sU#B}x% zY9I3Z`YBHJj}ZD8j{I7Hxo8sn56bOv7vDyp!~_4h@RiHuaysb>O7B%b(=Lubb{1EU z7vFl+yUy563aAY9ZsEoT1lJw7oWI7id$}$4F^=C2huBAd<%EEz{yWsrKt0EaACaF| X?C1CF=MV5>+VRusPw-Rx4B`I*WUe@W literal 0 HcmV?d00001 diff --git a/defects/snort3-0001/test/Snort3ServiceCandidateDedupTest.java b/defects/snort3-0001/test/Snort3ServiceCandidateDedupTest.java new file mode 100644 index 000000000..f363a6bc0 --- /dev/null +++ b/defects/snort3-0001/test/Snort3ServiceCandidateDedupTest.java @@ -0,0 +1,98 @@ +import java.util.*; + +/** + * Unit test for Snort3 CWE-407 defect: ServiceDiscovery.match_by_pattern() + * and get_port_based_services() use std::find on service_candidates vector + * for dedup, creating O(M*C) complexity per AppID pattern match. + * + * File: src/network_inspectors/appid/service_plugins/service_discovery.cc + * Lines: 273-281 (match_by_pattern), 351-356 (get_port_based_services) + * + * Fix: replace std::find with std::unordered_set for O(1) lookup. + */ +public class Snort3ServiceCandidateDedupTest { + + // Simulate ServiceDetector pointers as Integer IDs + // --- DEFECTIVE: std::find O(M*C) --- + static List matchByPatternDefective(List existingCandidates, int[] matchedServices) { + List candidates = new ArrayList<>(existingCandidates); + for (int service : matchedServices) { + if (!candidates.contains(service)) { + candidates.add(service); + } + } + return candidates; + } + + // --- FIXED: unordered_set O(M+C) --- + static List matchByPatternFixed(List existingCandidates, int[] matchedServices) { + List candidates = new ArrayList<>(existingCandidates); + Set seen = new HashSet<>(candidates); + for (int service : matchedServices) { + if (seen.add(service)) { + candidates.add(service); + } + } + return candidates; + } + + public static void main(String[] args) { + // Simulate: many pattern matches with overlapping service detectors + // In real Snort: custom AppID ODP with many detectors can produce large match lists + // In real Snort, custom ODP rule sets can have hundreds of service detectors; + // with deep packet inspection + multiple pattern matches per flow, M can be large. + int[] sizes = {500, 2000, 5000, 10000}; + System.out.println("snort3-0001: ServiceDiscovery service_candidates std::find dedup"); + System.out.println("M\tDefect(ms)\tFixed(ms)\tRatio"); + + for (int M : sizes) { + // Existing candidates (from port-based detection) + List existing = new ArrayList<>(); + for (int i = 0; i < M / 4; i++) { + existing.add(i); + } + + // Pattern matches: half overlap, half new + Random rng = new Random(42); + int[] matches = new int[M]; + for (int i = 0; i < M; i++) { + matches[i] = rng.nextInt(M); + } + + // Warmup + for (int w = 0; w < 3; w++) { + matchByPatternDefective(existing, matches); + matchByPatternFixed(existing, matches); + } + + int iters = Math.max(1, 200000 / M); + + long t0 = System.nanoTime(); + for (int i = 0; i < iters; i++) { + matchByPatternDefective(existing, matches); + } + long defectNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < iters; i++) { + matchByPatternFixed(existing, matches); + } + long fixedNs = System.nanoTime() - t0; + + double defectMs = defectNs / 1e6; + double fixedMs = fixedNs / 1e6; + double ratio = defectMs / fixedMs; + + System.out.printf("%d\t%.1f\t\t%.1f\t\t%.1fx%n", M, defectMs, fixedMs, ratio); + + // Correctness + List dResult = matchByPatternDefective(existing, matches); + List fResult = matchByPatternFixed(existing, matches); + assert new HashSet<>(dResult).equals(new HashSet<>(fResult)) : "Results differ at M=" + M; + assert dResult.size() == fResult.size() : "Sizes differ at M=" + M; + assert ratio > 1.5 || M < 100 : "Expected speedup at M=" + M + " but got ratio=" + ratio; + } + + System.out.println("ALL PASS"); + } +}