Unguarded NULL nc_parent dereference in nlookup dotdot walk on topology-disconnected cwd - unprivileged reliable local DoS
- File:
sys/kern/vfs_nlookup.c:756(deref fallout at :767) - Severity: Medium (unpriv reliable kernel panic)
- CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
- CWE: CWE-476 NULL Pointer Dereference
- Confidence: certain (3/3 panics β€90s, crash-dump root-caused)
- Found in pass 2 (GLM 5.3) β discovered while verifying DF-2717
Summary
In the nlookup ".." traversal, nctmp.ncp = nctmp.ncp->nc_parent
(vfs_nlookup.c:756) runs without a NULL guard, and the resulting handle is
dereferenced at :767 (NULL+0x84). A current working directory whose
namecache node has been topology-disconnected (nc_parent=0, unhashed β
produced by rmdir(cwd) deferred-zap completing under churn while fd_ncdir
holds the last reference) feeds a NULL straight through. The intended
NULL-parent handling at :831 runs after the deref.
Reproduction (from findings/poc/DF-2717/, storm bit-mix 0xf)
Unprivileged pthread storm: fchdir swapper between tmpfs/hammer2 mounts +
volatile mkdir/chdir/rmdir/chdir-away "last-ref-drop dance" + relative-path
open readers. Stock INVARIANTS kernel panicked 3/3 within 90 seconds:
nlookup+0x667 Fatal trap 12 fault at NULL+0x84; kgdb on vmcore.0 shows
nch.ncp=0x0, cwd ncp with nc_parent=0, nc_head=0, nc_name=0, nc_flag=0x1210,
nc_refs=30 β the disconnected-node signature. Single-threaded
rmdir(cwd)+ls .. is benign (fd ref blocks the zap); churn is required.
Threat
Any local user; reliable kernel panic (whole-system DoS). No memory-safety primitive beyond the NULL fixed-offset read.
Recommended fix (validated A/B on rebuilt kernel β see DF-2717 pack fix.diff)
NULL-guard the parent walk: if nctmp.ncp == NULL after the assignment,
recover exactly like the existing jail-break case (nctmp = nd->nl_rootnch).
Patched kernel survived 2Γ150s of the exact storm that killed stock 3/3.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5846 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df2717_storm.c | β | 6.4 KB | view raw | |
| panic-stock.txt | β | 845 B | view raw | |
| run-stock.log | β | 992 B | view raw | |
| run-fixed.log | β | 1.9 KB | view raw | |
| run-fixed3-console.txt | β | 3.7 KB | view raw | |
| panic-fixedrun-hammer2-incidental.txt | β | 893 B | view raw | |
| stock-hammer2-attribution-run.txt | β | 758 B | view raw | |
| fix.diff | β | 967 B | view raw | |
| env.txt | β | 722 B | view raw | |
| VERDICT.md | β | 5.0 KB | β raw |
DF-5846 β VERDICT
Status: REPRODUCED (unprivileged kernel panic, stock INVARIANTS kernel, fresh run 2026-10-26; 3/3 historical in the DF-2717 pack with crash-dump root-cause). Impact: local DoS (panic) only β no escalation primitive (fixed-offset NULL read).
1. Fresh stock reproduction (this run)
- Guest: DragonFly 6.5-DEVELOPMENT
#0: Thu Jul 2 06:02:54 UTC 2026(stock snapshot, INVARIANTS), 6 vCPU QEMU/KVM. - Trigger:
df2717_storm 90 3 0xfas unprivilegedmaxx(uid 1001) β pthread storm: fchdir swapper between tmpfs/tmp/df2717/aand hammer2/home/maxx/df2717/b, relative".."chdir swapper across the mount boundary, volatile mkdir/chdir/rmdir/chdir-away dance on 64 rotating directories, and 3 threads of relative-path open() readers. - Result (
panic-stock.txt): within the 90 s window βFatal trap 12: page fault,fault virtual address = 0x84,Stopped at nlookup+0x66c: movl 0x84(%rax),%edx,current process = 904(df5846_storm, uid 1001), CPU3. Signature byte-for-byte identical to the DF-2717-era captures (nlookup+0x66c, VA 0x84, kern_chdir caller).
2. Root cause (source-verified, path:line)
rmdir(cwd)defers the namecache zap (NCF_DEFEREDZAP) because the fd-table reference is held (sys/kern/vfs_cache.c). Under chdir churn the deferred zap completes whilefd_ncdirstill points at the node: it becomes topology-disconnected βnc_parent = NULL, unhashed, unnamed, but alive and referenced. Proven by kgdb on /var/crash/vmcore.0 in the DF-2717 run: cwd ncp hadnc_parent=0, nc_head=0, nc_name=0, nc_flag=0x1210, nc_refs=30, path"../../../tmp/df2717/a", element"..".- In
nlookup()'s".."handler the NULL-parent case is handled ONLY for the mount-point walk (sys/kern/vfs_nlookup.c:741-756,mnt_ncmounton.ncp == NULLβ "broke jail" recovery). The normal parent stepsys/kern/vfs_nlookup.c:758(nctmp.ncp = nctmp.ncp->nc_parent) has no NULL guard, even though the intended generic NULL-parent handling exists further down (:831+). - The resulting
nctmp.ncp = NULLsurvivescache_copy()(which is NULL-safe,sys/kern/vfs_cache.c:996-1026) intonch, andnch.ncp->nc_generation & ~3atsys/kern/vfs_nlookup.c:767dereferences NULL+0x84 β the observed trap.
Single-threaded rmdir(cwd); ls .. is benign (the fd reference blocks
the zap); the multi-actor storm is what completes the deferred zap while
walkers keep adopting the disconnected node as their walk base.
3. Fix and A/B validation
fix.diff = the NULL guard (Part B of the DF-2717 pack's validated fix;
the FD-spinlock hardening in that pack belongs to DF-2717 and is NOT
needed for this crash):
} else {
nctmp.ncp = nctmp.ncp->nc_parent;
}
+ if (nctmp.ncp == NULL) {
+ kprintf("vfs_nlookup: '..' traverse disconnected: ...");
+ nctmp = nd->nl_rootnch;
+ }
- Validation kernel: tri-fixed build
#2: Mon Oct 26 06:04:19 UTC 2026(vfs_nlookup NULL guard + DF-5847 nd6 fix + DF-5848 m_dup_data fix; the two others are in disjoint subsystems), RC=0 under -Werror. - A/B (the exact full-mix storm
90/150 3 0xf): stock baseline = 4/4 nlookup NULL-parent panics (3 historical + 1 fresh, all β€90 s). On the fixed kernel the guard fired 2Γ then 46Γ across two runs β every firing is a walk that is an instant panic on stock β with zero nlookup faults. The DF-5846 crash is gone. - Honest caveat / separate bug: the storm harness has a SECOND kill
path β
hammer2_xop_start_except+0x25(NULL+0x81554 read) β that eventually takes down BOTH stock and fixed kernels after the DF-5846 crash would already have fired. Attribution: on the FIXED kernel with mode 0xd (no cross-mount".."walks) the guard fired 0 times and the hammer2 crash still occurred; on PRISTINE STOCK with mode 0xd (which has no guard at all) the identical crash occurred. The hammer2 panic is therefore pre-existing and independent of this fix β it is a new candidate finding (transcripts: run-fixed3-console.txt, panic-fixedrun-hammer2-incidental.txt, stock-hammer2-attribution-run.txt). - The
'..' traverse disconnectedrecovery message firing 46Γ in one run confirms the guard handles the exact stock crash condition at volume without adverse effects (no leak, no wrong behavior).
4. Impact ceiling / privesc assessment
The primitive is a kernel read at a fixed NULL offset (0x84) β a trap, not a corruption. No attacker-controlled value reaches memory; there is no write, no leak, no lifetime hijack. No path to uid=0. Ceiling = reliable unprivileged local DoS, exactly as filed (Medium, CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H).
5. Notes
- The DF-2717 unlocked-read sites are the delivery vehicle that makes walkers adopt the disconnected base quickly, but a correctly-locked reader would copy the same legitimate handle β the missing NULL guard is the root cause and the fix stands alone.
- Guest returned to a clean snapshot after validation.
Fix verification
fixedStock baseline: 4/4 nlookup NULL-parent panics (3 historical DF-2717 + 1 fresh this run), all within 90s. Patched kernel (tri-fix build): the guard fired 2x then 46x across two full-mix runs with zero nlookup faults - the DF-5846 crash is gone. Honest caveat: the storm harness has a SECOND, independent kill path (hammer2_xop_start_except NULL+0x81554 read) that still takes down both stock and patched kernels after the DF-5846 crash would have fired; attribution runs on the patched kernel (mode 0xd, zero guard firings) and on pristine stock (mode 0xd) prove it is pre-existing and unrelated - it is a new candidate finding, not a fix failure. DF-5846-specific behavior eliminated.
run-fixed.log (both fixed-kernel storm runs + the two attribution runs); run-fixed3-console.txt (46 guard messages, crash at hammer2 site not nlookup); panic-stock.txt (baseline); fix.diff
Confirmed kernel references
Detail
Exploit chain
none - fixed-offset kernel read at NULL+0x84 (nc_generation offset); no write, no leak, no lifetime hijack; impact ceiling is reliable unprivileged local DoS
Evidence (decisive lines)
panic-stock.txt (fresh stock panic, nlookup+0x66c VA 0x84, process df5846_storm); run-fixed.log + run-fixed3-console.txt (46 guard recoveries, no nlookup fault on fixed kernel); stock-hammer2-attribution-run.txt (hammer2 crash pre-exists on stock, guard-independent); fix.diff
PoC changes
trigger used verbatim from the DF-2717 pack (df2717_storm.c); no code changes needed; watchdog-wrapped 90/150/300s budgets; fix.diff extracted standalone (part B of the DF-2717 validated fix)
Verified recommended fix
NULL-guard the dotdot parent walk: recover with nctmp = nd->nl_rootnch when nctmp.ncp == NULL after the nc_parent assignment (see fix.diff)
Verdict
Fresh stock-kernel reproduction of the unprivileged nlookup dotdot NULL-parent panic (Fatal trap 12, VA 0x84, nlookup+0x66c from uid-1001 storm process, inside the 90s window; identical to the 3/3 DF-2717-era panics with kgdb root-cause). Fix validated by A/B on the rebuilt kernel: the NULL guard fired 2x then 46x across two full-mix runs with ZERO nlookup faults - every firing is an instant panic on stock. The storm can still kill the guest afterwards at a DIFFERENT site (hammer2_xop_start_except, NULL+0x81554); attribution runs prove that hammer2 crash is pre-existing on pristine stock (mode 0xd, guard never fires) and independent of this fix - flagged as a separate candidate finding. No escalation primitive: fixed-offset NULL read only.
No comments yet.