# DF-5396 — urndis(4) bulk RX: 2-byte kernel heap overflow via RNDIS
`rm_datalen` (device-controlled) up to MCLBYTES copied after `m_adj(ETHER_ALIGN)`

## What

`sys/bus/u4b/net/if_urndis.c` — `urndis_bulk_read_callback()`. The RX
sanity chain accepts `msg.rm_datalen` up to and including `MCLBYTES`
(2048) — `if_urndis.c:859`:

```c
} else if (msg.rm_datalen > (uint32_t)MCLBYTES) {   /* rejects only > 2048 */
        ...
} else if (msg.rm_datalen > (uint32_t)(MHLEN - ETHER_ALIGN)) {
        m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);   /* 2048-byte cluster */
...
if (m != NULL) {
        m_adj(m, ETHER_ALIGN);                      /* m_data = cluster + 2 */
        usbd_copy_out(pc, ..., m->m_data, msg.rm_datalen);  /* up to 2048 */
```

After `m_adj(m, ETHER_ALIGN)` only `MCLBYTES - ETHER_ALIGN` (2046)
bytes are writable from `m->m_data`, but the copy uses the unchecked
`rm_datalen`. A data message with `rm_datalen` of 2047 or 2048 writes
1–2 fully device-controlled bytes past the end of the 2048-byte
`kmalloc(MCLBYTES, M_MBUFCL)` cluster backing the mbuf
(`mclmeta_ctor`, `sys/kern/uipc_mbuf.c:608-624`).

DragonFly synced this driver from FreeBSD r261544 (2014-02-06), before
FreeBSD commit **a51f9801806b** ("Fix for unaligned IP-header ...
Adjust a length check to include the 2-byte ethernet alignment while at
it.", hselasky, 2015-11-06) fixed exactly this:

```diff
-	} else if (msg.rm_datalen > (uint32_t)MCLBYTES) {
+	} else if (msg.rm_datalen > (uint32_t)(MCLBYTES - ETHER_ALIGN)) {
...
 		if (m != NULL) {
+			m->m_len = m->m_pkthdr.len = msg.rm_datalen + ETHER_ALIGN;
 			m_adj(m, ETHER_ALIGN);
```

DragonFly's own shared helper `uether_rxbuf()` enforces the correct
bound (`sys/bus/u4b/net/usb_ethernet.c:578`:
`if (len < ETHER_HDR_LEN || len > MCLBYTES - ETHER_ALIGN) return (1);`)
— only this driver's hand-rolled path diverges.

## Reproduction (baseline, stock kernel)

The malicious usbredir RNDIS device (`usbredir_rndis.py`, driven via
QEMU `-device usb-redir`) attaches as `ue0` and pushes crafted RNDIS
data messages on the bulk IN pipe:

```
urndis0: <vendor 0x0525 product 0xa4a2, class 2/0, rev 2.00/0.00, addr 2> on usbus0
ue0: <USB Ethernet> on urndis0
ue0: MAC address: 02:00:de:ad:be:ef
# host$ printf 'send 2046 36 1\nsend 2047 36 1\nsend 2048 36 1\n' | ctrl:45601
ue0  Ipkts=4  Ierrs=0  Ibytes=7641     # = 1500+2046+2047+2048, ALL accepted
```

2047 and 2048 are accepted on the stock driver — by the source-level
arithmetic of the path taken, those copies ran 1 and 2 bytes past the
cluster. ~2600 further 2048-length frames were pushed (with guest-side
cluster churn) without a panic: the 2-byte write lands in the *next
2048-byte slab chunk* of DF's slab allocator
(`sys/kern/kern_slaballoc.c`) — when that chunk is FREE its first word
is the `SLChunk.c_Next` freelist pointer, so the write corrupts the
low 16 bits of a slab freelist link; when it is in use it corrupts
another packet's cluster (silent). The partial-pointer redirect stays
inside mapped slab KVA, so no immediate fault — a *silent, exploitable*
primitive, not a crashy one.

## Fix validation (patched if_urndis.ko, fix.diff)

```
# host$ send 2046 / send 2047 / send 2048   (same PoC, patched module)
ue0  Ipkts=1  Ierrs=2  Ibytes=2046
```

2046 still accepted (no regression); 2047/2048 now rejected with
IERRORS — exactly the lengths whose copies overflow.

## Run it

See `boot_rig.sh` / `boot_rig_cold.sh` (QEMU rig), `usbredir_rndis.py`
(the malicious device; control plane on TCP 45601), and VERDICT.md.
Guest prerequisites: `mv /boot/kernel/if_cdce.ko /root/` (cdce's VPI
match on 0525:a4a2 otherwise claims the device — and panics via the
DF-5334 bug), `kldload if_urndis`, `ifconfig ue0 up`.
