382 lines
16 KiB
Java
382 lines
16 KiB
Java
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<Component> 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<Component> componentList,
|
||
List<AggDev> 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<Object, Component> componentMap,
|
||
List<AggDev> 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<ModuleBtf> 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<ModuleBtf> moduleBtfs,
|
||
String[] kptrFieldNames,
|
||
int kind,
|
||
Map<String, Integer> 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<Object> devPtrs = new ArrayList<>(C);
|
||
for (int i = 0; i < C; i++) devPtrs.add(new Object());
|
||
|
||
List<Component> componentList = new ArrayList<>(C);
|
||
Map<Object, Component> 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<AggDev> 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<Object> devPtrs = new ArrayList<>(C);
|
||
for (int i = 0; i < C; i++) devPtrs.add(new Object());
|
||
|
||
List<Component> componentList = new ArrayList<>(C);
|
||
Map<Object, Component> componentMap = new IdentityHashMap<>(C * 2);
|
||
for (Object dev : devPtrs) {
|
||
Component comp = new Component(dev);
|
||
componentList.add(comp);
|
||
componentMap.put(dev, comp);
|
||
}
|
||
|
||
List<AggDev> 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<ModuleBtf> 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<String, Integer> 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<ModuleBtf> 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<String, Integer> 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);
|
||
}
|
||
}
|