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 000000000..bd29ec55e Binary files /dev/null and b/defects/forgejo-0001/test/ForgejoSearchRepoIDsDedupTest.class differ 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 000000000..8e767caee Binary files /dev/null and b/defects/snort3-0001/test/Snort3ServiceCandidateDedupTest.class differ 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"); + } +}