package unit; import java.util.*; /** * CWE-407 unit test: systemd-0002 * * unit_file_get_list() calls strv_contains(states, unit_file_state_to_string(state)) * for every unit file found on disk. strv_contains is O(S) linear scan over the * states filter array. Total cost: O(U × S) where U = unit files, S = state filters. * * Fix: build a HashSet from the states array before the loop — O(1) lookup per unit. */ public class SystemdUnitFileGetListTest { // Enum representing systemd UnitFileState values enum UnitFileState { ENABLED, DISABLED, STATIC, MASKED, LINKED, INDIRECT, ENABLED_RUNTIME, LINKED_RUNTIME, ALIAS, GENERATED, TRANSIENT, BAD } static UnitFileState[] ALL_STATES = UnitFileState.values(); // ----------------------------------------------------------------------- // SLOW path: simulates the defective strv_contains check per unit file // ----------------------------------------------------------------------- static long slowFilter(String[] unitFiles, String[] states) { long ops = 0; List matched = new ArrayList<>(); for (String unit : unitFiles) { // Assign a state (deterministic from unit name hash) UnitFileState state = ALL_STATES[Math.abs(unit.hashCode()) % ALL_STATES.length]; String stateStr = state.name().toLowerCase(); // O(S) linear scan — strv_contains equivalent boolean found = false; for (String s : states) { ops++; if (stateStr.equals(s)) { found = true; break; } } if (found) matched.add(unit); } return ops; } // ----------------------------------------------------------------------- // FAST path: HashSet built once, O(1) lookup per unit // ----------------------------------------------------------------------- static long fastFilter(String[] unitFiles, String[] states) { long ops = 0; Set stateSet = new HashSet<>(Arrays.asList(states)); List matched = new ArrayList<>(); for (String unit : unitFiles) { UnitFileState state = ALL_STATES[Math.abs(unit.hashCode()) % ALL_STATES.length]; String stateStr = state.name().toLowerCase(); ops++; // single hash lookup if (stateSet.contains(stateStr)) matched.add(unit); } return ops; } // ----------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------- static String[] makeUnits(int n) { String[] a = new String[n]; for (int i = 0; i < n; i++) a[i] = "unit-" + i + ".service"; return a; } static String[] makeStates(int s) { String[] a = new String[s]; String[] names = {"enabled", "disabled", "static", "masked", "linked"}; for (int i = 0; i < s; i++) a[i] = names[i % names.length]; return a; } // Count matching results identically for both paths for correctness check static Set matchedSet(String[] unitFiles, String[] states) { Set stateSet = new HashSet<>(Arrays.asList(states)); Set matched = new LinkedHashSet<>(); for (String unit : unitFiles) { UnitFileState state = ALL_STATES[Math.abs(unit.hashCode()) % ALL_STATES.length]; if (stateSet.contains(state.name().toLowerCase())) matched.add(unit); } return matched; } // ----------------------------------------------------------------------- // Tests // ----------------------------------------------------------------------- static int pass = 0; static int fail = 0; static void check(String name, boolean condition) { if (condition) { System.out.println("PASS: " + name); pass++; } else { System.out.println("FAIL: " + name); fail++; } } // Slow path variant that always scans full list (no early exit) to measure worst case static long slowFilterWorstCase(String[] unitFiles, String[] states) { long ops = 0; for (String unit : unitFiles) { // Assign a state that never matches any filter (simulates "bad"/"transient" units) String stateStr = "bad"; // last state, never in typical filter lists for (String s : states) { ops++; if (stateStr.equals(s)) break; // never breaks — scans all S } } return ops; } static long fastFilterWorstCase(String[] unitFiles, String[] states) { long ops = 0; Set stateSet = new HashSet<>(Arrays.asList(states)); for (String unit : unitFiles) { ops++; // one hash lookup per unit, regardless of match } return ops; } static void testCase(int numUnits, int numStates) { String[] units = makeUnits(numUnits); String[] states = makeStates(numStates); // Worst case: every unit has state NOT in filter, slow path scans all S per unit long slowOps = slowFilterWorstCase(units, states); long fastOps = fastFilterWorstCase(units, states); double ratio = (double) slowOps / fastOps; System.out.printf(" U=%d S=%d: slowOps=%d fastOps=%d ratio=%.1fx%n", numUnits, numStates, slowOps, fastOps, ratio); check("U=" + numUnits + " S=" + numStates + ": slowOps == U*S", slowOps == (long) numUnits * numStates); check("U=" + numUnits + " S=" + numStates + ": fastOps == numUnits", fastOps == numUnits); check("U=" + numUnits + " S=" + numStates + ": slowOps/fastOps >= 5x", ratio >= 5.0); } static void testCorrectness() { // Small case: 6 units, filter for "enabled" and "static" String[] units = new String[12]; for (int i = 0; i < 12; i++) units[i] = "svc-" + i + ".service"; String[] states = {"enabled", "static"}; // Run both paths and compare matched counts Set expected = matchedSet(units, states); // Slow path result Set slowMatched = new LinkedHashSet<>(); Set stateSet = new HashSet<>(Arrays.asList(states)); for (String unit : units) { UnitFileState state = ALL_STATES[Math.abs(unit.hashCode()) % ALL_STATES.length]; String stateStr = state.name().toLowerCase(); for (String s : states) { if (stateStr.equals(s)) { slowMatched.add(unit); break; } } } check("correctness: slow matches expected", slowMatched.equals(expected)); // Fast path result Set fastMatched = new LinkedHashSet<>(); for (String unit : units) { UnitFileState state = ALL_STATES[Math.abs(unit.hashCode()) % ALL_STATES.length]; if (stateSet.contains(state.name().toLowerCase())) fastMatched.add(unit); } check("correctness: fast matches expected", fastMatched.equals(expected)); check("correctness: slow equals fast", slowMatched.equals(fastMatched)); } // ----------------------------------------------------------------------- // Main // ----------------------------------------------------------------------- public static void main(String[] args) { System.out.println("=== systemd-0002: unit_file_get_list states filter O(U*S) → O(U) ==="); testCorrectness(); testCase(1000, 5); testCase(2000, 5); testCase(5000, 10); System.out.println(); System.out.printf("%d/%d PASS%n", pass, pass + fail); if (fail > 0) System.exit(1); } }