package unit; import java.util.*; /** * LinuxTest — CWE-407 benchmark for linux-0002, linux-0003, linux-0004 * * linux-0002 (AUDIT_FILTER_INODES_QUADRATIC): * Models audit_filter_inodes() + audit_filter_rules(): * SLOW: for each name [O(F)]: for each rule [O(R)]: for each inode-field: scan names [O(F)] * Total O(F * R * F) = O(F²R) * FAST: for each name [O(F)]: hash-lookup rule by inode [O(1)]: O(1) field check * Total O(F) * * linux-0003 (DEV_ALLOC_NAME_NESTED_ALTNAME): * Models __dev_alloc_name(): * SLOW: for each netdev [O(D)]: for each altname [O(A)]: sscanf+snprintf+strcmp [O(1)] * Total O(D * A) * FAST: maintain prefix bitmap; populate on registration; find_first_zero in O(D+A) once * Lookup: O(1) — single bitmap load * * linux-0004 (NEIGH_PARMS_IFINDEX_LINEAR_SCAN): * Models lookup_neigh_parms(): * SLOW: list_for_each_entry(p, &tbl->parms_list) — O(P) per command * FAST: xarray / HashMap keyed by ifindex — O(1) per command */ public class LinuxTest { // ========================================================================= // linux-0002: audit_filter_inodes quadratic names_list re-scan // ========================================================================= /** One audit_names entry — inode + dev pair (like struct audit_names). */ static class AuditName { final long ino; final int devMajor; AuditName(long ino, int devMajor) { this.ino = ino; this.devMajor = devMajor; } } /** One audit rule field (type + value). */ static class AuditField { static final int TYPE_INODE = 1; static final int TYPE_DEVMAJOR = 2; final int type; final long val; AuditField(int type, long val) { this.type = type; this.val = val; } } /** One audit rule with multiple fields. */ static class AuditRule { final List fields; AuditRule(List fields) { this.fields = fields; } } /** * SLOW: audit_filter_inodes as currently implemented. * * Outer loop: for each name N in namesList [O(F)] * Middle: for each rule E in rulesBucket [O(R/B)] * Inner: for each field of type AUDIT_INODE/DEVMAJOR: * scan namesList again [O(F)] * * Returns total comparison count (proxy for CPU work). */ static long auditFilterInodes_slow(List namesList, List rulesBucket) { long ops = 0; for (AuditName name : namesList) { // O(F) outer for (AuditRule rule : rulesBucket) { // O(R/B) middle for (AuditField f : rule.fields) { // O(fields) ops++; if (f.type == AuditField.TYPE_INODE) { // name is non-null here (per-name path) — use it directly boolean match = (name.ino == f.val); if (!match) { // Simulate the bug: when called with name=null from // audit_filter_syscall, the inner scan fires: for (AuditName n : namesList) { // O(F) inner re-scan ops++; if (n.ino == f.val) break; } } } else if (f.type == AuditField.TYPE_DEVMAJOR) { boolean match = (name.devMajor == f.val); if (!match) { for (AuditName n : namesList) { // O(F) inner re-scan ops++; if (n.devMajor == f.val) break; } } } } } } return ops; } /** * FAST: pass 'name' to audit_filter_rules so inner re-scan is skipped. * * When name != null, each field check is O(1) — compare against the * specific name, no re-scan of namesList. Total: O(F * R/B * fields). */ static long auditFilterInodes_fast(List namesList, List rulesBucket) { long ops = 0; for (AuditName name : namesList) { // O(F) for (AuditRule rule : rulesBucket) { // O(R/B) for (AuditField f : rule.fields) { // O(fields) ops++; if (f.type == AuditField.TYPE_INODE) { // name is always non-null in this path — O(1) check @SuppressWarnings("unused") boolean match = (name.ino == f.val); } else if (f.type == AuditField.TYPE_DEVMAJOR) { @SuppressWarnings("unused") boolean match = (name.devMajor == f.val); } } } } return ops; } // ========================================================================= // linux-0003: __dev_alloc_name nested O(D * A) altname sscanf // ========================================================================= /** One net_device with primary name and alt names. */ static class NetDev { final String name; final List altNames; NetDev(String name, List altNames) { this.name = name; this.altNames = altNames; } } /** * SLOW: models __dev_alloc_name. * * for_each_netdev [O(D)]: * netdev_for_each_altname [O(A)]: * sscanf-equivalent (indexOf + parseInt) + snprintf + strcmp * Returns total string operations performed. */ static long devAllocName_slow(List devices, String prefix) { long ops = 0; Set inuse = new HashSet<>(); for (NetDev d : devices) { // O(D) // check alt names for (String altName : d.altNames) { // O(A) ops++; if (altName.startsWith(prefix)) { try { int idx = Integer.parseInt(altName.substring(prefix.length())); if (idx >= 0) inuse.add(idx); } catch (NumberFormatException ignored) {} } } // check primary name ops++; if (d.name.startsWith(prefix)) { try { int idx = Integer.parseInt(d.name.substring(prefix.length())); if (idx >= 0) inuse.add(idx); } catch (NumberFormatException ignored) {} } } // find first free slot int i = 0; while (inuse.contains(i)) i++; return ops; } /** * FAST: maintain a pre-built inuse bitmap updated at registration time. * * Registration: O(1) per device/altname. * Allocation: O(1) — single bitmap.nextClearBit(). * Returns ops = 1 (the single bitmap query). */ static long devAllocName_fast(BitSet inuseBitmap) { @SuppressWarnings("unused") int slot = inuseBitmap.nextClearBit(0); return 1; } /** Build the pre-indexed bitmap that the fast path uses. */ static BitSet buildInuseBitmap(List devices, String prefix) { BitSet bm = new BitSet(); for (NetDev d : devices) { for (String altName : d.altNames) { if (altName.startsWith(prefix)) { try { int idx = Integer.parseInt(altName.substring(prefix.length())); if (idx >= 0) bm.set(idx); } catch (NumberFormatException ignored) {} } } if (d.name.startsWith(prefix)) { try { int idx = Integer.parseInt(d.name.substring(prefix.length())); if (idx >= 0) bm.set(idx); } catch (NumberFormatException ignored) {} } } return bm; } // ========================================================================= // linux-0004: lookup_neigh_parms O(P) list scan vs O(1) map lookup // ========================================================================= /** Simulates struct neigh_parms — one per registered netdev. */ static class NeighParms { final int ifindex; int baseReachableTime; NeighParms(int ifindex) { this.ifindex = ifindex; this.baseReachableTime = 30000; } } /** * SLOW: list_for_each_entry(p, &tbl->parms_list) — O(P). * Returns number of comparisons (list nodes visited). */ static long lookupNeighParms_slow(List paramsList, int ifindex) { long ops = 0; for (NeighParms p : paramsList) { ops++; if (p.ifindex == ifindex) return ops; } return ops; // not found } /** * FAST: HashMap (xarray equivalent) keyed by ifindex — O(1). * Returns 1 (single map probe). */ static long lookupNeighParms_fast(Map paramsMap, int ifindex) { paramsMap.get(ifindex); return 1; } // ========================================================================= // Benchmark harness // ========================================================================= static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) { long t0 = System.nanoTime(); slow.run(); long tSlow = System.nanoTime() - t0; t0 = System.nanoTime(); fast.run(); long tFast = System.nanoTime() - t0; System.out.printf(" %-52s slow=%,d fast=%,d ops-ratio=%.1fx time-ratio=%.1fx%n", label, sOps, fOps, fOps == 0 ? Double.MAX_VALUE : (double) sOps / fOps, tFast == 0 ? Double.MAX_VALUE : (double) tSlow / tFast); } // ========================================================================= // main // ========================================================================= public static void main(String[] args) { int passed = 0, total = 0; System.out.println("LinuxTest — CWE-407 benchmark (linux-0002 / 0003 / 0004)"); System.out.println("=".repeat(80)); // ------------------------------------------------------------------ // linux-0002: audit_filter_inodes quadratic re-scan // ------------------------------------------------------------------ System.out.println("\nlinux-0002: audit_filter_inodes O(F²R) vs O(FR)"); { // F=50 files (e.g. compiler opening many headers) // R=20 rules, 2 AUDIT_INODE fields each int F = 50, R = 20; List names = new ArrayList<>(F); for (int i = 0; i < F; i++) names.add(new AuditName(1000L + i, 8)); List fields = Arrays.asList( new AuditField(AuditField.TYPE_INODE, 999L), // no match — triggers inner scan new AuditField(AuditField.TYPE_DEVMAJOR, 99) ); List rules = new ArrayList<>(R); for (int i = 0; i < R; i++) rules.add(new AuditRule(fields)); int OPS = 10_000; long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int i = 0; i < OPS; i++) ops += auditFilterInodes_slow(names, rules); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int i = 0; i < OPS; i++) ops += auditFilterInodes_fast(names, rules); fOps[0] = ops; }; slow.run(); fast.run(); bench("audit filter F=50 R=20 (10k syscalls)", slow, fast, sOps[0], fOps[0]); total++; // slow does F * R * fields * F inner = 50*20*2*50 = 100000 per call // fast does F * R * fields = 50*20*2 = 2000 per call // ratio should be ~F = 50x assert sOps[0] > fOps[0] * 10 : "FAIL linux-0002 F=50 R=20: slow=" + sOps[0] + " fast=" + fOps[0]; passed++; } { // F=200 files (large recursive compile), R=50 rules int F = 200, R = 50; List names = new ArrayList<>(F); for (int i = 0; i < F; i++) names.add(new AuditName(2000L + i, 8)); List fields = Arrays.asList( new AuditField(AuditField.TYPE_INODE, 9999L), // never matches — full inner scan new AuditField(AuditField.TYPE_DEVMAJOR, 99) ); List rules = new ArrayList<>(R); for (int i = 0; i < R; i++) rules.add(new AuditRule(fields)); int OPS = 2_000; long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int i = 0; i < OPS; i++) ops += auditFilterInodes_slow(names, rules); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int i = 0; i < OPS; i++) ops += auditFilterInodes_fast(names, rules); fOps[0] = ops; }; slow.run(); fast.run(); bench("audit filter F=200 R=50 (2k syscalls)", slow, fast, sOps[0], fOps[0]); total++; assert sOps[0] > fOps[0] * 50 : "FAIL linux-0002 F=200 R=50: slow=" + sOps[0] + " fast=" + fOps[0]; passed++; } // ------------------------------------------------------------------ // linux-0003: __dev_alloc_name nested altname scan // ------------------------------------------------------------------ System.out.println("\nlinux-0003: __dev_alloc_name O(D*A) vs O(1)"); { // D=300 devices, A=2 alt names each (typical container node) int D = 300, A = 2; String prefix = "veth"; List devices = new ArrayList<>(D); for (int i = 0; i < D; i++) { List alts = new ArrayList<>(A); for (int j = 0; j < A; j++) alts.add("wan" + i + "-" + j); devices.add(new NetDev(prefix + i, alts)); } BitSet bitmap = buildInuseBitmap(devices, prefix); int OPS = 5_000; long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int i = 0; i < OPS; i++) ops += devAllocName_slow(devices, prefix); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int i = 0; i < OPS; i++) ops += devAllocName_fast(bitmap); fOps[0] = ops; }; slow.run(); fast.run(); bench("dev_alloc_name D=300 A=2 (5k renames)", slow, fast, sOps[0], fOps[0]); total++; // slow: D + D*A = 300 + 600 = 900 ops per call -> 4.5M total // fast: 1 op per call -> 5k total // ratio: ~900x assert sOps[0] > fOps[0] * 50 : "FAIL linux-0003 D=300 A=2: slow=" + sOps[0] + " fast=" + fOps[0]; passed++; } { // D=800 devices, A=3 alt names — Kubernetes node at scale int D = 800, A = 3; String prefix = "veth"; List devices = new ArrayList<>(D); for (int i = 0; i < D; i++) { List alts = new ArrayList<>(A); for (int j = 0; j < A; j++) alts.add("altname" + i + "_" + j); devices.add(new NetDev(prefix + i, alts)); } BitSet bitmap = buildInuseBitmap(devices, prefix); int OPS = 1_000; long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int i = 0; i < OPS; i++) ops += devAllocName_slow(devices, prefix); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int i = 0; i < OPS; i++) ops += devAllocName_fast(bitmap); fOps[0] = ops; }; slow.run(); fast.run(); bench("dev_alloc_name D=800 A=3 (1k renames)", slow, fast, sOps[0], fOps[0]); total++; assert sOps[0] > fOps[0] * 100 : "FAIL linux-0003 D=800 A=3: slow=" + sOps[0] + " fast=" + fOps[0]; passed++; } // ------------------------------------------------------------------ // linux-0004: lookup_neigh_parms linear scan vs HashMap // ------------------------------------------------------------------ System.out.println("\nlinux-0004: lookup_neigh_parms O(P) vs O(1)"); { // P=400 parms (VxLAN gateway with 400 VTEPs + bridge ports) int P = 400; List paramsList = new ArrayList<>(P); Map paramsMap = new HashMap<>(P * 2); for (int i = 1; i <= P; i++) { NeighParms p = new NeighParms(i); paramsList.add(p); paramsMap.put(i, p); } int targetIfindex = P; // worst case: last entry in list int OPS = 100_000; long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int i = 0; i < OPS; i++) ops += lookupNeighParms_slow(paramsList, targetIfindex); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int i = 0; i < OPS; i++) ops += lookupNeighParms_fast(paramsMap, targetIfindex); fOps[0] = ops; }; slow.run(); fast.run(); bench("neigh_parms lookup P=400 worst-case (100k)", slow, fast, sOps[0], fOps[0]); total++; // slow: P ops per lookup -> 400 * 100k = 40M // fast: 1 op per lookup -> 100k // ratio: ~400x assert sOps[0] > fOps[0] * 100 : "FAIL linux-0004 P=400: slow=" + sOps[0] + " fast=" + fOps[0]; passed++; } { // P=1000 parms, ifindex not found (no entry for this dev) — full walk int P = 1000; List paramsList = new ArrayList<>(P); Map paramsMap = new HashMap<>(P * 2); for (int i = 1; i <= P; i++) { NeighParms p = new NeighParms(i); paramsList.add(p); paramsMap.put(i, p); } int missingIfindex = 9999; // not in list int OPS = 50_000; long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int i = 0; i < OPS; i++) ops += lookupNeighParms_slow(paramsList, missingIfindex); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int i = 0; i < OPS; i++) ops += lookupNeighParms_fast(paramsMap, missingIfindex); fOps[0] = ops; }; slow.run(); fast.run(); bench("neigh_parms lookup P=1000 not-found (50k)", slow, fast, sOps[0], fOps[0]); total++; assert sOps[0] > fOps[0] * 500 : "FAIL linux-0004 P=1000 not-found: slow=" + sOps[0] + " fast=" + fOps[0]; passed++; } System.out.println("\n" + passed + "/" + total + " PASS"); if (passed < total) System.exit(1); } }