# DF-3886 — `drm_dp_sideband_msg_build()` accumulates `curlen` into `msg[256]` without bound → 54-byte hub-controlled OOB write

**File:** `sys/dev/drm/drm_dp_mst_topology.c:371-377` (pass 2)
**Class:** kernel memory corruption (CWE-787), malicious MST hub
**Severity:** High / confidence: certain (source-level; hardware-gated)

## What

Distinct sink from the two pass-1 knowns:

- **DF-1264** is the `chunk[48]` array overrun in the *non-header continuation*
  branch (`memcpy(&msg->chunk[msg->curchunk_idx], replybuf, replybuflen)`,
  line 367).
- **DF-1265** is the *reply parsers'* OOB reads past `curlen`.

This finding is the **completed-chunk accumulation into `msg->msg[256]`**:

```c
371: 	if (msg->curchunk_idx >= msg->curchunk_len) {
372: 		/* do CRC */
373: 		crc4 = drm_dp_msg_data_crc4(msg->chunk, msg->curchunk_len - 1);
374: 		/* copy chunk into bigger msg */
375: 		memcpy(&msg->msg[msg->curlen], msg->chunk, msg->curchunk_len - 1);
376: 		msg->curlen += msg->curchunk_len - 1;
377: 	}
```

`curchunk_len` is the hub-controlled 6-bit `msg_len` from the sideband header
(`hdr->msg_len = buf[idx] & 0x3f`, line 190 — up to 63, so up to 62 bytes
appended per completed chunk). `curlen` is a `u8` **never validated against
`sizeof(msg->msg) == 256`**. A reply whose body exceeds 256 bytes — trivially
a crafted LINK_ADDRESS ACK with 15 ports: 18 + 15×21 = 333 bytes — walks the
append offset past 256 and copies hub-controlled reply bytes past the array.

## Bounds of the primitive (exact)

`dst = curlen ≤ 255`, `len = curchunk_len-1 ≤ 62` → max end offset 317 →
**up to 61 bytes past `msg[256]`** (u8 wrap keeps it bounded; measured 54 for
the natural 62-byte chunk cadence — `run.log`). The bytes past the array land
on, in order (`drm_dp_mst_helper.h:215-226`): `curchunk_len`,
`curchunk_idx`, `curchunk_hdrlen`, `curlen`, `have_somt`, `have_eomt`,
`initial_hdr` (24B: `lct`, `lcr`, `rad[8]`, flags), and — because
`down_rep_recv` is immediately followed by `up_req_recv` in
`struct drm_dp_mst_topology_mgr` (`drm_dp_mst_helper.h:464-470`) — the first
bytes of **`up_req_recv.chunk[48]`**, the parse buffer of the *other*
sideband channel.

## Consequences

- Hub-controlled overwrite of `have_eomt`/`curlen`/`initial_hdr` lets the hub
  desynchronize the message assembler mid-stream (forge `initial_hdr.lct`
  /`rad` used by `drm_dp_mst_handle_down_rep():2324` device lookup, force
  premature eomt on truncated bodies feeding the parsers of DF-1265, etc.).
- Data smuggling into `up_req_recv` pre-seeds the up-request parser with
  bytes the hub never sent as an up-request, compounding parser confusion.
- All writes stay inside the `drm_dp_mst_topology_mgr` allocation (embedded
  in the driver's `intel_dp`/`radeon_connector` heap object) — type-stable
  corruption of scaler fields, not a pointer smash; still a deterministic,
  fully content-controlled OOB write from an unprivileged physical attacker.

## PoC

Hardware-gated (same reason as DF-3881). `sbmsg_accum_model.c` models the
assembler arithmetic faithfully (same field order); build `./build.sh`, run
`./run.sh`. Expected output: first OOB append at chunk 5
(`memcpy(&msg[248], chunk, 62)` → 54 bytes past `msg[256]`), and the
clobbered-field dump showing `0xA5` reply bytes in `curchunk_len`, `curlen`,
`have_somt`, `have_eomt`, `initial_hdr[0]`, `up_req_recv.chunk[0]`.

## Fix

`fix.diff`: validate the accumulated length before the append
(`if ((int)msg->curlen + (int)msg->curchunk_len - 1 > (int)sizeof(msg->msg)) return false;`)
— mirrors the bound upstream added when it reworked this builder.
