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)
173 lines
6.4 KiB
Java
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);
|
|
}
|
|
}
|