java-topology/defects/dbus-0001/test/TestDbusClientPolicyOptimize.java
russell@unturf.com decacb5dbd systemd+dbus: 5-MOAD scan; systemd-0004 CWE-407 seccomp strv O(K*A), dbus-0001 CWE-407 policy optimize O(R^2)
systemd-0004: seccomp_load_syscall_filter_set() in src/shared/seccomp-util.c uses
strv_contains(added, name) — O(|added|) linear scan — inside NULSTR_FOREACH over
~537 KNOWN syscalls. On x86_64 (3 arches): ~484,000 string comparisons per service
start with SeccompFilter=. Sibling function seccomp_load_syscall_filter_set_raw()
already uses hashmap_contains for O(1); this function was left behind.
Fix: build Set* from added strv before the NULSTR_FOREACH loop. MEDIUM severity.

dbus-0001: bus_client_policy_optimize() in bus/policy.c iterates R rules and for
each blanket deny/allow calls remove_rules_by_type_up_to() which scans backward
from current position to head — O(R^2) total per new connection creation.
At R=100 rules (realistic system bus): ~10,000 comparisons per connect.
Fix: single O(R) reverse pass tracking last-seen blanket per rule type. MEDIUM.

dbus 5-MOAD summary:
  MOAD-0001: dbus-0001 DEFECT (policy optimize O(R^2))
  MOAD-0002: CLEAN (BusContext is standard daemon context, not a god object)
  MOAD-0003: CLEAN (single-threaded event loop, no thread-local state)
  MOAD-0004: CLEAN (_dbus_verbose is no-op in production builds)
  MOAD-0005: CLEAN (pending_activations hash table coalesces duplicate requests)
2026-04-03 15:53:52 -04:00

173 lines
6.4 KiB
Java

