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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3087 Β· 11 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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)

  1. 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.
  2. killall nfsd β€” the guest image auto-starts stock nfsd(8) whose waiting workers steal every nfsrv_wakenfsd assignment; 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).
  3. /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), then nfssvc(NFSSVC_NFSD, &nsd) with pre-allocated auth/verf buffers; - nfs_getreq() takes the FULLNAME path (nfs_socket.c:2392-2434), fills nfsd_authstr/nfsd_verfstr, sets NFSD_NEEDAUTH; - nfssvc_nfsd() :609-623: the three copyouts succeed (buffers valid) β†’ returns ENEEDAUTH (errno 81) with the request parked in nfsd->nfsd_nd, NFSD_REQINPROG set, nfsd->nfsd_slp held; - daemon prints the pointers and immediately calls nfssvc(NFSSVC_AUTHIN|NFSSVC_NFSD, &nsd); - sys_nfssvc :215-297 runs the AUTHIN credential insert (works, nfsd->nfsd_slp still valid, nfsd_nd still set), then nfssvc_nfsd() :443 re-enters with local nd = NULL (:448), takes the NFSD_REQINPROG else-branch (:549-570) which never reloads nfsd->nfsd_nd, falls past the if (nd) block (:599), and the reply do-loop evaluates nd->nd_procnum == NFSPROC_WRITE at :671 with writes_todo == 0 (init :457) β†’ movl 0x70(%rdi),%eax with rdi=NULL.
  4. 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) requires SYSCAP_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 zeroed nsd: at :616-620 copyout(..., 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_nd is never freed if the daemon abandons the ENEEDAUTH'd request (permanent leak of nfsrv_descript + request mbufs), and :760's kfree(nd) leaves nfsd_nd dangling until the next nfsrv_dorec overwrites 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 (reload nd = nfsd->nfsd_nd in the :549 else-branch; clear nfsd->nfsd_nd at 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

fixed
baseline reproduced→ patch + rebuild →patched clean

vm.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
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #1: Sun Sep 6 04:53:45 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC

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.