wave17 complete: systemd/emacs/vim/qemu/tcl/kafka-0007/spark-0004 + 559/240
This commit is contained in:
parent
4221966e66
commit
cce7ec653a
32 changed files with 3007 additions and 5 deletions
119
defects/systemd/patch/systemd-0001-strv-extend-dedup-hashset.md
Normal file
119
defects/systemd/patch/systemd-0001-strv-extend-dedup-hashset.md
Normal 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
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue