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

Namecache-reference leak on failed jail(2): pr_root handle never dropped on kern_jail error paths (deterministic via jail path = regular file)

Field Value
ID DF-2801
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L
CWE CWE-401 Missing Release of Memory
File sys/kern/kern_jail.c
Lines 206 (cache_copy), 211-244 (error paths), 342-351 (sys_jail out)
Area kern
Confidence certain
Discovered 2026-08-31
Pass 2 (GLM 5.3 second pass)
Bucket base:kern
Reported pending
Known CVE none
CVE match novel

Summary

kern_jail() cache_copy()s the jail path's namecache handle into pr->pr_root; every error path after that (assign_prison_id ERANGE, prison_sysctl_create failure, kern_jail_attach failure) cleans varsyms/nlookup but never drops it, and sys_jail kfrees pr with the reference live. Deterministic trigger: jail path = a regular file (nlookup never requires a directory; kern_chroot then fails ENOTDIR). Measured with pressure/settle floors: 2000 failed calls permanently pin ~1538 namecache entries (control βˆ’104). Root-gated privileged exhaustion primitive; patched kernel +118 vs βˆ’91 (noise).

NULL-guarded cache_drop on every error path and in sys_jail's out (cache_drop is not NULL-safe) β€” findings/poc/DF-2801/fix.diff.

Timeline

  • 2026-08-31 Discovered during pass-2 audit of kern_jail.c (GLM 5.3); leak quantified + fix validated same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2801 Β· 9 files
FileTypeDescriptionSize
jailleak.c β€” 3.3 KB view raw
run.sh β€” 1.7 KB view raw
run.log β€” 474 B view raw
run.2.log β€” 509 B view raw
run.3.log β€” 553 B view raw
run.patched.log β€” 352 B view raw
env.txt β€” 373 B view raw
fix.diff β€” 5.1 KB view raw
verdict.json β€” 3.6 KB view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched kernel: identical trigger (2000 ENOTDIR failures) pins nothing β€” leak-cycle floor growth +118 vs control -91 (noise), against +1538 on stock. Permanent pinning eliminated.

findings/poc/DF-2801/run.patched.log; fix.diff (kern_jail.c cache_drop hunks)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Tue Sep 1 13:39:40 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

host root: loop { jail(path=) } β€” each failing call leaks one namecache reference forever; unbounded kernel memory growth. No unprivileged route (jail(2) is capability-gated and denied in jails).

Evidence (decisive lines)

['findings/poc/DF-2801/run.3.log β€” decisive stock run: F0 6476 / control -104 / leak +1538, ENOTDIR=2000, REPRODUCED line', 'findings/poc/DF-2801/run.patched.log β€” fixed: control -91, leak +118 (noise), ENOTDIR=2000 same path']

PoC changes

two measurement fixes: (1) vfs.cache.numcache is an INT sysctl β€” first version parsed it as a string and read 0; (2) baseline must be compared as settled floors with reclaim pressure between phases β€” instantaneous deltas are confounded by lazy hammer2 reclaim (first design took the baseline after file creation and showed delta 0).

Verified recommended fix

Add NULL-guarded cache_drop(&pr->pr_root) to every kern_jail error return after cache_copy and to sys_jail's out path before kfree(pr).

Verdict

REPRODUCED: failed jail(2) calls permanently pin namecache entries. kern_jail() cache_copy()s the jail path handle into pr->pr_root (kern_jail.c:206) and no error path after that drops it (assign_prison_id :211-216, prison_sysctl_create, kern_jail_attach/out2 :227-244; sys_jail kfrees pr at :342-351 with the reference live). Trigger: jail path = regular file β€” nlookup succeeds, cache_copy runs, kern_chroot/checkvp_chdir fails ENOTDIR (vfs_syscalls.c:2058). Measured with a floor-comparison design (2000 files per cycle, 8000-file pressure churn + settle between phases to defeat lazy hammer2 reclaim): stock kernel control cycle -104 vs leak cycle +1538 permanently-pinned entries for 2000 calls (all 2000 failing ENOTDIR, i.e. exactly the traced path). Patched kernel (cache_drop on every error path, NULL-guarded): leak growth collapses to +118, same order as control noise, with the identical ENOTDIR failure count. Root-gated (jail(2) needs SYSCAP_NOJAIL_CREATE) so this is a privileged-user unbounded-kernel-memory-exhaustion primitive, not an unpriv escalation β€” rated Low severity, impact ceiling DoS.