java-topology/defects/systemd/unit/SystemdUnitFileGetListTest.java

196 lines
7.7 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<String> 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<String> stateSet = new HashSet<>(Arrays.asList(states));
List<String> 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<String> matchedSet(String[] unitFiles, String[] states) {
Set<String> stateSet = new HashSet<>(Arrays.asList(states));
Set<String> 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<String> 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<String> expected = matchedSet(units, states);
// Slow path result
Set<String> slowMatched = new LinkedHashSet<>();
Set<String> 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<String> 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);
}
}