krpc_call() early-error exits (socreate/sosetopt/sobind) leak the caller's request mbuf chain β md_mount/md_lookup_swap's 'message already freed' ownership contract is false
Summary
krpc_call chains the caller's request onto mhead only at krpc_subr.c:275-276; error exits before that point (:220,:231,:263) jump to out: which frees only mhead (NULL) - the request chain is leaked. md_mount's v3->v2 fallback reassigns m at :160 without freeing, and both md_mount:166 and md_lookup_swap:259 return under the comment 'message already freed', which is false for these exits. Bounded leak (<=~6 mbufs per boot); the trigger failures are practically unreachable in single-threaded early boot. No UAF/double-free variant (post-chaining exits free exactly once). Distinct from DF-3204 (reply-chain leak). Fix: free *data in out: when mhead==NULL.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3210 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.8 KB | β raw | |
| VERDICT.md | β | 3.1 KB | β raw | |
| verdict.json | β | 2.3 KB | view raw | |
| manifest.json | β | 827 B | view raw | |
| fix.diff | β | 483 B | view raw |
DF-3210 β krpc_call() early-error exits leak the caller's request mbuf chain; nfs_mountrpc.c's "message already freed" ownership contract is false
Files: sys/vfs/nfs/krpc_subr.c:213-265,275-276,437-441 (callee defect) manifesting at
sys/vfs/nfs/nfs_mountrpc.c:146-166, 252-259 (callers).
What
krpc_call() only chains the caller's request mbuf (*data) onto mhead at krpc_subr.c:275-276. Every error
exit before that point (socreate :220-221, sosetopt(SO_RCVTIMEO) :231-232, sobind :263-265) jumps to
out: where only mhead (NULL) and from (NULL) are freed β the request chain is leaked. After chaining,
all exits free it via m_freem(mhead).
The callers encode the opposite assumption:
* md_mount v3 attempt (nfs_mountrpc.c:143-147): on error it falls into the v2 branch and reassigns m
at :160 without freeing β leaks the v3 request chain when krpc_call exited early.
* md_mount v2 (nfs_mountrpc.c:163-166): return error; /* message already freed */ β comment false on
early exits.
* md_lookup_swap (nfs_mountrpc.c:252-259): same return error; /* message already freed */.
Severity honesty
Bounded leak: at most one chain (1-2 mbufs) per failed call, β€3 calls per boot β β€ ~6 mbufs. The triggering
failures (socreate/sosetopt/sobind with all 512 swept reserved ports busy) are practically unreachable in the
single-threaded early-boot context. No UAF, no double-free (verified: after a post-chaining error exit the
request is freed exactly once as part of m_freem(mhead), and callers never dereference m after a failed
krpc_call before reassignment β :146β:160 and :258-259). Filed as Info: real defect, no demonstrated impact.
Ceiling
Boot-path only (#if BOOTP||NFS_ROOT); audit guest boots hammer2 from disk, nfs_mountroot never runs β
same not-runtime-testable ceiling as DF-3203/3204/3205.
VERDICT β DF-3210
status: untested (boot-path-only; not runtime-testable on the audit guest) impact: none (bounded mbuf leak; no corruption, no reachable exhaustion) confidence: certain (ownership contract violation provable by inspection; trigger likelihood marginal)
Proof chain (path:line)
sys/vfs/nfs/krpc_subr.c:275-276βmhead = m_gethdr(M_WAITOK, MT_DATA); mhead->m_next = *data;β the caller's request becomes part ofmhead's chain only from this point on.- Error exits before :275 β
socreate(:220-221),sosetopt(SO_RCVTIMEO)(:231-232), andsobind(:263-265, the reserved-port sweep loop) β allgoto out. sys/vfs/nfs/krpc_subr.c:437-441βout:frees onlymhead(NULL on those paths) andfrom;*datais never freed and never reassigned on error β request chain leaked.- Caller-side manifestation:
*
sys/vfs/nfs/nfs_mountrpc.c:146-147v3krpc_callerror βelsebranch at :151 βmreassigned at :160 withoutm_freem(m)β v3 request leaked on early-exit errors. *sys/vfs/nfs/nfs_mountrpc.c:165-166βreturn error; /* message already freed */β false for early exits. *sys/vfs/nfs/nfs_mountrpc.c:258-259β same false comment inmd_lookup_swap; request (m_get head + xdr_string_encode chain, 2 mbufs) leaked. - Not a UAF/double-free: for error exits after chaining (sosend :324-327, PROGMISMATCH :393-395 β that
reply-chain variant is DF-3204, not re-reported),
*datawas freed exactly once insidem_freem(mhead); callers never touchmafter a failed call before reassignment (:146β:160) or return (:259).
Trigger reality
socreate(AF_INET, SOCK_DGRAM) and sosetopt(SO_RCVTIMEO) do not fail in practice; sobind fails only when
all ~512 swept reserved UDP ports (:256-262) are taken during single-threaded early boot. Bounded: β€1 chain
per failed call, β€3 calls per boot.
Why untested
Only reachable inside the boot-path mount RPCs (#if BOOTP||NFS_ROOT); audit guest (DragonFly 6.5-DEVELOPMENT
0, X86_64_GENERIC, hammer2 disk root) never executes nfs_mountroot β md_mount/md_lookup_swap.
Recommended fix
Free the unconsumed request at out: when it was never chained:
--- a/sys/vfs/nfs/krpc_subr.c
+++ b/sys/vfs/nfs/krpc_subr.c
@@ -437,7 +437,13 @@
out:
- if (mhead) m_freem(mhead);
+ if (mhead) {
+ m_freem(mhead);
+ } else if (*data) {
+ /* Early error exit before the request was chained onto mhead.
+ Callers (nfs_mountrpc.c:166,259) expect krpc_call to consume it. */
+ m_freem(*data);
+ *data = NULL;
+ }
if (from) kfree(from, M_SONAME);
soclose(so, FNONBLOCK);
return error;
(After gotreply, mhead != NULL and *data is the reply β path unaffected.)
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)
findings/poc/DF-3210/VERDICT.md steps 1-4; decisive lines: krpc_subr.c:275-276 (chaining point), :220-221/:231-232/:263-265 (pre-chain error exits), :437-441 (out: frees only mhead/from), nfs_mountrpc.c:146->160 (fallback reassign), :166/:259 (false 'message already freed' comments)
PoC changes
none β no runnable seed exists for a boot-path mbuf leak; verdict is an ownership/lifetime derivation over both files
Verified recommended fix
in krpc_call out: free *data when mhead==NULL (request never chained), matching the callers' 'krpc_call consumes the request' contract
Verdict
krpc_call() chains the caller's request mbuf onto mhead only at krpc_subr.c:275-276; its early error exits (socreate :220, sosetopt :231, sobind :263) jump to out: with mhead==NULL so *data is never freed, while both md_mount (nfs_mountrpc.c:163-166) and md_lookup_swap (:258-259) return on krpc_call error under the comment 'message already freed' β the ownership contract is false and the request chain leaks (md_mount's v3->v2 fallback additionally reassigns m at :160 without freeing). Inspection-certain defect; impact bounded to <=~6 mbufs per boot and the trigger failures are practically unreachable in single-threaded early boot, so filed Info. Not runtime-testable: boot-path-only code, guest boots hammer2 from disk (same ceiling as DF-3203/3204/3205).
No comments yet.