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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0673 Β· 12 files
FileTypeDescriptionSize
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
VERDICT.md verdict source-level trace of the leak + why the live PoC stalls
↓ download 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_recv is called from smb_nbst_recv (smb_trantcp.c:539), the SMB_TRAN_RECV vector, invoked in a for(;;) loop by smb_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.ko loaded (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_testable

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