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
493 lines
20 KiB
Java
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);
|
|
}
|
|
}
|