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

mbuf chain leak on duplicate SMB responses enables remote kernel memory-exhaustion DoS

Field Value
ID DF-0627
Status new
Severity High
CVSS 3.1 CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
CWE CWE-401 Missing Release of Memory after Effective Lifetime
File sys/netproto/smb/smb_iod.c
Lines 363-366 (leak); 374-377 (skipped free)
Area netproto/smb (kernel SMB client response dispatch)
Confidence certain
Discovered 2026-07-02
Reported pending

Summary

In smb_iod_recvall(), when a second SMB response arrives for an already-answered request (same MID, md_top already set, not SMBR_MULTIPACKET), the else branch logs "duplicate response … ignored" and breaks out of the TAILQ_FOREACH without freeing the freshly-received mbuf chain. The post-loop m_freem() is gated on rqp == NULL, which is false after a successful match, so the mbuf is orphaned. A malicious SMB server can flood duplicate responses for an outstanding MID and leak an unbounded number of mbuf chains (each up to SMB_MAXPKTLEN = 128 KB), exhausting kernel memory and panicking/OOM-killing the client.

Root cause

sys/netproto/smb/smb_iod.c:353-378:

353:    SMB_IOD_RQLOCK(iod);
354:    TAILQ_FOREACH(rqp, &iod->iod_rqlist, sr_link) {
355:        if (rqp->sr_mid != mid)
356:            continue;
357:        SMBRQ_SLOCK(rqp);
358:        if (rqp->sr_rp.md_top == NULL) {
359:            md_initm(&rqp->sr_rp, m);              /* ownership transferred */
360:        } else {
361:            if (rqp->sr_flags & SMBR_MULTIPACKET) {
362:                md_append_record(&rqp->sr_rp, m);   /* ownership transferred */
363:            } else {
364:                SMBRQ_SUNLOCK(rqp);
365:                SMBERROR("duplicate response %d (ignored)\n", mid);
366:                break;                               /* m NOT freed, NOT attached */
367:            }
368:        }
369:        SMBRQ_SUNLOCK(rqp);
370:        smb_iod_rqprocessed(rqp, 0);
371:        break;
372:    }
373:    SMB_IOD_RQUNLOCK(iod);
374:    if (rqp == NULL) {                                   /* FALSE here β€” rqp matched */
375:        SMBERROR("drop resp with mid %d\n", (u_int)mid);
377:        m_freem(m);                                       /* SKIPPED */
378:    }

md_initm() (subr_mchain.c:316) takes ownership of m by storing it in md_top. In the duplicate else branch the mbuf is neither attached (md_initm/md_append_record not called) nor freed (no m_freem), and the only post-loop m_freem at line 377 is gated on rqp==NULL which is false because rqp matched at line 355. The local pointer m is overwritten at the next loop iteration, so the chain is permanently orphaned.

Threat model & preconditions

  • Attacker position: the SMB server the victim client is connected to (network position, authenticated session sufficient).
  • Precondition: the client has an active VC with at least one outstanding request whose MID matches the flood (any normal file I/O on a mounted share, e.g. mount_smbfs //user@evil/share /mnt; touch /mnt/x).
  • Trigger: the server sends ONE valid SMB reply for that MID, then immediately sends K (e.g. 50000) additional NetBIOS-framed SMB messages, each bearing the same MID at offset 30. Each duplicate is a full standalone NB session message, so nbssn_recv (smb_trantcp.c:313) returns it as a separate mbuf chain. The client's smb_iod drains the burst in one recvall() pass; each duplicate hits the else branch at line 363 and leaks its chain.
  • Impact: each leak is one mbuf chain of attacker-chosen length up to 128 KB; a burst of N duplicates leaks up to N chains. Sustained flooding drives the kernel into mbuf/memory exhaustion and panic.
  • UI:R because a local user must mount the attacker's share.

Free the orphaned mbuf in the duplicate branch before break:

--- a/sys/netproto/smb/smb_iod.c
+++ b/sys/netproto/smb/smb_iod.c
@@ -360,11 +360,12 @@ smb_iod_recvall(struct smbiod *iod)
                md_append_record(&rqp->sr_rp, m);
            } else {
                SMBRQ_SUNLOCK(rqp);
+               m_freem(m);
                SMBERROR("duplicate response %d (ignored)\n", mid);
                break;
            }

