iod-shutdown race: after nfssvc_iod_stop1() the reader re-queues EINPROGRESS requests onto nm_reqtxq whose only consumer (the writer thread) is already exiting β stranded nfsreq + nfsm_info + bio (biodone never called, buffer stuck B_BUSY), queue head embedded in the nfsmount that nfs_unmount then frees
Summary
nfssvc_iod_stop1 flips both states to STOPPING. The writer breaks out as soon as it observes STOPPING (:180-181) and sets NFSSVC_DONE/clears nm_txthread (:217-218). The reader only re-checks rxstate at the top of its for(;;) - it can still be inside the reqrxq drain (:120-142). If nfs_request(PROCESSREPLY) returns EINPROGRESS there (server TRYLATER/JUKEBOX), the reader inserts the req onto nm_reqtxq (:136) and calls nfssvc_iod_writer_wakeup - a no-op once txstate != WAITING (:244-247), i.e. dead once the writer is DONE. The req is stranded: done()/biodone never run, leaking M_NFSREQ + nfsm_info + bio permanently B_BUSY, and the TAILQ head remains linked inside nmp which nfs_unmount then kfrees. Same for bios parked on nm_bioq when the writer takes the reqqlen-throttle break then observes STOPPING. Root-triggered (umount -f racing async I/O with server JUKEBOX, sub-ms window); ceiling = leaked memory + permanently-B_BUSY buffer, no demonstrated memory-safety violation. Not PoC'd (Low/speculative). Fix: drain the queues at shutdown / make writer_wakeup fail loudly past STOPPING. [VERIFIED pass2: sub-ms umount-f vs JUKEBOX race beyond rig; stranding path line-traced; structural fix compile-validated]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3237 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | β | 754 B | view raw | |
| README.md | β | 394 B | β raw | |
| VERDICT.md | β | 1.7 KB | β raw | |
| build.sh | β | 106 B | view raw | |
| run.sh | β | 118 B | view raw | |
| manifest.json | β | 497 B | view raw | |
| verdict.json | β | 1.4 KB | view raw | |
| fix.diff | β | 754 B | view raw | |
| env.txt | β | 272 B | view raw |
DF-3237 -- verification pack
Finding: iod-shutdown race strands nfsreq/nfsm_info/bio on a dying queue
Status: not_reproduced / impact leak / confidence speculative
What was run
n/a
How to reproduce
n/a (umount -f vs JUKEBOX server race)
Expected
n/a
See VERDICT.md for the full narrative and verdict.json for the persisted machine verdict. Artifacts: fix.diff.
DF-3237 verification
Kernel: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
Bottom line
Speculative and not dynamically exercised: after nfssvc_iod_stop1 the reader can still requeue an EINPROGRESS (server TRYLATER) request onto nm_reqtxq whose only consumer is exiting, and nfssvc_iod_writer_wakeup is a no-op once txstate leaves WAITING - stranding a nfsreq + nfsm_info + bio (biodone never called, buffer stuck B_BUSY) whose queue head lives inside the nfsmount that nfs_unmount then frees. The window is sub-millisecond inside an umount -f racing async I/O against a JUKEBOX server; constructing that NFS topology exceeded this batch budget. Fix (drain reqtxq when the writer is dead) authored as a structural sketch in fix.diff and compile-validated in its minimal form.
Kernel references
Exploit chain
(none: availability/correctness finding; no privilege chain)
Fix
drain nm_reqtxq once the writer thread is STOPPING/DONE
See fix.diff in this directory (git-apply-able against the read-only sys/ tree; applied only inside the disposable verification guest).
Reproduction
build: n/a
run: n/a (umount -f vs JUKEBOX server race)
expected: n/a
Notes on the PoC (what changed vs the finding sketch)
(none)
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md walk over nfs_iod.c:120-142/:180-181/:217-218/:244-247
Verified recommended fix
drain nm_reqtxq once the writer thread is STOPPING/DONE
Verdict
Speculative and not dynamically exercised: after nfssvc_iod_stop1 the reader can still requeue an EINPROGRESS (server TRYLATER) request onto nm_reqtxq whose only consumer is exiting, and nfssvc_iod_writer_wakeup is a no-op once txstate leaves WAITING - stranding a nfsreq + nfsm_info + bio (biodone never called, buffer stuck B_BUSY) whose queue head lives inside the nfsmount that nfs_unmount then frees. The window is sub-millisecond inside an umount -f racing async I/O against a JUKEBOX server; constructing that NFS topology exceeded this batch budget. Fix (drain reqtxq when the writer is dead) authored as a structural sketch in fix.diff and compile-validated in its minimal form.
No comments yet.