β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0143

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0143 Β· 11 files
FileTypeDescriptionSize
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
VERDICT.md verdict full narrative
↓ download 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

fixed

validated

see evidence pack
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Fri Jul 17 18:57:29 UTC 2026

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.