This ensures every received mbuf chain is either handed to a requestor (md_initm/md_append_record) or freed (signature mismatch at line 348, no-match drop at line 377, or duplicate at the new line).

References

Timeline

  • 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
  • 2026-07-02 Reported to DragonFlyBSD security contact (pending).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0627 Β· 10 files
FileTypeDescriptionSize
build.sh build-script no-op; trigger is a malicious SMB server 626 B view raw
run.sh run-script documents the malicious-server harness + netstat -m observation 1.2 KB view raw
fix.diff suggested-fix m_freem(m) in the duplicate else branch before break 494 B view raw
VERDICT.md verdict line-by-line source proof of the leak 2.8 KB ↓ raw
README.md readme build/run/expected 1.2 KB ↓ raw
env.txt environment guest env + SMB module notes 365 B view raw
../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
live_reachability_check.txt reachability-test Live SMB reachability test - module loadable, mount_smbfs hangs at SESSION_SETUP 1.9 KB view raw
evil_smb_dup.c exploit-attempt Malicious SMB1 server sending duplicate responses to trigger mbuf leak 12.3 KB view raw
README.md readme build/run/expected
↓ download raw

DF-0627 PoC β€” mbuf leak on duplicate SMB responses

Build

No client binary to build β€” the trigger is a malicious SMB server (network attacker position) plus a victim that mounts its share. build.sh is a no-op placeholder.

Run / trigger (requires the malicious-server harness)

  1. Run a malicious SMB1 server reachable from the guest.
  2. Victim mounts the share: mount_smbfs //user@evil/share /mnt and issues any request that produces an outstanding MID (e.g. touch /mnt/x).
  3. Server sends ONE valid reply for that MID, then a burst of K additional NetBIOS-framed SMB messages with the SAME MID.
  4. Observe the leak: ssh dfbsd 'netstat -m' β€” mbuf count climbs by K (each up to 128 KB). Sustained flooding β†’ mbuf/memory exhaustion β†’ panic.

Expected (bug present)

netstat -m mbuf/memory use climbs monotonically with each duplicate; eventually the kernel panics on mbuf exhaustion.

Expected (FIXED)

Each duplicate's mbuf chain is freed in the else branch; netstat -m stays flat.

Proof

The bug is proven by source tracing in VERDICT.md (the else branch at smb_iod.c:363-366 breaks without freeing m, and the post-loop m_freem at :377 is gated on rqp==NULL which is false after a match).

VERDICT.md verdict line-by-line source proof of the leak
↓ download raw

DF-0627 β€” mbuf chain leak on duplicate SMB responses (remote DoS)

Verdict

CONFIRMED by exhaustive source tracing β€” memory leak / remote DoS. Not exercised at runtime in this evidence pack because the trigger requires a malicious SMB server (network-attacker position); the leak is unambiguous in the code.

Mechanism (cited)

sys/netproto/smb/smb_iod.c smb_iod_recvall() (:322-379): - :324 SMB_TRAN_RECV(vcp, &m, td) receives an mbuf chain m from the network. - :354 TAILQ_FOREACH(rqp, ...) finds the outstanding request matching the response MID. - :358-359 first response: md_initm(&rqp->sr_rp, m) β€” ownership of m transfers to the request (md_top = m, sys/kern/libmchain/subr_mchain.c:319). - :361-362 multipacket: md_append_record(&rqp->sr_rp, m) β€” ownership transfers. - :363-366 duplicate response (same MID, md_top already set, not SMBR_MULTIPACKET): the else branch does SMBRQ_SUNLOCK(rqp); SMBERROR("duplicate response ..."); break; β€” it breaks out of the loop without calling md_initm/md_append_record AND without m_freem(m). m is neither attached nor freed. - :374-377 the only post-loop m_freem(m) is gated on if (rqp == NULL) β€” but rqp is non-NULL (it matched at :355), so m_freem is SKIPPED. - Next loop iteration :323 m = NULL; then :324 overwrites m; the orphaned chain is permanently leaked.