package defects.dbus_0001;
import org.junit.Test;
import static org.junit.Assert.*;
import java.util.*;
/**
* Unit test demonstrating dbus-0001 CWE-407:
* bus_client_policy_optimize() in bus/policy.c uses O(R^2) nested list scan
* to remove shadowed policy rules during connection creation.
*
* For each blanket-deny/allow rule, remove_rules_by_type_up_to() walks
* backward through the rule list, producing O(R^2) total operations.
*
* Fix: single O(R) reverse pass, tracking last blanket per type.
*/
public class TestDbusClientPolicyOptimize {
enum RuleType { SEND, RECEIVE, OWN }
static class PolicyRule {
RuleType type;
boolean isBlanket; // true = no field constraints = blanket deny/allow
PolicyRule(RuleType type, boolean isBlanket) {
this.type = type;
this.isBlanket = isBlanket;
}
}
/**
* Simulates defect: O(R^2) nested scan.
* Returns total comparisons performed.
*/
static long defect_optimize(LinkedList<PolicyRule> rules) {
long comparisons = 0;
ListIterator<PolicyRule> it = rules.listIterator();
while (it.hasNext()) {
PolicyRule rule = it.next();
int pos = it.previousIndex();
if (rule.isBlanket) {
// remove_rules_by_type_up_to: walk from head to current pos
ListIterator<PolicyRule> back = rules.listIterator();
int idx = 0;
while (back.hasNext() && idx < pos) {
PolicyRule r = back.next();
comparisons++; // type comparison
if (r.type == rule.type) {
back.remove();
it = rules.listIterator(pos); // reset iterator after removal
pos--;
}
idx++;
}
}
}
return comparisons;
}
/**
* Simulates fix: O(R) reverse pass.
* Returns total comparisons performed.
*/
static long fix_optimize(LinkedList<PolicyRule> rules) {
long comparisons = 0;
// seen_blanket per type
boolean[] seenBlanket = new boolean[RuleType.values().length];
ListIterator<PolicyRule> it = rules.listIterator(rules.size());
while (it.hasPrevious()) {
PolicyRule rule = it.previous();
comparisons++; // one type check per rule
int typeIdx = rule.type.ordinal();
if (rule.isBlanket) {
seenBlanket[typeIdx] = true;
} else if (seenBlanket[typeIdx]) {
// This rule is shadowed by a later blanket — remove O(1)
it.remove();
}
}
return comparisons;
}
/**
* Build a realistic policy rule list: interleaved specific rules and
* blanket denies of each type.
*/
static LinkedList<PolicyRule> buildRules(int rulesPerType, int blanketsPerType) {
LinkedList<PolicyRule> rules = new LinkedList<>();
for (RuleType type : RuleType.values()) {
for (int i = 0; i < rulesPerType; i++) {
rules.add(new PolicyRule(type, false)); // specific rule
if (i % (rulesPerType / Math.max(blanketsPerType, 1)) == 0) {
rules.add(new PolicyRule(type, true)); // blanket rule
}
}
}
return rules;
}
@Test
public void testRealisticSystemBus() {
// ~100 rules across 3 types, with ~5 blanket rules each
LinkedList<PolicyRule> defect_rules = buildRules(30, 5);
LinkedList<PolicyRule> fix_rules = new LinkedList<>(defect_rules);
long defect_ops = defect_optimize(defect_rules);
long fix_ops = fix_optimize(fix_rules);
System.out.printf("Realistic (R~%d): defect=%,d ops, fix=%,d ops, speedup=%.1fx%n",
defect_rules.size() + fix_rules.size(),
defect_ops, fix_ops, (double) defect_ops / fix_ops);
assertTrue("Fix should be faster than defect", fix_ops <= defect_ops);
assertTrue("Fix should have fewer ops at realistic scale", fix_ops * 5 < defect_ops);
}
@Test
public void testLargeDesktopSessionBus() {
// ~300 rules (GNOME/KDE session bus with many UID-specific rules)
LinkedList<PolicyRule> defect_rules = buildRules(90, 10);
LinkedList<PolicyRule> fix_rules = new LinkedList<>(defect_rules);
long defect_ops = defect_optimize(defect_rules);
long fix_ops = fix_optimize(fix_rules);
double speedup = (double) defect_ops / fix_ops;
System.out.printf("Large desktop (R~%d): defect=%,d ops, fix=%,d ops, speedup=%.1fx%n",
defect_rules.size(),
defect_ops, fix_ops, speedup);
assertTrue("Speedup should be > 10x at R=300", speedup > 10.0);
}
@Test
public void testWorstCaseAllBlankets() {
// Worst case: alternating specific and blanket rules of same type
LinkedList<PolicyRule> defect_rules = new LinkedList<>();
LinkedList<PolicyRule> fix_rules = new LinkedList<>();
for (int i = 0; i < 50; i++) {
PolicyRule specific = new PolicyRule(RuleType.SEND, false);
PolicyRule blanket = new PolicyRule(RuleType.SEND, true);
defect_rules.add(specific); defect_rules.add(blanket);
fix_rules.add(specific); fix_rules.add(blanket);
}
long defect_ops = defect_optimize(defect_rules);
long fix_ops = fix_optimize(fix_rules);
double speedup = (double) defect_ops / Math.max(fix_ops, 1);
System.out.printf("Worst case (R=100, all-blankets): defect=%,d ops, fix=%,d ops, speedup=%.1fx%n",
100, defect_ops, fix_ops, speedup);
assertTrue("Defect must be more expensive in worst case", defect_ops > fix_ops);
}
@Test
public void testSingleConnection() {
// Minimum case: even 1 blanket + 1 specific triggers the defect
LinkedList<PolicyRule> rules = new LinkedList<>();
rules.add(new PolicyRule(RuleType.SEND, false));
rules.add(new PolicyRule(RuleType.SEND, true));
LinkedList<PolicyRule> fix_rules = new LinkedList<>(rules);
long defect_ops = defect_optimize(rules);
long fix_ops = fix_optimize(fix_rules);
// Defect does 1 comparison; fix does 2 checks (same for tiny case)
assertTrue("Both paths complete without error", defect_ops >= 0 && fix_ops >= 0);
}
}