# DF-2566 — smb_t2_placedata mbuf m_len corruption on oversized TRANS2 count → kernel heap OOB / panic

## Verdict
**REPRODUCED** (kernel panic from a malicious SMB server). Fix **VALIDATED**
(fixed `smbfs.ko` rejects the oversized count, no panic).

## Mechanism (trigger → primitive → effect)

The DragonFly SMB **client** (`netproto/smb`, built into the loadable
`smbfs.ko`) parses SMB1 TRANS2 responses in `smb_t2_reply()`.
`DataCount`/`DataOffset` (`dcount`/`doff`) and `ParameterCount`/`ParameterOffset`
(`pcount`/`poff`) are `u_int16` values taken **verbatim from the (attacker- or
MITM-controlled) server response**.  The only ordering check
(`smb_rq.c:504`) validates `ddisp`, never that `dcount` fits the actual
response bytes.

`smb_t2_placedata()` then does, with no bounds check on `count`
(`sys/netproto/smb/smb_rq.c:436`):

```c
m0 = m_split(mtop, offset, M_WAITOK);          /* split the response at 'offset' */
for(len = 0, m = m0; m->m_next; m = m->m_next)
    len += m->m_len;
len += m->m_len;                                /* len = bytes available from offset */
m->m_len -= len - count;                        /* BUG: if count > len, m_len WRAPS UP */
```

When a malicious server reports `dcount` (or `pcount`) **larger than the bytes
actually present from `offset`**, `len - count` is negative, so `m->m_len`
grows by `count - len` — the last mbuf's length field is corrupted far beyond
its real data.  The corrupted chain is then concatenated into the reply
`mdchain` and consumed by `md_get_mem()`, which trusts `m_len` and reads past
the mbuf into **adjacent kernel heap** (info leak).  On an INVARIANTS kernel
the overflow is caught on free → `panic: overflowed mbuf`.

## Reproduction (live, on the running kernel)

`smb_evil.c` is a minimal C malicious SMB1 server (no python needed on the
guest).  It completes the NetBIOS session + `NegProt` (NT LM 0.12, share-level,
no-encrypt) + `SessionSetupAndX` + `TreeConnectAndX` handshake so
`mount_smbfs -N -I 127.0.0.1 //guest@127.0.0.1/share /mnt/s` connects, then on
the first `SMB_COM_TRANSACTION2` (0x32, triggered by `ls /mnt/s` →
FIND_FIRST2) it returns a TRANS2 response with:

- `TotalDataCount = DataCount (dcount) = 4096`
- `DataOffset (doff) = 55` (8 bytes of real payload follow)
- actual response payload = **8 bytes**

So `len = 8`, `count = 4096`, and `m->m_len -= 8 - 4096` inflates `m_len` by
4088.

### Observed crash (unpatched `#0` kernel + original `smbfs.ko`)
```
netsmb_dev: loaded
md_get_mem(474): incomplete copy
panic: overflowed mbuf 0xfffff801184ac200
Trace:
  m_free+0x351 -> m_free+0x351 -> m_freem+0x15 -> md_done+0x1c ->
  smb_t2_done+0x36 -> smbfs_findclose+0x86 -> Debugger("panic")
```
The `md_get_mem(474): incomplete copy` proves the corrupted chain was read
past the mbuf (the OOB read happened); the `overflowed mbuf` panic is INVARIANTS
catching the corruption on free.  On a non-INVARIANTS kernel the read past the
mbuf is a **silent kernel-heap info leak** returned to the readdir caller.

## Impact ceiling

Malicious/compromised SMB server (or network MITM) → corrupts a connecting
client kernel's mbuf chain on any TRANS2 (mount/list/stat).  Effects:
- **Kernel heap OOB read / info leak** (silent on non-INVARIANTS) — the
  directory-listing data returned to userspace contains adjacent kernel heap
  bytes (KASLR-defeat / disclosure).
- **Reliable DoS / panic** on the default GENERIC (INVARIANTS) kernel.

Trigger precondition is realistic: any user (or auto-mount facility) that
mounts/lists an attacker-controlled SMB share.  This is a CLIENT bug reached
via network; it is not a local unprivileged→root escalation, so the Phase-6
uid0 chain does not apply — the realistic ceiling is heap OOB-read/leak + DoS.

## Exploit chain

Client-side memory corruption reached by a malicious server.  Not a local
privesc; Phase 6 (uid0) N/A.  The primitive (arbitrary m_len inflation via
controlled `count`) is characterized above; on non-INVARIANTS it yields a
silent heap leak via the `md_get_mem` over-read.

## PoC changes
Authored `smb_evil.c` (malicious SMB1 server), `build.sh`, `run.sh`.  No prior
scaffolding existed.  Server uses share-level/no-encrypt negotiate so no
password/NTLM is needed (`-N`).

## Fix (`fix.diff`)
In `smb_t2_placedata()`, after computing `len` (bytes available from
`offset`), reject `count > len` with `EBADRPC` (and free the split chain)
before the corrupting subtraction.  This bounds the attacker-controlled count
to the actual response bytes.  Mirrors the defensive style already used for
the `m_split` failure.  **Supersedes** any pre-verification proposal (verified
line-accurate).

## Fix validation
- Baseline (`#0`, original `smbfs.ko`): PoC → `md_get_mem(474): incomplete
  copy` → `panic: overflowed mbuf` in `m_free`/`smb_t2_done`.  Guest down.
- Fixed `smbfs.ko` (only this `fix.diff`, rebuilt module): same PoC → the
  malicious TRANS2 is rejected (`count > len` → `EBADRPC`), client retries,
  `smb_maperror: Unmapped error 2:0`, the `ls` fails with an error, **no
  panic, guest stays up**.  `fix_status = fixed`.
- The fix is in a loadable module, so validation was a single-module rebuild
  (`make` in `sys/vfs/smbfs`) + install + re-run — no full kernel rebuild
  needed.