md_initm ownership claim: sys/kern/libmchain/subr_mchain.c:316-319 mdp->md_top = mdp->md_cur = m;. SMB_MAXPKTLEN = 0x1FFFF (sys/netproto/smb/smb.h:292); nbssn_recv (smb_trantcp.c:313) returns each NetBIOS session message as a separate chain, bounded by :305.

Threat model / impact

  • Attacker = the SMB server the victim client is connected to (network position; authenticated session sufficient). UI:R β€” a local user must mount the attacker's share.
  • Trigger: server sends one valid reply for an outstanding MID, then K additional NetBIOS-framed SMB messages with the SAME MID. Each duplicate hits :363 and leaks its chain (up to 128 KB). Sustained flooding β†’ mbuf/memory exhaustion β†’ panic/OOM.
  • Impact: remote kernel memory-exhaustion DoS (CWE-401). Pure resource leak; no memory corruption, no privilege boundary crossed β†’ no uid0 path.

Why no runtime run here

Reproducing requires a malicious SMB1 server (NEGOTIATE + SESSION_SETUP + TREE_CONNECT + the duplicate flood). That harness is out of scope for this evidence pack; the bug is proven line-by-line above. netstat -m on a victim under attack would show the mbuf count climbing monotonically per duplicate.

Fix

fix.diff adds m_freem(m) in the duplicate else branch before break, so every received chain is either attached (md_initm/md_append_record) or freed. Matches the finding markdown's recommended fix exactly.

Fix verification

not_testable

compile validated

Confirmed kernel references

Detail

Exploit chain

none β€” memory leak (DoS), not a memory-corruption primitive. The bug leaks one mbuf chain per duplicate response. Trigger requires a working SMB session + malicious server sending duplicate responses for an outstanding MID.

Evidence (decisive lines)

smbfs.ko loaded: YES
smb_iod_recvall in smbfs.ko: YES (offset 0x86f0)
mount_smbfs available: YES
Evil server accepts connection, processes NEGOTIATE + SESSION_SETUP
mount_smbfs HANGS after SESSION_SETUP (D state, never sends TREE_CONNECT)
Source trace confirms else branch at smb_iod.c:363-366 does NOT free m
Post-loop m_freem at :377 gated on rqp==NULL (FALSE after match) -> m leaks

PoC changes

Created evil_smb_dup.c: malicious SMB1 server based on DF-0901's stub_smbd.c, modified to send duplicate responses in a single TCP write(). Added live_reachability_check.txt documenting the SMB mount failure.

Verified recommended fix

fix.diff adds m_freem(m) in the duplicate-response else branch at smb_iod.c:366 before break. Applies and compiles. Matches finding proposal.

Verdict

NOT REPRODUCED at runtime, but SOURCE-CONFIRMED real. smbfs.ko loads successfully and smb_iod_recvall (smb_iod.c:353-378) is present. The bug IS traced line-by-line: duplicate-response else branch at lines 363-366 breaks out of TAILQ_FOREACH WITHOUT freeing mbuf m; post-loop m_freem at line 377 is gated on rqp==NULL which is FALSE after a match at line 355, so m leaks. I built evil_smb_dup.c (a malicious SMB1 server that sends duplicate responses) and attempted to trigger via mount_smbfs. The server correctly handles NBSS session setup, NEGOTIATE, and SESSION_SETUP, but mount_smbfs HANGS after SESSION_SETUP β€” it never sends TREE_CONNECT. The issue is that the SESSION_SETUP response format doesn't satisfy the DragonFly smbfs client's NTLMSSP authentication expectations (the client expects either anonymous auth acceptance with proper word format or an NTLMSSP challenge with STATUS_MORE_PROCESSING_REQUIRED). The bug trigger requires a fully functional SMB1 server that correctly completes the NTLMSSP exchange.