wave17 complete: systemd/emacs/vim/qemu/tcl/kafka-0007/spark-0004 + 559/240

This commit is contained in:
russell@unturf.com 2026-03-27 20:15:47 -04:00
parent 4221966e66
commit cce7ec653a
32 changed files with 3007 additions and 5 deletions

View file

@ -0,0 +1,119 @@
# systemd-0001: strv_extend_strv filter_duplicates O(N²) — CWE-407
## Severity
HIGH
## Location
`src/basic/strv.c``strv_extend_strv()` and `strv_extend_strv_consume()`
## Root Cause
`strv_contains(t, *s)` is called inside a loop that iterates over every element
of `b`. `strv_contains` expands to `strv_find()`, which is an O(N) linear scan
of the entire target array `t`. As entries are appended to `t`, each successive
membership check scans a longer array. Total cost: O(|b| × |t|) = O(N²).
## Defective Code
```c
// src/basic/strv.c strv_extend_strv()
STRV_FOREACH(s, b) {
if (filter_duplicates && strv_contains(t, *s)) // O(|t|) per iteration
continue;
t[p+i] = strdup(*s);
...
}
// src/basic/strv.c strv_extend_strv_consume()
STRV_FOREACH(s, b) {
if (strv_contains(t, *s)) { // O(|t|) per iteration
free(*s);
continue;
}
t[p+i] = *s;
...
}
```
`strv_contains` is defined in `src/basic/strv.h` as:
```c
#define strv_contains(l, s) (!!strv_find((l), (s)))
```
and `strv_find` is a plain linear scan:
```c
char* strv_find(char * const *l, const char *name) {
STRV_FOREACH(i, l)
if (streq(*i, name))
return *i;
return NULL;
}
```
## Call Chain
- `strv_extend_strv(a, b, /*filter_duplicates=*/true)` — direct callers throughout codebase
- `strv_split_and_extend_full()``strv_extend_strv_consume(t, l, filter_duplicates)` → O(N²) when filter=true
- `strv_split_and_extend()` is a common wrapper used in config parsing
## Complexity
- Before: O(N²) — each of N elements checks against growing array of size ~N
- After: O(N) — track seen strings in a `Set*` (systemd's `set.h`) keyed by string hash
## Fix
```c
int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates) {
size_t p, q, i = 0;
assert(a);
q = strv_length(b);
if (q == 0)
return 0;
p = strv_length(*a);
if (p >= SIZE_MAX - q)
return -ENOMEM;
char **t = reallocarray(*a, GREEDY_ALLOC_ROUND_UP(p + q + 1), sizeof(char *));
if (!t)
return -ENOMEM;
t[p] = NULL;
*a = t;
+ _cleanup_set_free_ Set *seen = NULL;
+ if (filter_duplicates) {
+ /* Pre-populate seen with existing entries */
+ STRV_FOREACH(e, t)
+ if (set_put_strdup(&seen, *e) < 0)
+ goto rollback;
+ }
STRV_FOREACH(s, b) {
- if (filter_duplicates && strv_contains(t, *s))
+ if (filter_duplicates && set_contains(seen, *s))
continue;
+ if (filter_duplicates && set_put_strdup(&seen, *s) < 0)
+ goto rollback;
t[p+i] = strdup(*s);
if (!t[p+i])
goto rollback;
i++;
t[p+i] = NULL;
}
...
}
```
The same pattern applies to `strv_extend_strv_consume()`.
## Speedup
At N=1000 strings with filter_duplicates=true:
- Before: ~500,000 string comparisons
- After: ~1,000 hash lookups
- Ratio: ~500x
## References
- CWE-407: Inefficient Algorithmic Complexity
- systemd `src/basic/set.h``Set*` uses `Hashmap` internally, O(1) average lookup

View file

@ -0,0 +1,103 @@
# systemd-0002: unit_file_get_list states filter O(U×S) — CWE-407
## Severity
MEDIUM
## Location
`src/shared/install.c``unit_file_get_list()`
## Root Cause
Inside `unit_file_get_list()`, the code iterates over all unit files found in
each directory of the unit search path. For each unit file, it calls
`strv_contains(states, unit_file_state_to_string(state))` to check if the
unit's state matches the caller's filter list.
`strv_contains` = `strv_find()` = O(S) linear scan (S = number of states in the
filter). This check is performed once per unit file U, giving O(U × S) total.
In practice, `systemctl list-units --state=STATE1,STATE2,...` can pass S states.
With thousands of units (common on a large system) and S > 1, this degrades
noticeably compared to an O(1) hash lookup.
## Defective Code
```c
// src/shared/install.c unit_file_get_list()
STRV_FOREACH(dirname, lp.search_path) {
...
FOREACH_DIRENT(de, d, return -errno) {
...
UnitFileState state;
r = unit_file_lookup_state(scope, &lp, de->d_name, &state);
if (r < 0)
state = UNIT_FILE_BAD;
if (!strv_isempty(states) &&
!strv_contains(states, unit_file_state_to_string(state))) // O(S) per unit
continue;
...
}
}
```
## Call Chain
- `unit_file_get_list(scope, root_dir, states, patterns, ret)`
- Called by `systemctl list-unit-files` with the `--state=` filter
## Complexity
- Before: O(U × S) — U unit files × S state strings scanned per file
- After: O(U) — one O(1) hash lookup per unit file
## Fix
```c
int unit_file_get_list(
RuntimeScope scope,
const char *root_dir,
char * const *states,
char * const *patterns,
Hashmap **ret) {
_cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_hashmap_free_ Hashmap *h = NULL;
+ _cleanup_set_free_ Set *states_set = NULL;
int r;
...
+ /* Build O(1) lookup set for states filter */
+ if (!strv_isempty(states)) {
+ STRV_FOREACH(s, states) {
+ r = set_put_strdup(&states_set, *s);
+ if (r < 0)
+ return r;
+ }
+ }
STRV_FOREACH(dirname, lp.search_path) {
...
FOREACH_DIRENT(de, d, return -errno) {
...
if (!strv_isempty(states) &&
- !strv_contains(states, unit_file_state_to_string(state)))
+ !set_contains(states_set, unit_file_state_to_string(state)))
continue;
...
}
}
...
}
```
## Speedup
At U=5000 units, S=5 states:
- Before: ~25,000 string comparisons
- After: ~5,000 hash lookups
- Ratio: ~5x (grows linearly with S)
The ratio is modest because S is bounded by the number of valid UnitFileState
values (~10), but the fix is trivially correct and eliminates the linear scan.
## References
- CWE-407: Inefficient Algorithmic Complexity
- `src/basic/set.h` — systemd Set with O(1) lookup