package unit; import java.util.*; /** * Linux0005Test — CWE-407 benchmark for linux-0005 and linux-0006 * * linux-0005 (COMPONENT_FIND_QUADRATIC): * Models drivers/base/component.c find_components(): * SLOW: for each adev [O(A)]: * for each match entry [O(M)]: * find_component() scans component_list [O(C)] * Total: O(A × M × C) per component_add() * With N component_add() calls at boot: O(N × A × M × C) = O(N²) boot cost * FAST: hash table keyed on dev pointer — O(1) lookup * Total per component_add(): O(A × M) * * linux-0006 (BTF_MODULE_SCAN_LINEAR): * Models kernel/bpf/btf.c bpf_find_btf_id(): * SLOW: idr_for_each_entry() scans all loaded module BTFs [O(M)] per kptr field * A BPF map struct with F kptr fields → O(F × M) per BPF_MAP_CREATE syscall * Kernel comment: "linear search could be slow" * FAST: hash table keyed on (name_hash ^ kind) — O(1) amortised after first miss * O(F) per BPF_MAP_CREATE after cache warm-up */ public class Linux0005Test { // ========================================================================= // linux-0005: component find_components O(A×M×C) vs O(A×M) // ========================================================================= /** Simulates struct component — one registered device component. */ static class Component { final Object dev; // device pointer (any Object — we use identity) Object boundAdev; // null if unbound Component(Object dev) { this.dev = dev; } } /** One entry in a match array — holds the dev pointer to find. */ static class MatchEntry { final Object devWanted; // the device this entry is looking for Component component; // filled in when found MatchEntry(Object devWanted) { this.devWanted = devWanted; } } /** Simulates struct aggregate_device. */ static class AggDev { final MatchEntry[] match; AggDev(MatchEntry[] match) { this.match = match; } } /** * SLOW: find_component() — O(C) linear scan of component_list. * Returns number of comparisons performed. */ static long findComponent_slow(List componentList, AggDev adev, MatchEntry mc) { long ops = 0; for (Component c : componentList) { ops++; if (c.boundAdev != null && c.boundAdev != adev) continue; // mc->compare(c->dev, mc->data) — identity comparison if (c.dev == mc.devWanted) { return ops; } } return ops; } /** * SLOW: find_components() — calls find_component() M times per adev. * Outer loop over adevs: O(A × M × C). * Returns total comparison count. */ static long findComponents_slow(List componentList, List adevList) { long ops = 0; for (AggDev adev : adevList) { for (MatchEntry mc : adev.match) { if (mc.component != null) continue; ops += findComponent_slow(componentList, adev, mc); } } return ops; } /** * FAST: hash table (IdentityHashMap as O(1) lookup) keyed on dev pointer. * find_component() becomes a single map.get() call. * Returns total comparison count (always 1 per match entry for a hit). */ static long findComponents_fast(Map componentMap, List adevList) { long ops = 0; for (AggDev adev : adevList) { for (MatchEntry mc : adev.match) { if (mc.component != null) continue; ops++; // one hash probe Component c = componentMap.get(mc.devWanted); if (c != null && (c.boundAdev == null || c.boundAdev == adev)) { // found } } } return ops; } // ========================================================================= // linux-0006: bpf_find_btf_id O(F×M) vs O(F) with cache // ========================================================================= /** Simulates one module BTF — holds a flat array of type names. */ static class ModuleBtf { final String moduleName; final String[] typeNames; ModuleBtf(String moduleName, String[] typeNames) { this.moduleName = moduleName; this.typeNames = typeNames; } /** O(T) linear scan — btf_find_by_name_kind for module BTF. */ int findByNameKind(String name, int kind) { for (int i = 0; i < typeNames.length; i++) { if (typeNames[i].equals(name)) return i + 1; // positive id } return -1; } } /** * SLOW: bpf_find_btf_id() — idr_for_each_entry over all module BTFs. * For each kptr field: scan M module BTFs → O(F × M × T). * Returns number of (module-BTF, field) scan iterations. */ static long findBtfId_slow(List moduleBtfs, String[] kptrFieldNames, int kind) { long ops = 0; for (String fieldName : kptrFieldNames) { // idr_for_each_entry walks all M module BTFs for (ModuleBtf mbtf : moduleBtfs) { ops++; int id = mbtf.findByNameKind(fieldName, kind); if (id > 0) break; // found — stop scanning } } return ops; } /** * FAST: name→id hash cache (HashMap as O(1) lookup). * First lookup for a name misses and populates the cache; subsequent * lookups are O(1). Returns total module-BTF iterations across all fields. * * Simulates: check vmlinux (O(log T) bsearch, modelled as O(1)), * then check cache (O(1)), then fall through to O(M) scan on miss. */ static long findBtfId_fast(List moduleBtfs, String[] kptrFieldNames, int kind, Map cache) { long ops = 0; for (String fieldName : kptrFieldNames) { String cacheKey = fieldName + ":" + kind; if (cache.containsKey(cacheKey)) { ops++; // O(1) cache hit continue; } // Cache miss — scan modules (first time only) for (ModuleBtf mbtf : moduleBtfs) { ops++; int id = mbtf.findByNameKind(fieldName, kind); if (id > 0) { cache.put(cacheKey, id); // populate cache break; } } } return ops; } // ========================================================================= // Harness // ========================================================================= static void bench(String label, long sOps, long fOps, long minRatio) { double ratio = fOps == 0 ? Double.MAX_VALUE : (double) sOps / fOps; boolean pass = ratio >= minRatio; System.out.printf(" %-60s slow=%,d fast=%,d ratio=%.1fx [%s]%n", label, sOps, fOps, ratio, pass ? "PASS" : "FAIL"); } // ========================================================================= // main // ========================================================================= public static void main(String[] args) { int passed = 0, total = 0; System.out.println("Linux0005Test — CWE-407 (linux-0005 component, linux-0006 btf)"); System.out.println("=".repeat(76)); // ------------------------------------------------------------------ // linux-0005: component find_components quadratic // ------------------------------------------------------------------ System.out.println("\nlinux-0005: component find_components O(A×M×C) vs O(A×M)"); { // Realistic SoC: 80 components, 6 aggregate devices, 8 match entries each int C = 80, A = 6, M = 8; List devPtrs = new ArrayList<>(C); for (int i = 0; i < C; i++) devPtrs.add(new Object()); List componentList = new ArrayList<>(C); Map componentMap = new IdentityHashMap<>(C * 2); for (Object dev : devPtrs) { Component comp = new Component(dev); componentList.add(comp); componentMap.put(dev, comp); } // Each adev matches the last M devices (worst-case: found at end of list) List adevList = new ArrayList<>(A); for (int a = 0; a < A; a++) { MatchEntry[] matches = new MatchEntry[M]; for (int m = 0; m < M; m++) { // Point at tail of the component list → worst case for linear scan matches[m] = new MatchEntry(devPtrs.get(C - 1 - m)); } adevList.add(new AggDev(matches)); } // Simulate N component_add() events — each triggers find_components on all adevs int N = 80; long slowTotal = 0, fastTotal = 0; for (int n = 0; n < N; n++) { // Clear bound state so all matches are re-evaluated for (AggDev ad : adevList) for (MatchEntry me : ad.match) me.component = null; slowTotal += findComponents_slow(componentList, adevList); } for (int n = 0; n < N; n++) { for (AggDev ad : adevList) for (MatchEntry me : ad.match) me.component = null; fastTotal += findComponents_fast(componentMap, adevList); } // Expected: slow = N × A × M × avg_scan = 80 × 6 × 8 × ~(C/2) ≈ 153600 // fast = N × A × M × 1 = 80 × 6 × 8 = 3840 // Ratio ≈ C/2 = 40x long expectedMinRatio = Math.max(5L, (long)(C / 4)); bench(String.format("SoC boot C=%d A=%d M=%d N=%d component_add events", C, A, M, N), slowTotal, fastTotal, expectedMinRatio); total++; if (slowTotal > fastTotal * expectedMinRatio) passed++; } { // Large display controller: 200 components, 12 adevs, 15 match entries int C = 200, A = 12, M = 15; List devPtrs = new ArrayList<>(C); for (int i = 0; i < C; i++) devPtrs.add(new Object()); List componentList = new ArrayList<>(C); Map componentMap = new IdentityHashMap<>(C * 2); for (Object dev : devPtrs) { Component comp = new Component(dev); componentList.add(comp); componentMap.put(dev, comp); } List adevList = new ArrayList<>(A); for (int a = 0; a < A; a++) { MatchEntry[] matches = new MatchEntry[M]; for (int m = 0; m < M; m++) matches[m] = new MatchEntry(devPtrs.get(C - 1 - m)); adevList.add(new AggDev(matches)); } int N = 200; long slowTotal = 0, fastTotal = 0; for (int n = 0; n < N; n++) { for (AggDev ad : adevList) for (MatchEntry me : ad.match) me.component = null; slowTotal += findComponents_slow(componentList, adevList); } for (int n = 0; n < N; n++) { for (AggDev ad : adevList) for (MatchEntry me : ad.match) me.component = null; fastTotal += findComponents_fast(componentMap, adevList); } long expectedMinRatio = Math.max(5L, (long)(C / 4)); bench(String.format("Display ctrl C=%d A=%d M=%d N=%d component_add events", C, A, M, N), slowTotal, fastTotal, expectedMinRatio); total++; if (slowTotal > fastTotal * expectedMinRatio) passed++; } // ------------------------------------------------------------------ // linux-0006: bpf_find_btf_id O(F×M) vs O(F) with cache // ------------------------------------------------------------------ System.out.println("\nlinux-0006: bpf_find_btf_id O(F×M) vs O(F) with hash cache"); { // 64 loaded kernel modules, BPF map struct with 10 kptr fields int M = 64, F = 10; int KIND = 22; // BTF_KIND_STRUCT // Build module BTFs — the target type lives in the last module (worst case) List moduleBtfs = new ArrayList<>(M); String[] kptrNames = new String[F]; for (int f = 0; f < F; f++) kptrNames[f] = "kptr_type_" + f; for (int m = 0; m < M; m++) { String[] types; if (m == M - 1) { // Last module holds all target types types = Arrays.copyOf(kptrNames, F); } else { types = new String[]{"unrelated_type_" + m}; } moduleBtfs.add(new ModuleBtf("module_" + m, types)); } // Simulate 500 BPF_MAP_CREATE syscalls — each re-scans all kptr fields int SYSCALLS = 500; long slowTotal = 0, fastTotal = 0; for (int s = 0; s < SYSCALLS; s++) slowTotal += findBtfId_slow(moduleBtfs, kptrNames, KIND); Map cache = new HashMap<>(); for (int s = 0; s < SYSCALLS; s++) fastTotal += findBtfId_fast(moduleBtfs, kptrNames, KIND, cache); // slow: SYSCALLS × F × avg_M_scanned = 500 × 10 × 64 = 320000 // fast: first call = 500 × 10 × 64 (cold), subsequent = SYSCALLS-1 × F × 1 // ≈ 10 × 64 + 499 × 10 = 640 + 4990 = 5630 total for F=10 fields // (cache warms on first SYSCALL, rest are O(F) hits) // Actual fast ≈ F*M + (SYSCALLS-1)*F = 640+4990 = 5630 // Ratio ≈ 320000/5630 ≈ 56x bench(String.format("BPF kptr M=%d modules F=%d fields SYSCALLS=%d", M, F, SYSCALLS), slowTotal, fastTotal, 10L); total++; if (slowTotal > fastTotal * 10L) passed++; } { // 200 modules, struct with 25 kptr fields, 1000 map-create events int M = 200, F = 25, SYSCALLS = 1000; int KIND = 22; List moduleBtfs = new ArrayList<>(M); String[] kptrNames = new String[F]; for (int f = 0; f < F; f++) kptrNames[f] = "heavy_kptr_" + f; for (int m = 0; m < M; m++) { String[] types = (m == M - 1) ? Arrays.copyOf(kptrNames, F) : new String[]{"stub_" + m}; moduleBtfs.add(new ModuleBtf("mod_" + m, types)); } long slowTotal = 0, fastTotal = 0; for (int s = 0; s < SYSCALLS; s++) slowTotal += findBtfId_slow(moduleBtfs, kptrNames, KIND); Map cache = new HashMap<>(); for (int s = 0; s < SYSCALLS; s++) fastTotal += findBtfId_fast(moduleBtfs, kptrNames, KIND, cache); // slow: 1000 × 25 × 200 = 5,000,000 // fast: first miss = 25×200=5000, then 999×25=24975 → ≈ 30000 // ratio ≈ 166x bench(String.format("BPF kptr M=%d modules F=%d fields SYSCALLS=%d", M, F, SYSCALLS), slowTotal, fastTotal, 20L); total++; if (slowTotal > fastTotal * 20L) passed++; } System.out.println("\n" + passed + "/" + total + " PASS"); if (passed < total) System.exit(1); } }