Uninitialized kernel stack stored into accounting state and disclosed via "get usage all" (ac_limit/ac_bytes/space)
| Field | Value |
|---|---|
| ID | DF-2925 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N |
| CWE | CWE-457 Use of Uninitialized Variable |
| File | sys/kern/vfs_quota.c |
| Lines | 277+280 (primary), 249+263, 293-294, 314-315 |
| Area | kern/vfs |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | kernleak |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
cmd_set_limit() declares uint64_t limit uninitialized, discards prop_dictionary_get_uint64()'s return (which leaves *valp untouched when the key is missing or not a number), and stores the stale stack slot into mp->mnt_acct.ac_limit; cmd_get_usage_all() echoes ac_limit to userspace. Same pattern for space in cmd_set_usage_all and the locals of cmd_set_limit_uid/gid. Unprivileged disclosure of 8 bytes of kernel stack per call. VERIFIED: leaked values track the previous syscall's leftover slot β the disclosed slot is steerable by the caller's prior syscall; a pointer-seeding syscall yields kernel addresses (KASLR-relevant). On the fix-validated kernel (zero-init, included in DF-2922's fix.diff) every run returns 0. Evidence: findings/poc/DF-2925/ (vqleak.c, run.200.log, run.patched.log).
Timeline
- 2026-09-02 Discovered during pass-2 audit of vfs_quota.c (GLM 5.3); leak reproduced 200 runs + fix validated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2925 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| vqleak.c | β | 3.0 KB | view raw | |
| build.sh | β | 42 B | view raw | |
| run.sh | β | 27 B | view raw | |
| run.log | β | 470 B | view raw | |
| run.200.log | β | 396 B | view raw | |
| run.patched.log | β | 162 B | view raw | |
| verdict.json | β | 2.7 KB | view raw | |
| README.md | β | 1.3 KB | β raw |
DF-2925 β uninitialized kernel stack disclosed through quota accounting
cmd_set_limit() (sys/kern/vfs_quota.c:273-284) declares uint64_t limit
uninitialized, discards the return of prop_dictionary_get_uint64()
(which leaves *valp untouched when the key is absent or not a number β
sys/libprop/prop_dictionary_util.c), and stores the stale stack slot into
mp->mnt_acct.ac_limit (:280). cmd_get_usage_all() then echoes
ac_limit to userspace (vfs_quota.c:184). Same pattern for space in
cmd_set_usage_all() (:249 β :255/:263) and for the limit/uid/gid
locals of cmd_set_limit_uid/gid (:293-294, :314-315).
Unprivileged readout of 8 bytes of kernel stack per call. The leaked slot tracks whatever the previous syscall left at that stack depth (observed: the previous vquotactl's loop counters β the value follows the caller's prior syscall activity, i.e. steerable stack disclosure; with a pointer-seeding syscall before vquotactl this leaks kernel addresses).
Build / run
cc -O -o vqleak vqleak.c -lprop ./vqleak /boot 5
Expected (unpatched)
run 0: set=0 get=0 leaked ac_limit=0x0000000000000010 ... run N: leaked ac_limit=<previous syscall's leftover> (varies, nonzero)
On the DF-2922-patched kernel the same command reports ac_limit=0.
Fix verification
fixedOn kernel #1 with the zero-initialization in fix.diff, the same PoC reports ac_limit=0 on every run (was nonzero stack garbage on #0).
VERDICT.md in DF-2922 pack (fix validation section); run on patched kernel: all runs 0x0
Confirmed kernel references
Detail
Evidence (decisive lines)
['run.log β 5-run sample: leaked ac_limit=0x10/0x0/0x3/0x4/0x5 (not attacker-set, varies with prior syscall)', "run.200.log β 200-run sweep showing the slot tracks the previous call's loop counter"]
PoC changes
prop_object_is_array() is not exported to userland β removed from the reply parser.
Verified recommended fix
Zero-initialize limit/space/id before the prop_dictionary_get_* calls (included and validated in DF-2922's fix.diff).
Verdict
Uninitialized kernel stack disclosed to unprivileged users through quota accounting. cmd_set_limit() (vfs_quota.c:273-284) stores the never-initialized uint64_t 'limit' into mp->mnt_acct.ac_limit when the "limit" key is absent (prop_dictionary_get_uint64 leaves *valp untouched on failure, prop_dictionary_util.c), and cmd_get_usage_all() echoes ac_limit to userspace (vfs_quota.c:184). Observed: consecutive unprivileged calls leaked 0x10, 0, 3, 4, 5, ... i.e. the previous syscall's leftover stack slot (the value tracks prior activity = steerable disclosure; same pattern for 'space' at :249/:263 and the locals at :293/:314). On the DF-2922-patched kernel (which zero-initializes these) the same PoC reports 0 across all runs β fix validated on the same build.
No comments yet.