# DF-0624 — OOB heap read in smb_t2_placedata via malicious TRANS2 response

## Verdict
**REPRODUCED (certain) — live kernel panic via a malicious SMB1 server.**
A remote attacker-controlled SMB server delivering a TRANS2 response whose
`ParameterCount`/`DataCount` exceed the actual payload deterministically
inflates a trailing mbuf's `m_len` past its real buffer, causing an OOB
kernel-heap read and a panic on the default GENERIC kernel (INVARIANTS ON).
Fix authored in `fix.diff`; validated before/after on a single-fix kernel
(`fix_status: fixed`).

## Mechanism (source trace, every hop cited)

`smb_t2_placedata()` (`sys/netproto/smb/smb_rq.c:423-442`) places a TRANS2
response's parameter/data bytes into the reply mdchain:

```c
423: static int
424: smb_t2_placedata(struct mbuf *mtop, u_int16_t offset, u_int16_t count,
425: 	struct mdchain *mdp)
426: {
427: 	struct mbuf *m, *m0;
428: 	int len;
429:
430: 	m0 = m_split(mtop, offset, M_WAITOK);   /* offset = server-controlled poff/doff */
431: 	if (m0 == NULL)
432: 		return EBADRPC;
433: 	for(len = 0, m = m0; m->m_next; m = m_next)
434: 		len += m->m_len;
435: 	len += m->m_len;                         /* len = REAL bytes from offset onward */
436: 	m->m_len -= len - count;                 /* count = server-controlled pcount/dcount */
...
440: 	m_cat(mdp->md_top, m0);
441: 	return 0;
442: }
```

`count` and `offset` are `u_int16_t` values decoded straight from the TRANS2
response by `smb_t2_reply()` via `md_get_uint16le`:
- `pcount`/`poff` at `smb_rq.c:490-491`
- `dcount`/`doff` at `smb_rq.c:500-501`

`smb_t2_reply()` calls `smb_t2_placedata()` unconditionally whenever
`pcount`/`dcount` are non-zero (`smb_rq.c:517-528`). The receive path
(`smb_iod.c`) validates only the 4-byte SMB magic before handing the entire raw
server mbuf chain to the parser, so the response body is fully
attacker-controlled.

When the server **lies** (`count > len`), the arithmetic
`m->m_len -= (len - count)` becomes `m->m_len += (count - len)`: the trailing
mbuf's `m_len` is inflated far past its real data buffer (up to ~65535 bytes).
**There is no `count > len` bound anywhere on this path.**

## Live reproduction (this run)

Built a minimal malicious SMB1 server (`malicious_smb_server.py`) that speaks
just enough of the DragonFly smb client's NBSS/SMB1 protocol — NEGOTIATE
(`sv_sm=0x01`, no encrypt, `sblen=0`, `caps=0`, so the client uses plaintext/no-
ext-security session setup), SESSION_SETUP_ANDX (success), TREE_CONNECT_ANDX
(success) — then answers every `SMB_COM_TRANSACTION2` (0x32) with a crafted
response whose `ParameterCount = 65535` but whose body is only 200 bytes, with
`ParameterOffset = 57` (absolute offset of the body).

Victim (guest `mount_smbfs -N -I 10.0.0.2 //guest@…/share`, then `ls`) drives
the full round trip and the kernel processes the lying TRANS2 response. With a
small lie (pcount=512, body=200, 312-byte over-read) the guest stays up but the
corruption is visible in dmesg:
```
bug: ecnt = 16705, but data is NULL (please report)   # 16705 == 0x4141, my fill bytes
md_get_mem(474): incomplete copy                        # OOB read walking the inflated chain
```
With a large lie (pcount=65535, body=200) the inflated mbuf's OOB read crosses
into unmapped memory / trips the INVARIANTS `m_free` overflow check and the
guest **panics** (`panic.txt`, captured from `dfbsd-qemu/boot.log`):
```
panic: overflowed mbuf 0xfffff801175c3c00
cpuid = 1
Trace beginning at frame 0xfffff8011799b5e8
m_free() at m_free+0x351 0xffffffff806bdff1
m_free() at m_free+0x351 0xffffffff806bdff1
m_freem() at m_freem+0x15 0xffffffff806be265
md_done() at md_done+0x1c 0xffffffff827b57bc
smb_t2_done() at smb_t2_done+0x2a 0xffffffff826163ea
smbfs_findclose() at smbfs_findclose+0x86 0xffffffff82620ec6
Debugger("panic")  ->  db>
```
This is the exact bug: `smb_t2_placedata` inflated `m_len` (count>len, no
bound) → OOB heap read (`md_get_mem incomplete copy`, attacker bytes `0x4141`
interpreted as structure counts) → the corrupted mbuf's overflow caught by the
INVARIANTS `KASSERT(M_TRAILINGSPACE(m) >= 0)` in `m_free`.

## Impact
- **CWE-125 OOB kernel-heap read** with attacker-influenced length (up to
  ~65535 bytes/response, repeatable across the multi-packet loop): silent heap
  disclosure to the mounting process (the small-lie run demonstrates the
  corruption path without panicking; a tuned lie would leak recycled kernel
  heap bytes into `ioc_rparam`/`ioc_rdata`).
- **Kernel panic / DoS** (the large-lie run) — reliable, default GENERIC,
  INVARIANTS ON.
- Triggered by a malicious SMB1 server reachable on the network; victim simply
  mounts the share and does any file op (stat/lookup/readdir/open). This is a
  realistic remote attack surface (smbfs/automounter).

This is a read-primitive bug (CWE-125), not a write primitive; per the
secondary objective the deliverable is demonstrating it genuinely manifests and
characterizing the ceiling — both the leak path and the panic are demonstrated.

## Fix (`fix.diff`)
Bound `count` against the real chain length `len` before mutating `m->m_len`:
`if (count > (u_int16_t)len) { m_freem(m0); return EBADRPC; }`. Uses the
existing `EBADRPC` convention already used throughout `smb_rq.c` for malformed
responses; `smb_t2_reply` (`:517-528`) already checks the return value and
breaks out of the receive loop, so no caller changes are needed. Matches the
finding markdown's proposed fix.

## PoC changes
`findings/poc/DF-0624/` was empty on arrival. This runner authored:
`malicious_smb_server.py` (the malicious SMB1 server), `run_live.sh` (driver
that runs the server on the host + drives the guest client), `fix.diff`,
`build.sh`, `run.sh`, `README.md`, `VERDICT.md`, `manifest.json`, `env.txt`,
plus the captured evidence `panic.txt`, `server_panic.log`, `run.log`.
