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)
This commit is contained in:
parent
a89cc53fed
commit
decacb5dbd
8 changed files with 681 additions and 0 deletions
173
defects/dbus-0001/test/TestDbusClientPolicyOptimize.java
Normal file
173
defects/dbus-0001/test/TestDbusClientPolicyOptimize.java
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue