systemd+dnsmasq: 5-MOAD scan; dnsmasq-0001 + systemd-0003 MOAD-0001 defects
dnsmasq: option_filter() O(N²) duplicate elimination in src/dhcp-common.c. Two nested loops over dhcp_opt list called per DHCP reply. Fix: 256-entry bitmap keyed by 1-byte option code. 23/23 PASS, 25-50x measured ratio. MOADs 0002/0004/0005 CLEAN; MOAD-0003 N/A (single-threaded). systemd: dbus-cgroup.c IPIngressFilterPath/IPEgressFilterPath setter calls strv_contains() inside for(;;) loop — O(N²) on D-Bus property writes. Fix: HashSet pre-populated before loop. 17/17 PASS, 250x at N=500. MOADs 0002/0003/0004/0005 CLEAN (single-threaded sd-event, no key logging).
This commit is contained in:
parent
6c6dc2b114
commit
db4e0cc498
6 changed files with 680 additions and 0 deletions
49
defects/systemd/patch/SCAN.md
Normal file
49
defects/systemd/patch/SCAN.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# systemd 5-MOAD scan — 2026-03-31
|
||||
|
||||
Source: https://github.com/systemd/systemd (depth=1)
|
||||
Focus: src/core/, src/resolve/, src/cryptsetup/, src/login/
|
||||
|
||||
Prior defects in this directory:
|
||||
- systemd-0001: strv_extend_strv dedup O(N²) (src/basic/strv.c) — HIGH
|
||||
- systemd-0002: unit_file_get_list states filter O(U×S) (src/shared/install.c) — MEDIUM
|
||||
|
||||
## MOAD-0001 (CWE-407): DEFECT — systemd-0003
|
||||
|
||||
`src/core/dbus-cgroup.c` BPF filter path setter: `strv_contains(*filters, path)`
|
||||
called inside a `for(;;)` loop reading D-Bus array elements for
|
||||
`IPIngressFilterPath=` / `IPEgressFilterPath=` properties. O(N²) total for N
|
||||
paths. See `systemd-0003-dbus-cgroup-bpf-filter-strv-dedup.patch`.
|
||||
|
||||
Same pattern also appears in `src/core/load-fragment.c`
|
||||
`config_parse_ip_filter_bpf_progs()`. Both sites are LOW-MEDIUM severity
|
||||
(config/property-write path, not hot packet path).
|
||||
|
||||
Other candidates reviewed and found LOW severity or too small in practice:
|
||||
- `src/core/device.c` LIST_FOREACH + strv_contains(aliases, d->path): called
|
||||
on udev hotplug events. aliases list is small (udev SYSTEMD_ALIAS values).
|
||||
- `src/core/transaction.c` nested LIST_FOREACH: not a membership test, just
|
||||
pairwise conflict checking over job sets.
|
||||
|
||||
## MOAD-0002 (Intertangle): CLEAN (architectural note)
|
||||
|
||||
The `Manager` struct (`src/core/manager.h`, 672 lines) is a god object holding
|
||||
all systemd state. This is intentional and well-known. The subsystems (device,
|
||||
mount, swap, service, etc.) all reference it. No novel coupling to patch.
|
||||
Marked CLEAN.
|
||||
|
||||
## MOAD-0003 (Leaked Context): CLEAN
|
||||
|
||||
systemd is a single-threaded sd-event loop. No `thread_local`, `__thread`, or
|
||||
`pthread_getspecific` found in `src/core/` or `src/resolve/`. CLEAN.
|
||||
|
||||
## MOAD-0004 (CWE-312 Logged Secret): CLEAN
|
||||
|
||||
Reviewed `src/cryptsetup/cryptsetup.c` for LUKS passphrase, volume key, and
|
||||
TPM key material in log calls. Found only device names and error messages in
|
||||
`log_debug`/`log_info`/`log_warning` — no passphrase values, no key bytes,
|
||||
no token secrets. CLEAN.
|
||||
|
||||
## MOAD-0005 (Thundering Herd): CLEAN
|
||||
|
||||
systemd is a single-threaded sd-event loop. No concurrent hashmap/set access.
|
||||
CLEAN.
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
# systemd-0003: dbus-cgroup IPIngressFilterPath/IPEgressFilterPath dedup O(N²) — CWE-407
|
||||
|
||||
## Severity
|
||||
LOW-MEDIUM
|
||||
|
||||
## Location
|
||||
`src/core/dbus-cgroup.c` — BPF filter path setter block (~line 578)
|
||||
|
||||
## Root Cause
|
||||
When systemd accepts an `IPIngressFilterPath=` or `IPEgressFilterPath=` array
|
||||
via D-Bus (e.g., `systemctl set-property SomeUnit.service IPIngressFilterPath=…`),
|
||||
it reads each path string from the bus message in a `for (;;)` loop and calls
|
||||
`strv_contains(*filters, path)` before appending to the `*filters` strv.
|
||||
|
||||
`strv_contains` expands to `strv_find()`, an O(N) linear scan of the entire
|
||||
target array. Each successive path added to the array causes a longer scan of
|
||||
all previously accepted paths. For N paths in the incoming message, total cost
|
||||
is O(N²) string comparisons.
|
||||
|
||||
```c
|
||||
/* src/core/dbus-cgroup.c */
|
||||
for (;;) {
|
||||
const char *path;
|
||||
r = sd_bus_message_read(message, "s", &path);
|
||||
...
|
||||
if (!UNIT_WRITE_FLAGS_NOOP(flags) && !strv_contains(*filters, path)) {
|
||||
r = strv_extend(filters, path); /* O(N) scan before each append */
|
||||
...
|
||||
}
|
||||
n++;
|
||||
}
|
||||
```
|
||||
|
||||
In practice, kernel BPF program lists per cgroup are short (typically < 10
|
||||
entries), so the quadratic cost is low in normal operation. However, a
|
||||
misbehaving or malicious D-Bus client can send a large array, triggering
|
||||
pathological O(N²) CPU usage in PID 1 (systemd itself) during property
|
||||
handling.
|
||||
|
||||
## Fix
|
||||
|
||||
Build a `Set*` (hashmap-backed, O(1) lookup) from the already-accepted paths
|
||||
before the loop, then use `set_contains()` instead of `strv_contains()`:
|
||||
|
||||
```c
|
||||
_cleanup_set_free_ Set *seen = NULL;
|
||||
/* Pre-populate with existing filter paths */
|
||||
STRV_FOREACH(e, *filters)
|
||||
if (set_put_strdup(&seen, *e) < 0)
|
||||
return log_oom();
|
||||
|
||||
for (;;) {
|
||||
const char *path;
|
||||
r = sd_bus_message_read(message, "s", &path);
|
||||
...
|
||||
if (!UNIT_WRITE_FLAGS_NOOP(flags) && !set_contains(seen, path)) {
|
||||
r = set_put_strdup(&seen, path);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
r = strv_extend(filters, path);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
n++;
|
||||
}
|
||||
```
|
||||
|
||||
The same `strv_contains` pattern appears in `src/core/load-fragment.c` inside
|
||||
`config_parse_ip_filter_bpf_progs()` which builds BPF prog path lists from
|
||||
unit files. That site has the same O(N²) characteristic and should apply the
|
||||
same fix.
|
||||
|
||||
## Speedup
|
||||
At N=500 duplicate filter paths sent via D-Bus:
|
||||
- Before: ~125,000 string comparisons
|
||||
- After: ~500 hash lookups
|
||||
- Ratio: ~250x
|
||||
|
||||
At typical sizes (N < 10) the difference is immaterial. The fix matters for
|
||||
adversarial or automated D-Bus clients.
|
||||
|
||||
## References
|
||||
- CWE-407: Inefficient Algorithmic Complexity
|
||||
- `src/core/dbus-cgroup.c` STR_IN_SET block for `IPIngressFilterPath`
|
||||
- `src/core/load-fragment.c` `config_parse_ip_filter_bpf_progs()`
|
||||
- `src/basic/strv.h` `strv_contains` macro → `strv_find()` O(N)
|
||||
- `src/basic/set.h` `Set*` backed by hashmap, O(1) average
|
||||
Loading…
Add table
Add a link
Reference in a new issue