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).
No comments yet.