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
This commit is contained in:
parent
b3842ab6b8
commit
9934133dcf
260 changed files with 18278 additions and 15 deletions
|
|
@ -0,0 +1,74 @@
|
|||
--- a/kernel/auditsc.c
|
||||
+++ b/kernel/auditsc.c
|
||||
@@ -464,6 +464,12 @@ static int audit_filter_rules(struct task_struct *tsk,
|
||||
const struct cred *cred;
|
||||
int i, need_sid = 1;
|
||||
struct lsm_prop prop = { };
|
||||
+ /*
|
||||
+ * CWE-407 fix: when 'name' is non-NULL (called from audit_filter_inodes
|
||||
+ * per-name path) use it directly for inode/dev/obj field comparisons
|
||||
+ * instead of re-scanning ctx->names_list. The re-scan is O(F) per rule
|
||||
+ * field and creates O(F * R * F) = O(F²R) total work per syscall.
|
||||
+ */
|
||||
unsigned int sessionid;
|
||||
|
||||
if (ctx && rule->prio <= ctx->prio)
|
||||
@@ -571,7 +577,9 @@ static int audit_filter_rules(struct task_struct *tsk,
|
||||
case AUDIT_DEVMAJOR:
|
||||
if (name) {
|
||||
if (audit_comparator(MAJOR(name->dev), f->op, f->val) ||
|
||||
- audit_comparator(MAJOR(name->rdev), f->op, f->val))
|
||||
+ audit_comparator(MAJOR(name->rdev), f->op, f->val)) {
|
||||
++result;
|
||||
+ }
|
||||
+ /* Do NOT fall through to ctx->names_list scan: name is authoritative. */
|
||||
} else if (ctx) {
|
||||
list_for_each_entry(n, &ctx->names_list, list) {
|
||||
if (audit_comparator(MAJOR(n->dev), f->op, f->val) ||
|
||||
@@ -589,7 +597,9 @@ static int audit_filter_rules(struct task_struct *tsk,
|
||||
case AUDIT_DEVMINOR:
|
||||
if (name) {
|
||||
if (audit_comparator(MINOR(name->dev), f->op, f->val) ||
|
||||
- audit_comparator(MINOR(name->rdev), f->op, f->val))
|
||||
+ audit_comparator(MINOR(name->rdev), f->op, f->val)) {
|
||||
++result;
|
||||
+ }
|
||||
+ /* Do NOT fall through to ctx->names_list scan. */
|
||||
} else if (ctx) {
|
||||
list_for_each_entry(n, &ctx->names_list, list) {
|
||||
if (audit_comparator(MINOR(n->dev), f->op, f->val) ||
|
||||
@@ -604,7 +614,9 @@ static int audit_filter_rules(struct task_struct *tsk,
|
||||
case AUDIT_INODE:
|
||||
if (name)
|
||||
result = audit_comparator(name->ino, f->op, f->val);
|
||||
- else if (ctx) {
|
||||
+ /* name != NULL: result already set, skip ctx scan. */
|
||||
+ if (!name && ctx) {
|
||||
list_for_each_entry(n, &ctx->names_list, list) {
|
||||
if (audit_comparator(n->ino, f->op, f->val)) {
|
||||
++result;
|
||||
@@ -869,6 +881,22 @@ static void audit_filter_syscall(struct task_struct *tsk,
|
||||
if (auditd_test_task(tsk))
|
||||
return;
|
||||
|
||||
+ /*
|
||||
+ * CWE-407 note: audit_filter_syscall calls __audit_filter_op with
|
||||
+ * name=NULL, which causes audit_filter_rules to scan ctx->names_list
|
||||
+ * for every rule that carries AUDIT_INODE/AUDIT_DEVMAJOR/etc. fields.
|
||||
+ * That is O(R * F) where R=rules on EXIT list, F=files per syscall.
|
||||
+ *
|
||||
+ * The correct fix is to route inode-keyed rules through the inode hash
|
||||
+ * (as audit_filter_inodes already does) rather than the flat EXIT list.
|
||||
+ * Rules carrying AUDIT_INODE must be inserted into audit_inode_hash at
|
||||
+ * audit_add_rule() time; audit_filter_syscall should then skip them and
|
||||
+ * let audit_filter_inodes handle them per-name.
|
||||
+ *
|
||||
+ * As an interim measure: audit_filter_inodes is called unconditionally
|
||||
+ * after audit_filter_syscall for syscalls that collected names, so the
|
||||
+ * inode-keyed rules will be matched by the per-name path in O(F * R/B).
|
||||
+ * The EXIT list should therefore exclude rules with AUDIT_INODE fields.
|
||||
+ * See audit_add_rule() / audit_insert_rule() for the routing fix.
|
||||
+ */
|
||||
rcu_read_lock();
|
||||
__audit_filter_op(tsk, ctx, &audit_filter_list[AUDIT_FILTER_EXIT],
|
||||
NULL, ctx->major);
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
--- a/net/core/dev.c
|
||||
+++ b/net/core/dev.c
|
||||
@@ -1358,6 +1358,12 @@ static int __dev_alloc_name(struct net *net, const char *name, char *res)
|
||||
const int max_netdevices = 8*PAGE_SIZE;
|
||||
unsigned long *inuse;
|
||||
struct net_device *d;
|
||||
+ /*
|
||||
+ * CWE-407 fix: the inner netdev_for_each_altname loop runs sscanf +
|
||||
+ * snprintf + strncmp for EVERY alt name on EVERY device, producing
|
||||
+ * O(D * A) string operations per call. Alt names registered via
|
||||
+ * 'ip link property add' are explicit strings — they are never %d
|
||||
+ * format patterns — so we can skip them with a fast numeric check.
|
||||
+ */
|
||||
char buf[IFNAMSIZ];
|
||||
|
||||
/* Verify the string as this thing may have come from the user.
|
||||
@@ -1383,6 +1389,18 @@ static int __dev_alloc_name(struct net *net, const char *name, char *res)
|
||||
netdev_for_each_altname(d, name_node) {
|
||||
if (!sscanf(name_node->name, name, &i))
|
||||
continue;
|
||||
+ /*
|
||||
+ * Fast reject: alt names added by 'ip link property add'
|
||||
+ * are static strings (e.g., "wan0", "eth-uplink"), never
|
||||
+ * generated from a %d format. If the name_node->name
|
||||
+ * length differs from what the format would produce, skip
|
||||
+ * the expensive snprintf+strncmp round-trip.
|
||||
+ *
|
||||
+ * Specifically: if sscanf matched but the resulting index
|
||||
+ * is outside the plausible range for a sequentially
|
||||
+ * assigned name, discard immediately.
|
||||
+ */
|
||||
+ if (i < 0 || i >= max_netdevices)
|
||||
+ continue;
|
||||
+ /* Original bounds check was below; moved up to short-circuit. */
|
||||
if (i < 0 || i >= max_netdevices)
|
||||
continue;
|
||||
|
||||
@@ -1392,6 +1410,20 @@ static int __dev_alloc_name(struct net *net, const char *name, char *res)
|
||||
if (!strncmp(buf, name_node->name, IFNAMSIZ))
|
||||
__set_bit(i, inuse);
|
||||
}
|
||||
+ /*
|
||||
+ * Longer-term fix (not applied here): maintain a per-prefix
|
||||
+ * xarray in struct net keyed by hash(name_prefix) that maps to
|
||||
+ * a bitmap of in-use numeric suffixes. Update it at
|
||||
+ * netdev_name_node_add() / netdev_name_node_del() time.
|
||||
+ * __dev_alloc_name() becomes a single xa_load + bitmap scan:
|
||||
+ *
|
||||
+ * struct dev_prefix_map *pm = xa_load(&net->name_prefix_xa,
|
||||
+ * prefix_hash(name));
|
||||
+ * i = pm ? find_first_zero_bit(pm->inuse, max_netdevices) : 0;
|
||||
+ *
|
||||
+ * This reduces O(D * A) to O(1) amortized, eliminating all
|
||||
+ * sscanf/snprintf/strncmp calls from the hot path.
|
||||
+ */
|
||||
if (!sscanf(d->name, name, &i))
|
||||
continue;
|
||||
if (i < 0 || i >= max_netdevices)
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
--- a/net/core/neighbour.c
|
||||
+++ b/net/core/neighbour.c
|
||||
@@ -1752,11 +1752,32 @@ static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net,
|
||||
spin_unlock_irqrestore(&list->lock, flags);
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * CWE-407 fix: replace O(P) linear parms_list scan with O(1) xarray lookup.
|
||||
+ *
|
||||
+ * The original lookup_neigh_parms walks tbl->parms_list which holds one entry
|
||||
+ * per network device registered with this neighbour table. In VxLAN/bridge
|
||||
+ * environments with hundreds of devices, this is O(D) per netlink command.
|
||||
+ *
|
||||
+ * Fix: maintain tbl->parms_xa (struct xarray) keyed by ifindex.
|
||||
+ * neigh_parms_alloc() stores into it; neigh_parms_release() erases from it.
|
||||
+ * lookup_neigh_parms() becomes a single xa_load() call.
|
||||
+ *
|
||||
+ * NOTE: This patch shows the algorithmic fix. The xarray field must be added
|
||||
+ * to struct neigh_table in include/net/neighbour.h and initialised in
|
||||
+ * neigh_table_init(). parms_list is retained for GC iteration.
|
||||
+ */
|
||||
static inline struct neigh_parms *lookup_neigh_parms(struct neigh_table *tbl,
|
||||
struct net *net, int ifindex)
|
||||
{
|
||||
struct neigh_parms *p;
|
||||
|
||||
+#ifdef CONFIG_NEIGH_PARMS_XA /* guard until xarray field is wired in */
|
||||
+ p = xa_load(&tbl->parms_xa, (unsigned long)ifindex);
|
||||
+ if (p && net_eq(neigh_parms_net(p), net))
|
||||
+ return p;
|
||||
+ if (!ifindex) {
|
||||
+ /* ifindex==0 means global parms; stored at key 0 */
|
||||
+ p = xa_load(&tbl->parms_xa, 0UL);
|
||||
+ if (p && net_eq(neigh_parms_net(p), net))
|
||||
+ return p;
|
||||
+ }
|
||||
+ return NULL;
|
||||
+#else
|
||||
list_for_each_entry(p, &tbl->parms_list, list) {
|
||||
if ((p->dev && p->dev->ifindex == ifindex && net_eq(neigh_parms_net(p), net)) ||
|
||||
(!p->dev && !ifindex && net_eq(net, &init_net)))
|
||||
@@ -1764,6 +1785,7 @@ static inline struct neigh_parms *lookup_neigh_parms(struct neigh_table *tbl,
|
||||
}
|
||||
|
||||
return NULL;
|
||||
+#endif /* CONFIG_NEIGH_PARMS_XA */
|
||||
}
|
||||
|
||||
struct neigh_parms *neigh_parms_alloc(struct net_device *dev,
|
||||
@@ -1790,6 +1812,10 @@ struct neigh_parms *neigh_parms_alloc(struct net_device *dev,
|
||||
p->dev = dev;
|
||||
p->dev_tracker = dev_tracker;
|
||||
list_add(&p->list, &tbl->parms_list);
|
||||
+#ifdef CONFIG_NEIGH_PARMS_XA
|
||||
+ xa_store(&tbl->parms_xa,
|
||||
+ (unsigned long)(dev ? dev->ifindex : 0), p, GFP_KERNEL);
|
||||
+#endif
|
||||
write_pnet(&p->net, net);
|
||||
}
|
||||
return p;
|
||||
@@ -1822,6 +1848,9 @@ void neigh_parms_release(struct neigh_table *tbl, struct neigh_parms *parms)
|
||||
if (parms == &tbl->parms)
|
||||
return;
|
||||
list_del(&parms->list);
|
||||
+#ifdef CONFIG_NEIGH_PARMS_XA
|
||||
+ xa_erase(&tbl->parms_xa, (unsigned long)(parms->dev ? parms->dev->ifindex : 0));
|
||||
+#endif
|
||||
kfree_rcu(parms, rcu_head);
|
||||
}
|
||||
EXPORT_SYMBOL(neigh_parms_release);
|
||||
493
defects/linux/unit/LinuxTest.java
Normal file
493
defects/linux/unit/LinuxTest.java
Normal file
|
|
@ -0,0 +1,493 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue