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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3237 Β· 9 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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_testable
↓ fix.diffper-fix-DF-3237

Confirmed 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.