nbssn_recv() leaks mbufs on receive error and on inner-loop sbinit() reset
Summary
nbssn_recv (smb_trantcp.c:345-374): inner do/while retry :346 sbinit(&sio,savelen) zeroes sb_mb=NULL frees nothing -> prior partial chain orphaned. Error path :352-353 if(error)break exits outer loop sio.sb_mb leaked cleanup :367-373 only runs error==0. sbinit (sockbuf.h:115-126) only zeroes fields frees nothing. sorecvtcp returns ECONNRESET mid-stream RST with partial bytes already in sio.sb_mb. Attacker: malicious SMB server sends NBSS header advertising large length delivers few bytes then RST. Each failed receive leaks up to 131071B mbufs. smb_iod_recvall loops re-enters SMB_TRAN_RECV repeats -> mbuf exhaustion -> network collapse/panic. Fix: m_freem(sio.sb_mb) before sbinit + on error path.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0673 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fake_smb_server.c | trigger-source | minimal C fake NBSS+SMB server; incomplete (doesn't speak enough SMB1 to advance the kernel client) | 5.0 KB | view raw |
| fake_smb_server.py | trigger-source | Python draft of same (unused; guest lacks python3) | 5.4 KB | view raw |
| run_poc.sh | run-script | root-driven mount_smbfs against fake server; measure mbuf delta | 1.1 KB | view raw |
| build.sh | build-script | cc -Wall -o fake_smb_server fake_smb_server.c | 134 B | view raw |
| run.sh | run-script | wrapper | 573 B | view raw |
| VERDICT.md | verdict | source-level trace of the leak + why the live PoC stalls | 4.7 KB | β raw |
| run.log | run-log | attempted live run; mount_smbfs hangs in D1 before leak site | 861 B | view raw |
| fix.diff | suggested-fix | git-apply-able: m_freem(sio.sb_mb) before sbinit + on error paths | 953 B | view raw |
| env.txt | environment | uname, cc, smbfs.ko loaded | 339 B | view raw |
| fix_build.log | build-log | compile-validation: kernel+module build with fix applied, rc=0, no errors | 5.6 MB | β download |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0673 β nbssn_recv (smb_trantcp.c) mbuf leak on receive error / partial chain
Verdict
SOURCE-CONFIRMED (not live-testable here). The bug is unambiguous in source;
the live trigger requires a malicious SMB server speaking NetBIOS Session
Service + enough SMB to advance the kernel SMB client into the post-negotiate
receive loop, which is beyond what this guest's mount_smbfs + a minimal fake
server could drive in the available time (the trigger hangs in mount_smbfs
waiting on smbiod before reaching the leak site).
Mechanism
sys/netproto/smb/smb_trantcp.c:313-376 nbssn_recv:
Two distinct leak paths, both rooted in sbinit() only zeroing fields
(sys/sys/sockbuf.h:115-126) β it never frees the prior sb_mb chain:
Path 1 β inner do/while re-iteration orphans a partial chain
do {
sbinit(&sio, savelen); // :346 ZEROES sio.sb_mb without freeing it
rcvflg = MSG_WAITALL;
error = so_pru_soreceive(so, NULL, NULL, &sio, NULL, &rcvflg);
} while (error == EWOULDBLOCK || error == EINTR || error == ERESTART);
If so_pru_soreceive returns EWOULDBLOCK/EINTR/ERESTART having already
moved some bytes into sio.sb_mb (which happens with MSG_WAITALL on a
slow/interrupted TCP receive), the next loop iteration's sbinit(&sio, savelen)
zeroes sio.sb_mb β orphaning (leaking) the prior partial chain.
Path 2 β outer-loop if (error) break; skips cleanup
if (error) // :352-353
break; // *** exits outer loop ***
...
if (error == 0) { // :367 <-- cleanup only runs on success
if (mpp) *mpp = sio.sb_mb;
else m_freem(sio.sb_mb);
...
}
Any non-retry receive error (e.g. ECONNRESET after a TCP RST) leaves a partial
sio.sb_mb chain that the success-only cleanup never frees.
Reachability
nbssn_recvis called fromsmb_nbst_recv(smb_trantcp.c:539), theSMB_TRAN_RECVvector, invoked in afor(;;)loop bysmb_iod_recvall(smb_iod.c:304,322-324). Each iteration that errors out and re-enters can re-leak.- Sustained attack (a malicious SMB server that keeps accepting connections and RST'ing mid-stream) drives mbuf-pool exhaustion β network collapse / panic.
Threat model / preconditions (realistic)
smbfs.koloaded (root action; realistic on systems that mount SMB shares).- A malicious SMB server the victim mounts from (network attacker / MitM / compromised server).
No kldload by the attacker; the trigger is the kernel SMB client processing
untrusted server data.
Why not live-reproduced here
The fake-server PoC in fake_smb_server.c completes the NBSS session-request
handshake and emits a malformed SMB negotiate response, then sends a NBSS
message header claiming 4000 B but delivers only 50 B before RST'ing. The
intent was to drive mount_smbfs into the nbssn_recv post-negotiate path.
In practice mount_smbfs hangs in D1 (uninterruptible wait) before reaching
the leak site β smbiod blocks waiting on the bogus negotiate response that
the minimal fake server never properly formats, so the kernel never advances
to nbssn_recv for the post-negotiate message. A live PoC would require a
fully-conforming NBSS+SMB negotiate response (correct SMB1 header, dialect
array, security blob, etc.) β substantial additional work beyond this run's
budget.
The bug itself is unambiguous: sbinit() does not free, and the error paths
skip m_freem(sio.sb_mb). Source confidence is certain.
Realistic impact ceiling
This is an mbuf-pool exhaustion DoS (resource leak). It is not a memory-corruption primitive β no attacker-controlled write, no UAF, no type confusion β so there is no escalation chain. Ceiling: repeated mounts from a malicious server eventually exhaust the mbuf pool β network collapse / panic.
Fix
sys/netproto/smb/smb_trantcp.c:
- Free sio.sb_mb before sbinit(&sio, savelen) in the inner do/while
(Path 1).
- Free sio.sb_mb on the if (error) break; outer-loop exit (Path 2).
- Free sio.sb_mb on the ECONNRESET outer-loop exit (the so->so_state &
SS_ISDISCONNECTING|β¦ path) β same class of leak.
See fix.diff (git-apply-able, applies cleanly, compiles, kernel boots).
Fix validation (Phase 8)
fix_status: not_testable. The fix.diff applies (patch -p1 succeeds,
git apply --check succeeds) and compiles as part of the single-fix-combined
kernel (6.5-DEVELOPMENT #1 built and booted successfully, see DF-0680
evidence pack for the combined build log). But because the live trigger is not
reproducible on this guest (see above), the before/after leak behavior cannot
be measured here. The change is small, mechanical, and obviously correct given
the source-level analysis.
Fix verification
not_testablecompile validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. nbssn_recv mbuf leak on error/incomplete. Needs fake SMB server (mount_smbfs hangs at negotiate). Fix compiles.
No comments yet.