java-topology/defects/systemd-0004/TICKET.md
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

2.4 KiB

systemd-0004: seccomp-util.c strv_contains in KNOWN syscall scan O(K*A)

MOAD: MOAD-0001 (CWE-407) Severity: MEDIUM Speedup: ~150x at K=537, A=300 (per architecture per service start) Language: C File: src/shared/seccomp-util.c:1195-1210

Description

seccomp_load_syscall_filter_set() builds a char **added string vector (strv) containing every syscall name that was already handled by the requested filter set. After processing the filter set it iterates over all ~537 syscalls in the @known set and, for each one, calls strv_contains(added, name) to check whether that syscall was already covered:

SECCOMP_FOREACH_LOCAL_ARCH(arch) {
    _cleanup_strv_free_ char **added = NULL;

    r = add_syscall_filter_set(seccomp, set, action, NULL, log_missing, &added);
    ...
    NULSTR_FOREACH(name, syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].value) {
        ...
        if (strv_contains(added, name))   /* O(|added|) linear scan */
            continue;
        ...
    }
}

strv_contains() performs a linear scan. The added strv can hold 200-400 entries depending on which filter set is loaded. On x86_64 systemd iterates 3 architectures (x86, x32, x86_64). Total comparisons:

3 arches * 537 KNOWN * ~300 added = ~484,000 string comparisons per service start

The sibling function seccomp_load_syscall_filter_set_raw() (line 1246) already uses hashmap_contains(filter, ...) for O(1) lookup, confirming that O(1) lookup is the intended pattern. This function was never updated to match.

Every systemd service unit that uses SystemCallFilter= triggers this path on service activation. Container environments starting many services simultaneously amplify the impact.

Fix

Before the NULSTR_FOREACH loop, build a Set *added_set from added strv entries. Use set_contains(added_set, name) instead of strv_contains(added, name). This mirrors the hashmap_contains pattern already used in the raw variant.

/* Build O(1) lookup set from added strv */
_cleanup_set_free_ Set *added_set = NULL;
STRV_FOREACH(n, added) {
    r = set_put_strdup(&added_set, *n);
    if (r < 0)
        return log_oom();
}

NULSTR_FOREACH(name, syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].value) {
    ...
    if (set_contains(added_set, name))   /* O(1) */
        continue;
    ...
}

Complexity drops from O(K * A) to O(K + A) per architecture.