DragonFlyBSD Kernel Audit
← triage · dashboard
DF-3043

nm_bioqlen async-bio accounting leaks on every nfs_startio early-exit and underflows on commit→write reissue — permanent mount-wide loss of NFS read-ahead from a malicious/oversized-server attribute claim (and loss of the async throttle)

Field Value
ID DF-3043
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L
CWE CWE-401
File sys/vfs/nfs/nfs_bio.c
Lines 957 vs nfs_iod.c:139/212; leak sites :1346-1373, :1463-1478, :1687-1693, nfsm_subs.c:826-836; underflow :1753-1755
Area vfs/nfs
Confidence certain
Discovered 2026-09-02
Pass 2 (GLM 5.3 second pass)
Bucket base:vfs
Reported pending
Known CVE none
CVE match novel

Summary

nfs_asyncio() increments nmp->nm_bioqlen when queueing a bio, but the counter is decremented ONLY when a queued nfsreq completes. Every path where nfs_startio() completes the bio WITHOUT ever queueing an nfsreq leaks one count permanently: readrpc EFBIG early-out, writerpc degenerate-dirty-range and EFBIG early-outs, commitrpc no-write-verifier early-out, and nfsm_request_bio initial-transmission early abort. Conversely, when a COMMIT fails and nfs_commitrpc_bio_done chains nfs_writerpc_bio for the SAME bio, a second request is queued whose completion decrements again after the commit request already decremented — one increment, two decrements → counter underflow. Once nm_bioqlen saturates at min(nfs_maxasyncbio=64, nm_maxasync_scaled/256), nfs_asyncok() returns 0 forever (nothing resets the counter short of unmount), permanently disabling read-ahead for the entire mount; underflow makes the throttle ineffective in the other direction. An unpriv local user against a malicious (or pathologically oversized) NFS server: a plain stat() applies the server-claimed size to n_size (server claims n_size > nm_maxfilesize — itself server-negotiable via FSINFO); a sequential read at the last legal block then makes the read-ahead block pass the n_size gate and be queued, where readrpc's EFBIG check biodone's it without decrement — up to min(readahead, seqcount) leaks per read; ~64 such reads permanently kill read-ahead mount-wide (benign perf degradation). Speculative adjacent hazard sharing the root cause: nfs_asyncok's state check racing nfssvc_iod_stop1 during umount -f can orphan a locked bp on nm_bioq and wedge vflush (microsecond window). Not built (Low/perf-only per contract). Fix: balance the counter at every bypass site + re-increment on the commit→write chain (row diff).

Timeline

  • 2026-09-02 Discovered during pass-2 audit of nfs_bio.c (GLM 5.3). DF-0799 re-verified, not re-reported (its dirtyend underflow family re-traced: all guards convert to skip-the-write, data-loss only).

Discussion (0)

No comments yet.