nlookupdata leaked on nlookup failure (missing nlookup_done)
Summary
nlookup_init(:351) succeeds, nlookup(&nd)(:354) fails -> return error(:355) without nlookup_done(&nd). nl_path and cached state leaked. User controls path, can make nlookup fail repeatedly -> memory exhaustion. Gated by vfs_quota_enabled.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0143 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df0143_poc.c | trigger-source | vquotactl loop on nonexistent path -> nlookup failure -> nl_path leak | 3.4 KB | view raw |
| build.sh | build-script | cc -o df0143_poc df0143_poc.c -lprop | 99 B | view raw |
| run.sh | run-script | ./df0143_poc /tmp 20000 | 299 B | view raw |
| build.log | build-log | final successful build | 13 B | view raw |
| run.log | run-log | decisive leak run, ENOENT each, 5000 iters | 391 B | view raw |
| leak_sample.txt | leak-sample | nameibufs 17 -> 19500 (19.5MB) before/after | 520 B | view raw |
| env.txt | environment | uname + cc, vfs.quota_enabled=1 | 188 B | view raw |
| fix.diff | suggested-fix | add nlookup_done(&nd) on nlookup error path | 327 B | view raw |
| fix_build.log | build-log | single-fix kernel build (combined vfs_quota.c), rc=0 | 5.6 MB | β download |
| fix_run.log | fix-run-log | patched: nameibufs 16 -> 16 (FLAT), no leak | 215 B | view raw |
| VERDICT.md | verdict | full narrative | 2.3 KB | β raw |
DF-0143 β nlookupdata leaked on nlookup failure (missing nlookup_done)
Verdict: REPRODUCED (unbounded kernel memory leak, DoS)
The missing nlookup_done() on the nlookup() failure path is confirmed live:
each failed vquotactl on a nonexistent path leaks one MAXPATHLEN M_NAMEI
buffer (and a namecache reference). 20000 calls leaked ~19.5 MB. An
unprivileged user controls the path and can repeat indefinitely.
The bug (sys/kern/vfs_quota.c:351-359)
error = nlookup_init(&nd, path, UIO_USERSPACE, 0); /* 351: kmalloc nl_path */
if (error)
return (error);
error = nlookup(&nd); /* 354 */
if (error)
return (error); /* 355-356: BUG β no nlookup_done */
nch = nd.nl_nch;
cache_zero(&nd.nl_nch);
nlookup_done(&nd); /* 359: only on SUCCESS */
nlookup_init (vfs_nlookup.c:131) allocates nd->nl_path from the namei_oc
objcache (backed by M_NAMEI, MAXPATHLEN=1024, vfs_init.c:207) and takes a
namecache reference (cache_copy, :150/160/171). nlookup_done releases both.
On the nlookup() failure path the function returns at line 355 without
nlookup_done, leaking nl_path + the namecache ref.
Evidence (live, unprivileged maxx, vfs.quota_enabled=1)
vquotactl('/tmp/df0143_does_not_exist_<pid>') x 20000 -> errno ENOENT each
BEFORE: nameibufs 17 (0M) vfs.cache.numcache 865
AFTER : nameibufs 19.5K (19.5M) vfs.cache.numcache 886
Per-call leak β 1 nameibuf (1024 B). 20000 Γ 1024 β 19.5 MB of M_NAMEI slab
permanently leaked. The returned errno=ENOENT confirms the call reaches the
nlookup() failure path (line 354) β exactly the buggy return.
Exploit chain
none (resource-leak DoS, no memory-corruption primitive) β the leaked objects
are namei path buffers + namecache refs; they are not attacker-shapeable into a
write/UAF. The realistic impact ceiling is unbounded kernel memory growth β
exhaustion / OOM, triggerable by an unprivileged user (no privilege check in
sys_vquotactl).
Fix
fix.diff adds nlookup_done(&nd) before the error return at line 355.
git apply --check passes.
Fix validation
See Phase 8 β the single-fix kernel shows nameibufs staying flat across the
same 20000-call workload (no leak).
Fix verification
fixedvalidated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (live). nlookup_data leaked on nlookup failure -> 19.5MB nameibufs leak. Unprivileged vquotactl.
No comments yet.