cmd_get_usage_all walks accounting RB trees without ac_spin β RB_NEXT traversal cycle (unkillable kernel spin) and torn 64-bit reads
| Field | Value |
|---|---|
| ID | DF-2924 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:H |
| CWE | CWE-662 / CWE-835 |
| File | sys/kern/vfs_quota.c |
| Lines | 183-215 (walk :187, :202) vs :158-171/:228-268 (mutation) |
| Area | kern/vfs |
| Confidence | likely |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:kern |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
cmd_get_usage_all() iterates ac_uroot/ac_groot and reads ac_bytes/ac_limit with NO lock, while vfs_stdaccount and cmd_set_usage_all insert/rebalance and bzero the same trees under ac_spin. RB_FOREACH/RB_NEXT chase rbe_parent pointers; a concurrent rotation reverses a parent/child edge so the successor walk can revisit nodes β a cycle means the reading thread spins forever in kernel mode (no lock held, never sleeps, ignores SIGKILL). Two unprivileged processes (quota_enabled=1): one spamming "set usage all" with fresh uid sets, one spamming "get usage all" β potential permanent CPU-eating kernel livelock; plus unsynchronized torn counter reads. Not cleanly reproducible on the guest (sustained traffic first trips DF-2926's proplib-limit panic in <60 s; low-rate run did not livelock) β defect directly visible in source, classified likely. Fix: hold ac_spin across the walk (bounded work) or a seqlock for lock-free readers.
Timeline
- 2026-09-02 Discovered during pass-2 audit of vfs_quota.c (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2924 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| vqlivelock.c | β | 3.0 KB | view raw | |
| verdict.json | β | 2.3 KB | view raw | |
| README.md | β | 1.6 KB | β raw |
DF-2924 β cmd_get_usage_all() walks accounting RB trees without ac_spin
Code
sys/kern/vfs_quota.c:187 and :202 β RB_FOREACH over ac_uroot/ac_groot and reads of ac_bytes/ac_limit (:183-184) with NO spinlock, while concurrent writers mutate the same trees under ac_spin: * vfs_stdaccount() vfs_quota.c:158-171 (RB_INSERT + chunk updates) * cmd_set_usage_all() :228-268 (bzero + mass re-insert)
RB_FOREACH/RB_NEXT chase rbe_parent pointers; a concurrent rotation reverses a parent/child edge, so the successor walk can revisit nodes β a traversal CYCLE means the reading thread spins forever in kernel mode (no lock held, never sleeps, unkillable). Additionally all counter reads are unsynchronized torn 64-bit reads (accounting garbage).
Trigger (both sides unprivileged when vfs.quota_enabled=1): writer: "set usage all" with fresh uid sets in a loop (rotations); reader: "get usage all" in a tight loop. vqlivelock.c implements this.
Result on guest
Could not obtain a clean measurement window: the sustained traffic first exhausts the proplib malloc limit via the sys_vquotactl leak and panics the kernel ("prop dictionary: malloc limit exceeded" β see DF-2926) in under 60 s, both at 4096- and 128-entry arrays; a 60 s low-rate run did not livelock. The locking defect is directly visible in the source (walk without lock vs mutation under lock); classification: likely, not reproduced (masked by DF-2926).
Fix
Take ac_spin around cmd_get_usage_all's walk (it already only reads; contention is bounded), or use a seqlock/version counter for lock-free readers.
Fix verification
not_testablenot_testable: no baseline reproduction to compare against.
README.md
Confirmed kernel references
Detail
Evidence (decisive lines)
['README.md result section β DF-2926 panic preempted the measurement both times', 'source citations: unlocked walk at vfs_quota.c:183-215 vs locked mutation at :158-171 and :228-268']
PoC changes
Array size reduced 4096->128 for the second attempt to stay under the proplib limit (still hit it).
Verified recommended fix
Hold ac_spin across cmd_get_usage_all()'s tree walk and counter reads (or a seqlock for readers).
Verdict
cmd_get_usage_all() (vfs_quota.c:187/:202) iterates the accounting RB trees and reads ac_bytes/ac_limit without holding ac_spin while vfs_stdaccount (:158-171) and cmd_set_usage_all (:228-268) mutate/rebalance the same trees under the spinlock. RB_NEXT chases rbe_parent pointers, so a concurrent rotation can make the successor walk revisit nodes => traversal cycle => unkillable kernel-mode spin; plus torn 64-bit counter reads. The locking defect is plain in the source; on the guest every attempt to exercise it was masked by DF-2926 (sustained vquotactl traffic exhausts the proplib malloc limit and panics the kernel within ~60 s, at 4096- and 128-entry arrays alike; a 60 s low-rate run did not livelock). Classified likely / not reproduced (confounder documented).
No comments yet.