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 rules) { long comparisons = 0; ListIterator 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 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 rules) { long comparisons = 0; // seen_blanket per type boolean[] seenBlanket = new boolean[RuleType.values().length]; ListIterator 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 buildRules(int rulesPerType, int blanketsPerType) { LinkedList 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 defect_rules = buildRules(30, 5); LinkedList 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 defect_rules = buildRules(90, 10); LinkedList 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 defect_rules = new LinkedList<>(); LinkedList 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 rules = new LinkedList<>(); rules.add(new PolicyRule(RuleType.SEND, false)); rules.add(new PolicyRule(RuleType.SEND, true)); LinkedList 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); } }