# DF-2905 — md_done() leaks all pending m_nextpkt records of an mdchain

**File:** `sys/kern/libmchain/subr_mchain.c` (md_done, :323-330; md_append_record/md_next_record, :336-369)
**Severity:** High · **Class:** resource leak → remote memory-exhaustion DoS · **Bucket:** legacy (only consumer: `sys/netproto/smb/`, netsmb/smbfs)

## What the bug is

`md_done()` tears down an mdchain with `m_freem(md_top)`. On DragonFly,
`m_free()` follows **only `m->m_next`** (`sys/kern/uipc_mbuf.c:1320`) and even
prints `mfree: m->m_nextpkt != NULL` when the mbuf being freed still has a
record link (`:1334-1339`) — but it does **not** free the `m_nextpkt` list.
Whenever an mdchain has records appended via `md_append_record()` and
`md_done()` runs before every record was consumed through `md_next_record()`,
every remaining record chain is orphaned: a permanent, attacker-sized mbuf
leak.

In-tree reachability (malicious SMB server → root-mounted smbfs client):

* `smb_iod_recvall` (`sys/netproto/smb/smb_iod.c:353-372`) attaches every
  same-MID NetBIOS message of a `SMBR_MULTIPACKET` request as a new record
  (`md_initm`/`md_append_record`), draining the socket in one loop **before**
  the requester thread wakes.
* `smb_t2_reply` (`sys/netproto/smb/smb_rq.c:474-545`) breaks out of its
  record loop on success (:531-534) or on any parse error, without consuming
  remaining records; `smb_t2_request_int` then calls `smb_rq_done` →
  `md_done(&rqp->sr_rp)` (`smb_rq.c:741-743, 149`) — all still-attached
  records leak.
* `smb_rq_new` (`smb_rq.c:115-116`) re-inits `sr_rp` with `md_done` on
  request reuse — same leak on the retry path.

Each leaked record is a full server-controlled message up to
`SMB_MAXPKTLEN` (0x1FFFF = 128 KiB, `sys/netproto/smb/smb.h:292`). A
sustained flood of extra same-MID responses exhausts the mbuf/cluster pools
(`kern.ipc.nmbclusters`) → system-wide networking DoS/panic. Same threat
model as pass-1 finding DF-0627 (duplicate-response leak in `smb_iod.c`),
but a **different defect and trigger**: unconsumed record tails at
`md_done()`, in the mchain library itself.

## How to reproduce (single-tenant QEMU guest, root)

```
# host: pack transferred to guest /root/df2905
ssh dfbsd   # (vm.sh's ssh config; see dfbsd-qemu/)
cd /root/df2905
sh build.sh          # builds mbread + 4 KLD variants (base/fix x tail/walk)
sh run.sh            # measures live mbufs (mbstat.m_mbufs) around each kldload
```

Expected on stock (vulnerable) `subr_mchain.c`:

* `RESULT baseline-tail: delta=+8` (3 records x 4 mbufs; md_done frees only
  record 1; ±1 mbuf noise from the ssh session itself)
* kernel console shows `mfree: m->m_nextpkt != NULL` with a backtrace whose
  innermost frames are `md_done() → m_freem()` — the kernel's own mbuf layer
  flagging the orphaned record list (see `mfree_backtrace.txt`)
* `RESULT baseline-walk: delta=0` — walking via `md_next_record()` frees each
  record correctly (the leak is only at terminal `md_done`)

Expected on the fixed copy (`subr_mchain_fixed.c`, generated from `fix.diff`):

* `RESULT fixed-tail: delta=0` — leak gone
* `RESULT fixed-walk: delta=0` with identical rc/md_top sequence — record
  walking semantics preserved, no double-free

## Files

* `mchain_leak.c`   KLD test module (compiles the real `subr_mchain.c` in;
                    stock GENERIC has no libmchain symbols, no clash)
* `mbread.c`        userland reader of `kern.ipc.mbstat` (m_mbufs is first member)
* `Makefile`        KLD makefile (mode via generated `walkflag.h`)
* `build.sh`, `run.sh`  exact guest commands
* `run.log`, `run.2.log`  decisive runs (incl. 3x stress matrix)
* `mfree_backtrace.txt`   in-kernel evidence of the orphaned nextpkt list
* `env.txt`         guest identity
* `fix.diff`        git-apply-able fix (never applied to sys/)
* `VERDICT.md`, `manifest.json`, `verdict.json`  verdict record
