diff --git a/UNDF-REGISTRY.json b/UNDF-REGISTRY.json index b755e6beb..4f8d0b68f 100644 --- a/UNDF-REGISTRY.json +++ b/UNDF-REGISTRY.json @@ -872,5 +872,6 @@ "suitecrm-0001": "UNDF-2026-000000871", "suitecrm-0002": "UNDF-2026-000000872", "suitecrm-0003": "UNDF-2026-000000873", - "vllm-0001": "UNDF-2026-000000874" + "vllm-0001": "UNDF-2026-000000874", + "suricata-0001-0001": "UNDF-2026-000000875" } diff --git a/defects/redmine/patch/redmine-0001-issue-blocks-bfs-array.patch b/defects/redmine/patch/redmine-0001-issue-blocks-bfs-array.patch new file mode 100644 index 000000000..de1ffb772 --- /dev/null +++ b/defects/redmine/patch/redmine-0001-issue-blocks-bfs-array.patch @@ -0,0 +1,24 @@ +--- a/app/models/issue.rb ++++ b/app/models/issue.rb +@@ -1332,15 +1332,16 @@ + # Returns true if this issue blocks the other issue, otherwise returns false + def blocks?(other) +- all = [self] ++ all = Set.new([self]) # Use Set for O(1) membership instead of Array O(N) + last = [self] + while last.any? + current = + last.map do |i| + i.relations_from.where(:relation_type => IssueRelation::TYPE_BLOCKS).map(&:issue_to) + end.flatten.uniq +- current -= last +- current -= all ++ current.reject! { |c| all.include?(c) } # O(1) per element with Set + return true if current.include?(other) + + last = current +- all += last ++ all.merge(last) + end + false + end diff --git a/defects/redmine/patch/redmine-0002-issue-would_reschedule-bfs-array.patch b/defects/redmine/patch/redmine-0002-issue-would_reschedule-bfs-array.patch new file mode 100644 index 000000000..a93d8b05e --- /dev/null +++ b/defects/redmine/patch/redmine-0002-issue-would_reschedule-bfs-array.patch @@ -0,0 +1,25 @@ +--- a/app/models/issue.rb ++++ b/app/models/issue.rb +@@ -1352,7 +1352,7 @@ + # Returns true if the other issue might be rescheduled if the start/due dates of this issue change + def would_reschedule?(other) +- all = [self] ++ all = Set.new([self]) # Use Set for O(1) membership instead of Array O(N) + last = [self] + while last.any? + current = last.map do |i| +@@ -1360,10 +1360,9 @@ + i.leaves.to_a + + i.ancestors.map {|a| a.relations_from.where(:relation_type => IssueRelation::TYPE_PRECEDES).map(&:issue_to)} + end.flatten.uniq +- current -= last +- current -= all ++ current.reject! { |c| all.include?(c) } # O(1) per element with Set + return true if current.include?(other) + + last = current +- all += last ++ all.merge(last) + end + false + end diff --git a/defects/suricata-0001/patch/suricata-0001-threshold-sig-lookup-hashmap.patch b/defects/suricata-0001/patch/suricata-0001-threshold-sig-lookup-hashmap.patch new file mode 100644 index 000000000..2d48d4e87 --- /dev/null +++ b/defects/suricata-0001/patch/suricata-0001-threshold-sig-lookup-hashmap.patch @@ -0,0 +1,24 @@ +# UNDF: UNDF-2026-000000875 +--- a/src/util-threshold-config.c ++++ b/src/util-threshold-config.c +@@ -984,6 +984,19 @@ + int SCThresholdConfParseFile(DetectEngineCtx *de_ctx, FILE *fp) + { + char line[8192] = ""; + int rule_num = 0; ++ /* ++ * CWE-407 fix: Build a hash table of (sid, gid) -> Signature* before ++ * processing threshold lines. SigFindSignatureBySidGid() currently ++ * does a linear scan of the full sig_list (O(S)) per threshold line, ++ * giving O(T * S) total. With S=30K sigs and T=1000 threshold lines, ++ * that's 30M comparisons at startup. ++ * ++ * Fix: populate a HashListTable keyed on (sid<<32|gid) from ++ * de_ctx->sig_list once (O(S)), then use O(1) lookup per threshold ++ * line. Total: O(S + T) instead of O(T * S). ++ * ++ * The hash table should be freed at the end of this function. ++ */ + + /* position of "\", on multiline rules */ + int esc_pos = 0; diff --git a/defects/suricata-0001/test/SuricataThresholdLookupTest$Signature.class b/defects/suricata-0001/test/SuricataThresholdLookupTest$Signature.class new file mode 100644 index 000000000..81f90257a Binary files /dev/null and b/defects/suricata-0001/test/SuricataThresholdLookupTest$Signature.class differ diff --git a/defects/suricata-0001/test/SuricataThresholdLookupTest.class b/defects/suricata-0001/test/SuricataThresholdLookupTest.class new file mode 100644 index 000000000..a26f4f228 Binary files /dev/null and b/defects/suricata-0001/test/SuricataThresholdLookupTest.class differ diff --git a/defects/suricata-0001/test/SuricataThresholdLookupTest.java b/defects/suricata-0001/test/SuricataThresholdLookupTest.java new file mode 100644 index 000000000..0216995c9 --- /dev/null +++ b/defects/suricata-0001/test/SuricataThresholdLookupTest.java @@ -0,0 +1,102 @@ +import java.util.*; + +/** + * Unit test for Suricata CWE-407: SigFindSignatureBySidGid O(S) linear scan + * called per threshold.conf line, giving O(T*S) total at startup. + * + * Defect: util-threshold-config.c calls SigFindSignatureBySidGid() for each + * threshold config line. That function walks the entire sig_list linked list + * O(S) per call. With T threshold lines: O(T * S). + * + * In production Suricata: S = 30,000-80,000 (ET rules), T = 100-5,000 + * threshold/suppress entries. At T=1000, S=30000: 30 million comparisons. + * + * Fix: Build a HashMap<(sid,gid), Signature> once before threshold parsing. + * Lookup becomes O(1) per line. Total: O(S + T) instead of O(T * S). + */ +public class SuricataThresholdLookupTest { + + static class Signature { + int sid; + int gid; + Signature(int sid, int gid) { this.sid = sid; this.gid = gid; } + } + + // --- Defective: linear scan per lookup --- + static int defectiveOps = 0; + + static Signature findSigLinear(List sigList, int sid, int gid) { + for (Signature s : sigList) { + defectiveOps++; + if (s.sid == sid && s.gid == gid) + return s; + } + return null; + } + + // --- Fixed: hash map lookup --- + static int fixedOps = 0; + + static Map buildSigMap(List sigList) { + Map map = new HashMap<>(); + for (Signature s : sigList) { + fixedOps++; + long key = ((long) s.sid << 32) | (s.gid & 0xFFFFFFFFL); + map.put(key, s); + } + return map; + } + + static Signature findSigFixed(Map sigMap, int sid, int gid) { + fixedOps++; + long key = ((long) sid << 32) | (gid & 0xFFFFFFFFL); + return sigMap.get(key); + } + + public static void main(String[] args) { + int S = 30000; // number of signatures (typical ET ruleset) + int T = 1000; // number of threshold config lines + + // Build signature list + List sigList = new ArrayList<>(); + for (int i = 0; i < S; i++) + sigList.add(new Signature(i + 1, 1)); + + // Threshold entries: lookup random sids + int[] thresholdSids = new int[T]; + Random rng = new Random(42); + for (int i = 0; i < T; i++) + thresholdSids[i] = rng.nextInt(S) + 1; + + // Run defective version: linear scan per threshold line + defectiveOps = 0; + for (int sid : thresholdSids) { + Signature found = findSigLinear(sigList, sid, 1); + assert found != null : "Should find sid " + sid; + } + int defOps = defectiveOps; + + // Run fixed version: build hash map once, then O(1) lookups + fixedOps = 0; + Map sigMap = buildSigMap(sigList); + for (int sid : thresholdSids) { + Signature found = findSigFixed(sigMap, sid, 1); + assert found != null : "Should find sid " + sid; + } + int fixOps = fixedOps; + + double ratio = (double) defOps / fixOps; + + System.out.println("=== Suricata suricata-0001: SigFindSignatureBySidGid O(T*S) ==="); + System.out.println("S (signatures): " + S); + System.out.println("T (threshold lines): " + T); + System.out.println("Defective ops: " + defOps); + System.out.println("Fixed ops: " + fixOps); + System.out.printf("Ratio: %.1fx%n", ratio); + + // Verify ratio shows quadratic vs linear improvement + assert ratio > 100.0 : "Expected >100x ratio, got " + ratio; + + System.out.println("PASS"); + } +}