From 27382ae7916eddd20fc4787a179c724c701a8a41 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 30 Mar 2026 13:27:58 -0400 Subject: [PATCH] =?UTF-8?q?netdata=20CLEAN;=20telegraf-0001=20dedup=20GetF?= =?UTF-8?q?ield=20O(F=C2=B2)=20=E2=80=94=201=20defect,=201/1=20PASS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Netdata: well-engineered with Judy arrays, dictionaries, hash tables throughout. No CWE-407 defects found. Telegraf: dedup processor Apply() calls m.GetField(f.Key) O(F) inside field comparison loop → O(F²). Fix: build field map for O(1) lookup. 125x overhead at F=500. --- defects/netdata/patch/CLEAN.md | 32 +++++ ...legraf-0001-dedup-getfield-quadratic.patch | 44 +++++++ defects/telegraf/unit/TelegrafTest.class | Bin 0 -> 3859 bytes defects/telegraf/unit/TelegrafTest.java | 113 ++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 defects/netdata/patch/CLEAN.md create mode 100644 defects/telegraf/patch/telegraf-0001-dedup-getfield-quadratic.patch create mode 100644 defects/telegraf/unit/TelegrafTest.class create mode 100644 defects/telegraf/unit/TelegrafTest.java diff --git a/defects/netdata/patch/CLEAN.md b/defects/netdata/patch/CLEAN.md new file mode 100644 index 000000000..8b5390982 --- /dev/null +++ b/defects/netdata/patch/CLEAN.md @@ -0,0 +1,32 @@ +# Netdata — CWE-407 Scan Result: CLEAN + +**Date:** 2026-03-30 +**Scanner:** agent blackops +**Target:** https://github.com/netdata/netdata (depth=1) +**Language:** C + Go + +## Scan Coverage + +- `src/health/` — alarm templates, silencers, prototypes +- `src/streaming/` — stream path, replication, capabilities +- `src/database/` — rrdlabels, rrdhost, rrdset, rrddim, contexts, query_target, engine +- `src/libnetdata/` — adaptive_resortable_list, dictionary, facets, string dedup, user-auth +- `src/web/` — websocket JSONRPC, MCP, API v1 +- `src/exporting/` — Prometheus exporter server list +- `src/registry/` — person/machine URL tracking +- `src/collectors/` — ebpf, log2journal +- `src/go/plugin/` — job manager, SNMP profile loader, multipath, weblog collector + +## Findings + +No CWE-407 defects found. Netdata's core data structures are well-engineered: + +- **Judy arrays** (JudyL, JudyHS) for labels, metrics registry +- **Dictionary** (hash-table based) for rrdhost, rrdset, rrddim, contexts +- **SIMPLE_HASHTABLE** for facets value indexing +- **Bitmask dedup** for HTTP access flags, RRDR options, stream capabilities +- **Adaptive Resortable List** (self-sorting linked list) for /proc parsing — amortized fast path +- **STRING dedup** via global hash table for string interning + +The few linked-list scans found (registry person URLs, Prometheus server list, health silencer list) +are per-request lookups, not inside nested loops. No O(N²) membership patterns detected. diff --git a/defects/telegraf/patch/telegraf-0001-dedup-getfield-quadratic.patch b/defects/telegraf/patch/telegraf-0001-dedup-getfield-quadratic.patch new file mode 100644 index 000000000..7dd83e1db --- /dev/null +++ b/defects/telegraf/patch/telegraf-0001-dedup-getfield-quadratic.patch @@ -0,0 +1,44 @@ +# UNDF: (leave blank) +# CWE-407: Dedup processor Apply() uses GetField O(F) inside field loop → O(F²) +# +# In plugins/processors/dedup/dedup.go, the Apply() method compares each field +# of an incoming metric against the cached metric by calling m.GetField(f.Key) +# which performs a linear scan of the cached metric's field list. For each +# incoming metric, this is O(F_new × F_cached) ≈ O(F²). +# +# Fix: Build a map from the cached metric's fields for O(1) lookup, reducing +# the overall comparison to O(F). +# +# Severity: MEDIUM — Telegraf metrics typically have 10-50 fields, but +# system/CPU/disk metrics can exceed 100 fields. The dedup processor is a +# common pipeline component. +# +--- a/plugins/processors/dedup/dedup.go ++++ b/plugins/processors/dedup/dedup.go +@@ -31,6 +31,14 @@ func (d *Dedup) Apply(metrics ...telegraf.Metric) []telegraf.Metric { + idx := 0 + for _, metric := range metrics { + id := metric.HashID() + m, ok := d.cache[id] + + // If not in cache then just save it +@@ -53,8 +61,15 @@ func (d *Dedup) Apply(metrics ...telegraf.Metric) []telegraf.Metric { + + // For each field compare value with the cached one + changed := false + added := false + sametime := metric.Time() == m.Time() ++ ++ // Build a map of cached fields for O(1) lookup instead of ++ // calling m.GetField() which is O(F) per call ++ cachedFields := make(map[string]interface{}, len(m.FieldList())) ++ for _, cf := range m.FieldList() { ++ cachedFields[cf.Key] = cf.Value ++ } ++ + for _, f := range metric.FieldList() { +- if value, ok := m.GetField(f.Key); ok { ++ if value, ok := cachedFields[f.Key]; ok { + if value != f.Value { + changed = true + break diff --git a/defects/telegraf/unit/TelegrafTest.class b/defects/telegraf/unit/TelegrafTest.class new file mode 100644 index 0000000000000000000000000000000000000000..ea0101726b9f3f8142e44aa50cbdf8019cea7285 GIT binary patch literal 3859 zcmb7GYj7J^75=WZ(#q>iV>`ARIgc)hli2Yy%G|t8OyZ<%hUpK6;RpYK8Ti2rzxl~9G?19@?ylo?VqlOx zckkZ4=bZ1H^PO|=Z|>Z<37{A6XedKKg`y*fkU-@b^Q_sPF|$+cqo>YT6ShF8E1gZ- zJpzHorei9?0&AR2B|DvI?=2L}bMbW1)}TU@N-6}D#v!?L)iQMnyO7RKRnzKf9hImO zcyPv?w)%3}3DX`=+ow4z+Gf@+3e-|TdI&4LRz;nT2eDS*fh8ys1wopr zlvLmocO8i}9hb88I@V#mKsaq%1=G$I1nL?+V~$5d-g>M88}X0|L&wLkNuYYk(&82Y z^|VmOfEY~dR_JuU2qLD#7;e+UTs6oee9nI3Sko9!Q%oJ6$ z3RErGK4cb851V=EO1q8@?4YS)`dQ0CFr=XcS_O7tw~9SF_F|vFswH6xa$mkgy!%#& zkPpye%Bc61v3R;rXY9%(J_jn0{WPTV$71P z(;*aS_V2lP*5^`T|CT|w3eru9dmOje_Zua81B;`h8>AmT& z)N@S7xYSeS*E5)&wNhau1)_#A*u5pyX`~$0oEo`&vBj8_JMO-b`{`WwmbM*}vszi0nZ%Te(^4kAj1v91(y0t%Qrp zEHOGf;4MqN1N|Dy`XwC~oW^g>rdbV2UoOSmt(N|elx9v@g)!5~=Bk8kPE4~4dGF=}t%sW8E=A$eZq?1H>b2+^rVsyd1$1m}11D}G=;Pz_}Kj%c?%N*rrKx3cbIYL3XHAk@k zpS$0KnykNu@_3+oef?!@Z4Q0_JsDVk0}lX+q{0a=P>JhUQ-<;LWnymOPtEUAu*^|i zM>Syb%2CC!8m)ZpmH6B4d{~CD3YGeSKc^`6(2mS?B#YW>6 zjkv~Np>dvup7AR*9j5UU<-M-?P9>sv01nBSi=Z>u?11YGMMB=!ZF1&)RSnJTji`ZW zBp8S~Ktpo~%`N{kD`f?nsJPms3fB&p+^wB z4NW2T+t{FD8zJ$8J1~ zJ?P`Da1i^D!hYn@Ne0C547wa|*Fw322)*4!KX0MM&Dt%-#!Z@ZmZt|POxl(O=qhCGCC^7YYEg7iPMw3cqbTk>POpPW(RpM`>@#f){n>~R&O0BZtGG1$`RoeCh zYlHJR(iw6eY9tg<=ke&?usiwqUac;Cp-#Jy+pkOgkE&Pd(v^fwvuds?Tl{JJi?2IGiJNfS; zV2_`Gt&&^wC`bza3abe=sE})CUq=ls$&WV}PB0pQ*xbTRuGL>cxcb>S1gk%F1J601 z>fdH++@d|hdI1hFu)XXGeWYbS6Er|l4stw%5gy3nIE<5QGcy>*OMJeJNAV^e!wu5) zeeVAdkK@;*>~C=#f8|x~?|4G+HYe)vq*#lSVjWJ29hh)ZJc9BCay-Zlukv588~qcn z`~$%|Si|<<{FCb)4%f>*LM5qwM8)6&ldiZzaudq9rn0S+<28K4B?MlePqG!ZC|&F3 z@%(dL^!_Sd;J8l4;Jl2%i;N84)W94RWgcIYozaa%J$oaN=G!!*F+*x)@eq4sGifSI zu>r~ggGIyJ*jYVD?wbcjJHXQn-*hy73m5sUXFa|}beEj*+s^o=GkzCy{I2oakMUEy HjmW makeFields(int n) { + List fields = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + fields.add(new String[]{"field_" + i, "value_" + i}); + } + return fields; + } + + /** DEFECTIVE: linear scan for each field lookup, O(F) per call. */ + static Object getFieldLinear(List fields, String key) { + for (String[] f : fields) { + if (f[0].equals(key)) { + return f[1]; + } + } + return null; + } + + /** DEFECTIVE: dedup comparison using linear GetField — O(F²). */ + static long dedupCompareDefective(List incoming, List cached) { + long ops = 0; + for (String[] f : incoming) { + // Linear scan of cached fields + for (String[] cf : cached) { + ops++; + if (cf[0].equals(f[0])) { + break; + } + } + } + return ops; + } + + /** FIXED: build hash map first, then O(1) lookup — O(F). */ + static long dedupCompareFixed(List incoming, List cached) { + long ops = 0; + Map cachedMap = new HashMap<>(cached.size()); + for (String[] cf : cached) { + cachedMap.put(cf[0], cf[1]); + ops++; + } + for (String[] f : incoming) { + cachedMap.get(f[0]); // O(1) amortized + ops++; + } + return ops; + } + + static boolean testDedupGetField() { + System.out.println("=== telegraf-0001: Dedup GetField O(F²) → O(F) ==="); + boolean pass = true; + + int[] sizes = {10, 50, 100, 500}; + for (int n : sizes) { + List incoming = makeFields(n); + List cached = makeFields(n); + + long defectOps = dedupCompareDefective(incoming, cached); + long fixedOps = dedupCompareFixed(incoming, cached); + double ratio = (double) defectOps / fixedOps; + + System.out.printf(" F=%d: defect=%d ops, fixed=%d ops, ratio=%.1fx%n", + n, defectOps, fixedOps, ratio); + + // At F=100: defect ~5050 ops (sum 1..100), fixed ~200 ops, ratio ~25x + // At F=500: defect ~125250 ops, fixed ~1000 ops, ratio ~125x + if (n >= 50 && ratio < 2.0) { + System.out.printf(" FAIL: expected ratio >= 2.0 at F=%d, got %.1f%n", n, ratio); + pass = false; + } + } + + // Verify correctness: both should find the same matches + List a = makeFields(20); + List b = makeFields(20); + for (String[] f : a) { + Object linearResult = getFieldLinear(b, f[0]); + if (linearResult == null || !linearResult.equals(f[1])) { + System.out.println(" FAIL: linear lookup returned wrong result for " + f[0]); + pass = false; + } + } + + System.out.println(" " + (pass ? "PASS" : "FAIL")); + return pass; + } + + // --- Main --- + + public static void main(String[] args) { + boolean allPass = true; + + allPass &= testDedupGetField(); + + System.out.println(); + System.out.println(allPass ? "ALL TESTS PASSED" : "SOME TESTS FAILED"); + System.exit(allPass ? 0 : 1); + } +}