java-topology/defects/linux/unit/LinuxTest.java
russell@unturf.com 9934133dcf whitepaper: 312 sites / 151 ecosystems — wave2+3 defect tables and PDF rebuild
Add 88 new defect entries to HIGH and MEDIUM tables:
  HIGH: mysql-0001/0002, mariadb-0001, redis-0001/0002, valkey-0001/0002, openvpn-0001,
        vlc-0001, prometheus-0001, otel-collector-0001, cockroachdb-0001..0004,
        tidb-0001..0008, kubernetes-0001/0002, go-0001, kotlin-0002, scala-0001,
        allegro5-0001, sdl2-0001, grafana-0001, clickhouse-0001, duckdb-0001,
        mongodb-0001, envoy-0001, istio-0001, cilium-0001, linkerd2-0001,
        linux-0001/0002/0003, tor-0002/0003, curl-0001, julia-0001, lua-0001,
        perl5-0001, nats-0001, spring-0003/0004, tomcat-0001, onos-0002, odl-0002

  MEDIUM: helm-0001, mariadb-0002, openssl-0001/0002, memcached-0001,
          cassandra-0001..0004, flink-0001, storm-0001/0002, zookeeper-0001..0003,
          pip-0001, gradle-0001, nginx-0001, haproxy-0001, caddy-0001, varnish-0001,
          ffmpeg-0001, gstreamer-0001, raylib-0001, love2d-0001, php-0001/0002,
          r-source-0001, cpython-0002, ruby-0001, rabbitmq-0003/0004, activemq-0001,
          ovs-0001, onos-0003, odl-0002, jetty-0001

PDF: 976K
2026-03-27 15:23:43 -04:00

493 lines
20 KiB
Java

package unit;
import java.util.*;
/**
* LinuxTest — CWE-407 benchmark for linux-0001, linux-0002, linux-0003
*
* linux-0001 (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-0002 (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-0003 (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-0001: 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<AuditField> fields;
AuditRule(List<AuditField> 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<AuditName> namesList,
List<AuditRule> 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<AuditName> namesList,
List<AuditRule> 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-0002: __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<String> altNames;
NetDev(String name, List<String> 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<NetDev> devices, String prefix) {
long ops = 0;
Set<Integer> 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<NetDev> 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-0003: 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<NeighParms> 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<Integer, NeighParms> 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-0001 / 0002 / 0003)");
System.out.println("=".repeat(80));
// ------------------------------------------------------------------
// linux-0001: audit_filter_inodes quadratic re-scan
// ------------------------------------------------------------------
System.out.println("\nlinux-0001: 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<AuditName> names = new ArrayList<>(F);
for (int i = 0; i < F; i++) names.add(new AuditName(1000L + i, 8));
List<AuditField> fields = Arrays.asList(
new AuditField(AuditField.TYPE_INODE, 999L), // no match — triggers inner scan
new AuditField(AuditField.TYPE_DEVMAJOR, 99)
);
List<AuditRule> 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-0001 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<AuditName> names = new ArrayList<>(F);
for (int i = 0; i < F; i++) names.add(new AuditName(2000L + i, 8));
List<AuditField> fields = Arrays.asList(
new AuditField(AuditField.TYPE_INODE, 9999L), // never matches — full inner scan
new AuditField(AuditField.TYPE_DEVMAJOR, 99)
);
List<AuditRule> 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-0001 F=200 R=50: slow=" + sOps[0] + " fast=" + fOps[0];
passed++;
}
// ------------------------------------------------------------------
// linux-0002: __dev_alloc_name nested altname scan
// ------------------------------------------------------------------
System.out.println("\nlinux-0002: __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<NetDev> devices = new ArrayList<>(D);
for (int i = 0; i < D; i++) {
List<String> 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-0002 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<NetDev> devices = new ArrayList<>(D);
for (int i = 0; i < D; i++) {
List<String> 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-0002 D=800 A=3: slow=" + sOps[0] + " fast=" + fOps[0];
passed++;
}
// ------------------------------------------------------------------
// linux-0003: lookup_neigh_parms linear scan vs HashMap
// ------------------------------------------------------------------
System.out.println("\nlinux-0003: lookup_neigh_parms O(P) vs O(1)");
{
// P=400 parms (VxLAN gateway with 400 VTEPs + bridge ports)
int P = 400;
List<NeighParms> paramsList = new ArrayList<>(P);
Map<Integer, NeighParms> 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-0003 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<NeighParms> paramsList = new ArrayList<>(P);
Map<Integer, NeighParms> 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-0003 P=1000 not-found: slow=" + sOps[0] + " fast=" + fOps[0];
passed++;
}
System.out.println("\n" + passed + "/" + total + " PASS");
if (passed < total) System.exit(1);
}
}