# VERDICT — DF-3882

**status: not_reproduced** (kernel path hardware-gated) / impact: none
*demonstrated* / confidence: likely (source-certain race, attacker-steerable
window, model-demonstrated interleave)

## Source trace (why the UAF is real)

1. `drm_dp_send_link_address()` queues txmsg, waits in
   `drm_dp_mst_wait_tx_reply()` for up to `4 * HZ` (:808-810). On timeout
   with `state == DRM_DP_SIDEBAND_TX_SENT` it clears
   `mstb->tx_slots[txmsg->seqno]` (:829-832) under `qlock`, returns `-EIO`,
   and the caller immediately `kfree(txmsg)` (:1649). SENT means: fully
   transmitted, off `tx_msg_downq` (list_del at :1563), referenced only by
   the slot — which the timeout path just cleared. Nothing else keeps it
   alive.
2. Concurrently, the HPD path (`drm_dp_mst_hpd_irq` →
   `drm_dp_mst_handle_down_rep`) reads `txmsg = mstb->tx_slots[slot]` under
   `qlock` (:2336-2339), then **drops the lock**, does
   `drm_dp_sideband_parse_reply(&mgr->down_rep_recv, &txmsg->reply)`
   (:2353) — a union write of up to ~700 bytes (link_addr: 16 ports × ~40B)
   whose content is the hub's reply — then re-locks and writes
   `txmsg->state = DRM_DP_SIDEBAND_TX_RX` (:2362) and reads
   `mstb->tx_slots[slot]` (:2363) from an mstb reference already dropped at
   :2359.
3. Interleaving: sender's timeout fires inside the handler's unlocked window
   → sender's `kfree(txmsg)` precedes the handler's `:2353` / `:2362`
   writes → both writes land in freed memory. The handler never re-checks
   the slot before using txmsg; there is no txmsg refcount anywhere in this
   file (kzalloc'ed, owned by the sender, no kref).

Reachability prerequisites: DP MST active (malicious hub), SMP, and the reply
released at the 4 s deadline — all controlled by the hub; retry unlimited.

## What was executed

`txmsg_uaf_race_model.c` on the DragonFly guest (cc 8.3, pthreads): sender
thread times out at a 200 ms deadline and munmap()s the txmsg page (models
kfree); handler thread captures the slot 1 ms before the deadline, sleeps
through the "parse", then writes `state` — **SIGSEGV in 3/3 runs** because
the page is gone (`run.log`; exit 42 is the model's UAF marker). This
demonstrates the interleaving is schedulable, not the kernel bug.

## Why not `reproduced`

`drm_dp_mst_handle_down_rep()` requires a DP MST-capable GPU raising
DOWN_REP_MSG_RDY; the QEMU guest has none (`/dev/dri` absent, no drm kld —
`env.txt`). No software-only entry exists. Classification: **not_reproduced**,
hardware-gated.

## Fix

`fix.diff`:
- parse into a local `struct drm_dp_sideband_msg_reply_body` before touching
  txmsg;
- under `qlock`, publish into txmsg **only if** `mstb->tx_slots[slot] ==
  txmsg` still holds (i.e. the sender's timeout path hasn't taken it);
- move `drm_dp_put_mst_branch_device(mstb)` after the last `mstb` access,
  fixing the mstb-after-put read at :2363 as well.

Kernel-side fix validation impossible on this guest (no DP hardware):
`fix_status: not_testable`.

## 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.
