# VERDICT — DF-3881

**status: not_reproduced** (hardware-gated) / impact: none *demonstrated* /
confidence: certain (source-level)

## Why the bug is real (kernel path, line by line)

1. `drm_dp_mst_topology_mgr_set_mst()` creates `mst_primary` with
   **lct = 1** (`drm_dp_add_mst_branch_device(1, NULL)`, :2140).
2. Each LINK_ADDRESS reply enumerates ports; for a port with
   `peer_device_type == DP_PEER_DEVICE_MST_BRANCHING`,
   `drm_dp_add_port()` (:1140) calls `drm_dp_port_setup_pdt()` (:1196).
3. `drm_dp_port_setup_pdt()` (:1071) declares **`u8 rad[6]`** and calls
   `drm_dp_calculate_rad(port, rad)` (:1083), which computes
   `idx = (parent_lct - 1) / 2`, does `memcpy(rad, port->parent->rad, idx + 1)`
   and `rad[idx] |= port->port_num << shift` and **returns
   `parent_lct + 1`** (:1052-1066). `parent->rad` is `u8 rad[8]`
   (drm_dp_mst_helper.h:125). For `parent_lct ≥ 13`, `idx ≥ 6`:
   - `memcpy(rad, parent->rad, 7..8)` writes 1-2 bytes past `rad[6]`;
   - `rad[6..127] |= port_num << shift` writes a 4-bit hub-chosen value at a
     hub-chosen stack offset (offset = depth).
4. The returned lct = parent+1 is passed to `drm_dp_add_mst_branch_device()`
   (:1085), which does `memcpy(mstb->rad, rad, lct / 2)` into the heap
   object's `u8 rad[8]` (:848-850): for lct ≥ 17 this copies ≥ 9 bytes
   (up to 127), a linear heap overflow of the freshly allocated
   `drm_dp_mst_branch` (~200-byte kmalloc zone).
5. Every subsequent down-request to that device runs
   `set_hdr_from_dst_qlock()` (:1484-1485)
   `memcpy(hdr->rad, mstb->rad, mstb->lct / 2)` into the stack
   `drm_dp_sideband_msg_hdr.rad[8]` — same ≥17 overflow of the caller's frame
   (`process_single_tx_qlock()`, :1496-1501).
6. The enumeration is **recursively unbounded**:
   `drm_dp_send_link_address()` reply loop (:1639-1641) →
   `drm_dp_add_port()` → `drm_dp_send_link_address(port->mstb)` (:1198) —
   one nested frame-set per claimed hub level, executed synchronously inside
   the topology/probe worker. DFBSD kernel stacks are 16KB
   (`KSTACK_PAGES` default 4); at ~300 bytes/level the chain reaches the
   guard page around depth 54 — a hub only needs 54 fabricated replies.
7. `drm_dp_mst_dump_mstb()` (:2954-2964) writes `prefix[i] = '\t'` for
   `i < mstb->lct` into `char prefix[10]` — OOB for lct ≥ 10; reachable from
   root-only radeon debugfs (radeon_dp_mst.c:775).

No `DP_MAX_LCT` bound, no depth check, no rad-size validation exists anywhere
in this file (verified: only three `lct` comparisons exist — :849, :1058,
:1484 — all `> 1` tests). Upstream Linux later capped this class of growth;
this tree has no such cap.

## What was executed on the guest

`mst_lct_model.c` — exact arithmetic model of sinks 3/4/5/6 (built with the
guest's `cc 8.3`, run as unprivileged `maxx`): first stack-OOB at
parent_lct=13, heap/hdr OOB at parent_lct=17, prefix OOB at lct=10,
stack-exhaustion estimate ≈ depth 54 (full output in `run.log`).

## Why not `reproduced`

The sink lives in the DisplayPort MST driver path, which requires a GPU with
DP 1.2 MST that enumerates through drm. The QEMU guest has no GPU:
`/dev/dri`, `/dev/fb`, `/dev/iic` all absent, no drm/i915/radeon kld loaded
(`env.txt`). There is no software-only entry into
`drm_dp_calculate_rad()`/`drm_dp_add_mst_branch_device()` (all callers descend
from `drm_dp_mst_topology_mgr_set_mst()`, invoked by the GPU driver's HPD
handler). Classification: **not_reproduced**, hardware-gated, source-certain.

## Exploit chain (for the maintainer's threat model)

physical malicious hub → 12-deep claimed topology → 1-2 byte stack overwrite
in `drm_dp_port_setup_pdt()` with `rad[idx] |= port_num<<shift`
(semi-controlled); 16-deep → 1-byte heap overwrite past `mstb->rad[8]`
inside `drm_dp_mst_branch` (adjacent fields: lct, num_ports, msg_slots,
list_head, port_parent, mgr, tx_slots[2], guid[16] — corrupting the list_head
or tx_slots of the next allocation yields pointer-graft primitives); 54-deep →
kernel stack exhaustion (guard-page panic, deterministic DoS). Grooming the
`drm_dp_mst_branch` kmalloc zone is straightforward for a device that also
speaks i2c/EDID (same allocation cache churns on every hotplug cycle).

## Fix validation

fix.diff authored against the read-only sys/ tree (never applied): caps lct at
16 in `drm_dp_add_mst_branch_device()` (kfree on reject), refuses RAD
computation for parent_lct > 12 in `drm_dp_calculate_rad()`, sizes
`prefix[18]` in `drm_dp_mst_dump_mstb()`. Cannot be kernel-built/validated on
this guest (no GPU to boot a DRM console against; kernel build of the drm
module itself is possible but the fixed path is unreachable — recorded as
`fix_status: not_testable` per the honest-classification rules for
hardware-gated findings).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
