nfssvc(NFSSVC_AUTHIN) resume of an ENEEDAUTH'd nfsd re-enters the reply loop with nd == NULL β deterministic kernel NULL-deref panic (:671), plus nfsd_nd leak/dangle
Summary
When an AUTH_KERB/FULLNAME request sets NFSD_NEEDAUTH, nfssvc_nfsd() returns ENEEDAUTH (:623) with the request parked in nfsd->nfsd_nd and NFSD_REQINPROG set. The protocol's next step - nfssvc(NFSSVC_AUTHIN|NFSSVC_NFSD, nsd) - re-enters nfssvc_nfsd() whose local nd is initialized NULL (:448); the NFSD_REQINPROG else-branch (:549-570) never reloads nfsd->nfsd_nd, the 'if (nd)' auth/cache block (:599) is skipped, and the reply do-loop dereferences nd->nd_procnum at :671 with writes_todo==0 -> NULL+0x70 read -> Fatal trap 12. Nothing in the kernel ever reloads or (except via dorec overwrite) frees nfsd_nd: abandoned requests leak the nfsrv_descript + request mbuf chain, and :760's kfree leaves nfsd_nd dangling. Direct trigger needs root (nfssvc SYSCAP_RESTRICTEDROOT) running a daemon implementing the 4.4BSD kerb nfsd protocol (base nfsd(8) cannot trigger: zeroed nsd_authstr -> copyouts fail -> RC_DROPIT). Remote amplification: any NFS client sends one AUTH_KERB FULLNAME request to a server whose (non-base) daemon implements the protocol -> the mandated resume panics the host. VERIFIED stock INVARIANTS from fresh reset (kerbd.c harness, 1st attempt deterministic): 'Fatal trap 12 ... fault virtual address = 0x70 ... Stopped at sys_nfssvc+0x3d4'. Fix validated by full in-guest rebuild (reload nd on resume + NULL nfsd_nd at kfree sites): patched kernel re-dispatches the parked request and keeps serving.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3087 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| VERDICT.md | β | 4.3 KB | β raw | |
| kerbd.c | β | 4.7 KB | view raw | |
| fix.diff | β | 893 B | view raw | |
| run.sh | β | 242 B | view raw | |
| run.log | β | 419 B | view raw | |
| run.fixed.log | β | 590 B | view raw | |
| panic.txt | β | 744 B | view raw | |
| env.txt | β | 402 B | view raw | |
| verdict.json | β | 4.9 KB | view raw | |
| manifest.json | β | 1016 B | view raw |
DF-3087 β nfssvc(NFSSVC_AUTHIN) resume of an ENEEDAUTH'd nfsd enters the reply loop with nd == NULL β kernel NULL-deref panic (nfs_syscalls.c:671)
Threat / trigger
nfssvc(2) is root-only (caps_priv_check_td(SYSCAP_RESTRICTEDROOT)).
An AUTH_KERB/FULLNAME NFS request drives the kernel nfsd loop out to
userland with ENEEDAUTH, keeping the in-progress request in
nfsd->nfsd_nd with NFSD_REQINPROG set (sys_nfssvc returns at
nfs_syscalls.c:623 without freeing nd). The documented daemon protocol
(4.4BSD kerb NFS) then calls nfssvc(NFSSVC_AUTHIN|NFSSVC_NFSD, &nsd)
with the same nsd. nfssvc_nfsd() re-enters with a fresh local
nd = NULL (:448), takes the NFSD_REQINPROG else-branch (:549) which
never reloads nfsd->nfsd_nd, skips the if (nd) cache/auth block
(:599), and the reply do-loop evaluates nd->nd_procnum at :671 with
writes_todo == 0 β deterministic NULL-deref.
Base-system nfsd(8) never triggers it (its zeroed nsd makes the
:616-620 copyouts fail β RC_DROPIT), so exploitation requires a daemon
implementing the kerb AUTHIN protocol (root) β severity Low. A remote
amplification exists only against such a (non-base) daemon.
Build (guest, root)
cc -O -o /root/kerbd kerbd.c # needs <vfs/nfs/*.h> from base
Run (guest, root; stock nfsd(8) must not be running)
killall nfsd 2>/dev/null; sleep 1 /root/kerbd 20497
Expected (vulnerable kernel)
kerbd: nfssvc(NFSD) returned -1 errno=81 (Need authenticator) kerbd: ENEEDAUTH ok (nfsd=0x...), resuming with NFSSVC_AUTHIN -> guest dies: Fatal user address access from kernel mode from kerbd at ... Fatal trap 12: page fault while in kernel mode fault virtual address = 0x70 <- offsetof(nd_procnum) Stopped at sys_nfssvc+0x3d4: movl 0x70(%rdi),%eax
Expected (fixed kernel, fix.diff applied)
Resume completes inside the kernel (request re-dispatched, error reply
sent to the still-open connection); kerbd prints
nfssvc(AUTHIN|NFSD) returned ... NO PANIC; guest stays up.
Verified on
DragonFly 6.5-DEVELOPMENT #0 (stock, fresh vm.sh reset with-src) β
panic reproduced, 1st attempt, deterministic; and on an instrumented
INVARIANTS build (same fault VA 0x70, sys_nfssvc+0x3e4).
DF-3087 β VERDICT: reproduced (deterministic kernel panic), fix validated
Bottom line
nfssvc(NFSSVC_AUTHIN|NFSSVC_NFSD) β the documented resume step of the
kerberos nfsd protocol β deterministically panics the DragonFly kernel
with a NULL-deref inside sys_nfssvc/nfssvc_nfsd at
sys/vfs/nfs/nfs_syscalls.c:671. Reproduced on the stock INVARIANTS
kernel #0 from a fresh vm.sh reset with-src, first clean attempt;
fix validated by full kernel rebuild (panic gone, guest serves on).
Reproduction narrative (stock kernel, 2026-09-06)
- Fresh
vm.sh reset with-srcβ DragonFly 6.5-DEVELOPMENT #0 (Thu Jul 2 06:02:54 UTC 2026), X86_64_GENERIC (INVARIANTS), 6 vCPU. killall nfsdβ the guest image auto-starts stock nfsd(8) whose waiting workers steal everynfsrv_wakenfsdassignment; without this step the PoC's own nfsd thread never gets the socket (this was the only reason the first two attempts "hung" instead of crashing)./root/kerbd 20497: - forks a sender that connects and writes one 80-byte record-marked AUTH_KERB/RPCAKN_FULLNAME NFSv3 GETATTR request, holding the connection open; - parent accepts,nfssvc(NFSSVC_ADDSOCK), thennfssvc(NFSSVC_NFSD, &nsd)with pre-allocated auth/verf buffers; -nfs_getreq()takes the FULLNAME path (nfs_socket.c:2392-2434), fillsnfsd_authstr/nfsd_verfstr, setsNFSD_NEEDAUTH; -nfssvc_nfsd():609-623: the three copyouts succeed (buffers valid) β returns ENEEDAUTH (errno 81) with the request parked innfsd->nfsd_nd,NFSD_REQINPROGset,nfsd->nfsd_slpheld; - daemon prints the pointers and immediately callsnfssvc(NFSSVC_AUTHIN|NFSSVC_NFSD, &nsd); -sys_nfssvc:215-297 runs the AUTHIN credential insert (works,nfsd->nfsd_slpstill valid,nfsd_ndstill set), thennfssvc_nfsd():443 re-enters with localnd = NULL(:448), takes the NFSD_REQINPROG else-branch (:549-570) which never reloadsnfsd->nfsd_nd, falls past theif (nd)block (:599), and the reply do-loop evaluatesnd->nd_procnum == NFSPROC_WRITEat :671 withwrites_todo == 0(init :457) βmovl 0x70(%rdi),%eaxwith rdi=NULL.- Serial console:
Fatal user address access from kernel mode from kerbd at ffffffff80807cc4 Fatal trap 12: page fault while in kernel mode fault virtual address = 0x70 <- offsetof(struct nfsrv_descript, nd_procnum) Stopped at sys_nfssvc+0x3d4: movl 0x70(%rdi),%eax current process = 872 (kerbd)Guest dead (vm.sh statusβ down). Identical fault on the instrumented build (#3) at sys_nfssvc+0x3e4, same VA 0x70.
Why severity is Low
nfssvc(2)requiresSYSCAP_RESTRICTEDROOT(uid 0 outside jail) β the direct trigger is a privileged local user.- Base-system
nfsd(8)(sbin/nfsd/nfsd.c:826-841) passes a zeroednsd: at :616-620copyout(..., nsd.nsd_authstr=NULL, ...)fails βcacherep = RC_DROPITβ the request is dropped, ENEEDAUTH never escapes, no base-system crash path. - Remote amplification exists only against a (nonexistent in base) daemon implementing the kerb AUTHIN protocol: a remote client sends one AUTH_KERB FULLNAME request and the daemon's protocol-mandated resume panics the host.
- Also two adjacent lifetime defects in the same dead code, fixed by
the same patch:
nfsd->nfsd_ndis never freed if the daemon abandons the ENEEDAUTH'd request (permanent leak of nfsrv_descript + request mbufs), and :760'skfree(nd)leavesnfsd_nddangling until the nextnfsrv_dorecoverwrites it.
Fix validation (full cycle)
- Baseline: stock #0 β panic (run.log, panic.txt).
vm.sh reset with-src;cd /usr/src && patch -p1 < fix.diff(reloadnd = nfsd->nfsd_ndin the :549 else-branch; clearnfsd->nfsd_ndat both nd-kfree sites);make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel; reboot into #1 (Sun Sep 6 04:53:45 UTC 2026).- Same PoC: ENEEDAUTH returns identically; AUTHIN resume re-dispatches the parked request inside the kernel (error reply for the fh-less GETATTR goes to the still-open connection) and the nfsd keeps serving until the 40 s timeout kills it β no panic, guest up (run.fixed.log).
Impact summary
Class: kernel NULL-pointer read β fatal trap (local privileged DoS); no write primitive, no escalation. CWE-476.
Fix verification
fixedvm.sh reset with-src (stock #0) -> kerbd panics the guest (run.log, panic.txt). Same tree + fix.diff (patch -p1) -> make nativekernel+installkernel -> reboot -> identical PoC: ENEEDAUTH returned, AUTHIN resume re-dispatches the parked request and the nfsd keeps serving until the 40s timeout; guest up (run.fixed.log). Bad behavior gone.
findings/poc/DF-3087/run.fixed.log; findings/poc/DF-3087/fix.diff
Confirmed kernel references
Detail
Exploit chain
root runs kerbd (or any daemon implementing the 4.4BSD kerb ENEEDAUTH/AUTHIN nfsd protocol) -> remote NFS client (or local sender) submits one AUTH_KERB/RPCAKN_FULLNAME NFSv3 request -> kernel parks it and returns ENEEDAUTH -> daemon calls nfssvc(NFSSVC_AUTHIN|NFSSVC_NFSD, nsd) -> NULL-deref at nfs_syscalls.c:671 -> Fatal trap 12, all CPUs stopped, guest down. No privilege escalation: read-fault on NULL+offset (0x70), panic-only.
Evidence (decisive lines)
["findings/poc/DF-3087/panic.txt -- 'Fatal trap 12 ... fault virtual address = 0x70 ... Stopped at sys_nfssvc+0x3d4: movl 0x70(%rdi),%eax', current process = kerbd (stock kernel #0, fresh reset)", 'findings/poc/DF-3087/run.log -- stock-kernel run: ENEEDAUTH (errno 81) returned to userland, AUTHIN resume, guest down', 'findings/poc/DF-3087/run.fixed.log -- kernel #1 with fix.diff: same ENEEDAUTH flow, resume completes, RC=124 (timeout, nfsd keeps serving), guest up', 'findings/poc/DF-3087/kerbd.c -- self-contained trigger (daemon + kerb request sender)', 'findings/poc/DF-3087/fix.diff -- one-line reload + nfsd_nd NULL-clearing, git-apply-able']
PoC changes
Authored from scratch: minimal ADDSOCK+NFSD daemon with pre-allocated auth/verf buffers (the copyouts at :616-620 must succeed for ENEEDAUTH to escape), a sender child crafting the 19-word AUTH_KERB FULLNAME request, and the fatal AUTHIN resume. Two setup iterations were needed: the guest auto-starts stock nfsd(8) whose workers steal every nfsrv_wakenfsd assignment (PoC must killall nfsd), and a leftover zapd worker did the same.
Verified recommended fix
In nfssvc_nfsd()'s NFSD_REQINPROG else-branch, reload nd = nfsd->nfsd_nd; clear nfsd->nfsd_nd wherever nd is kfree'd (see fix.diff).
Verdict
Deterministic kernel NULL-deref panic, reproduced on the stock INVARIANTS kernel from a fresh vm.sh reset on the first clean attempt. An AUTH_KERB/FULLNAME request makes nfssvc_nfsd() return ENEEDAUTH with the request parked in nfsd->nfsd_nd and NFSD_REQINPROG set (nfs_syscalls.c:609-623); the documented daemon resume via nfssvc(NFSSVC_AUTHIN|NFSSVC_NFSD) re-enters nfssvc_nfsd() whose local nd is NULL (:448) and is never reloaded from nfsd->nfsd_nd (:549-570), so the reply do-loop dereferences nd->nd_procnum at :671 with writes_todo==0. Fatal trap 12, fault VA 0x70 (offsetof nd_procnum), 'Stopped at sys_nfssvc+0x3d4: movl 0x70(%rdi),%eax', current process = kerbd. Root-only trigger (nfssvc requires SYSCAP_RESTRICTEDROOT); base-system nfsd(8) cannot trigger it because its zeroed nsd_authstr makes the :616-620 copyouts fail first (request becomes RC_DROPIT), so severity is Low: local privileged-user kernel panic, plus a theoretical remote amplifier against any non-base daemon implementing the kerb AUTHIN protocol. fix.diff (reload nd from nfsd->nfsd_nd on resume; clear nfsd->nfsd_nd where nd is freed) validated by full kernel rebuild: baseline panics, patched kernel completes the resume and keeps serving (run.fixed.log), guest survives.
No comments yet.