# DF-3626 — Unbounded sink-driven retry loop in intel_dp_check_mst_status (MST HPD livelock)

- **File:** `sys/dev/drm/i915/intel_dp.c:4294-4323` (loop body; label `go_again` at 4294)
- **Class:** CWE-835 (loop with unreachable exit condition driven by untrusted data)
- **Severity:** Medium (kernel-thread livelock / display-subsystem DoS; attacker = malicious
  MST-capable DisplayPort branch device — "evil dock" / "evil monitor", physical plug-in)
- **Status on this audit's QEMU guest:** `inconclusive` — the guest is virtio-only
  (`vgapci0@pci0:0:2:0 chip=0x11111234`), the i915 driver can never PCI-probe-attach,
  so no DP aux/MST path can execute. Source-level trace below is line-accurate and the
  vulnerable function **is present in the shipped `/boot/kernel/i915.ko`**
  (`nm` → `00000000000cbd80 t intel_dp_check_mst_status`; strings contain
  `got esi %3ph` / `got esi2 %3ph` from the loop body).

## What the code does

`intel_dp_hpd_pulse()` (intel_dp.c:5656) is invoked for short HPD pulses from
`intel_hotplug.c` `dig_port_work` on `dev_priv->hotplug.dp_wq`
(intel_hotplug.c:496-497). For an MST-active port it calls
`intel_dp_check_mst_status()` (intel_dp.c:5687), which:

1. reads the 14-byte ESI block from the sink (`intel_dp_get_sink_irq_esi`,
   intel_dp.c:4293);
2. `drm_dp_mst_hpd_irq()` (drm_dp_mst_topology.c:2450) sets `*handled = true`
   whenever `esi[0] & 0x3f != mgr->sink_count`, or `esi[1] & DP_DOWN_REP_MSG_RDY`,
   or `esi[1] & DP_UP_REQ_MSG_RDY`;
3. if `handled`, the driver writes `esi[1..3]` back to `DP_SINK_COUNT_ESI+1`
   to *clear* the interrupt bits (intel_dp.c:4309-4317), re-reads the ESI, and —
   if the read succeeds — **`goto go_again`** (intel_dp.c:4319-4323).

There is **no iteration bound** on the `go_again` loop. A branch device that
simply ignores the clear-write and keeps returning the same ESI payload (or
toggles the 6-bit sink-count field each read) makes `handled` true on every
pass → infinite loop.

## Consequences (source-level)

- The `dp_wq` kthread spins forever inside `intel_dp_check_mst_status()`
  (each iteration performs aux reads/writes + `drm_dp_mst_handle_down_rep()`
  sideband processing — continuous work, not a sleeping stall).
- `intel_display_power_get(dev_priv, intel_dp->aux_power_domain)` taken by the
  caller at intel_dp.c:5684 is **never released** → the AUX/PPS display power
  well refcount is leaked permanently → runtime PM of the display block wedged.
- All subsequent hotplug events queued on `dev_priv->hotplug.dp_wq`
  (every DP/HDMI hotplug on all ports) starve → display subsystem unusable
  until reboot.
- CPU: one core pegged in the workqueue thread.

## Trigger (attacker behavior spec — no host-side "program" exists for this class)

A malicious DP sink is hardware. The minimal evil device implements:

1. DPCD 0x000 = rev `0x12`, `DP_MSTM_CAP (0x0011)` = `DP_MST_CAP|UPSTREAM_IS_SRC`,
   downstream-port-present bit set → i915 enters MST mode
   (`intel_dp_can_mst()`, intel_dp.c:4036).
2. Assert a short HPD pulse.
3. Answer every native AUX read of `DP_SINK_COUNT_ESI (0x2002..0x200f)` with
   `esi[1] |= DP_DOWN_REP_MSG_RDY` (0x02) and never clear it regardless of any
   write to 0x2003-0x2005.

Same effect with less hardware: alternate the low 6 bits of `esi[0]`
(sink count) between two values on successive reads.

This is implementable today on an FPGA DP source/sink board or by patching a
cheap USB-C MST dock's branch firmware — the standard "evil dock" threat model
for DP parsing bugs.

## Fix

Bound the number of `go_again` bounces (see `fix.diff`): after N handled
re-reads (e.g. 8), break out and let the normal HPD/reprobe machinery take
over. Also `drm_kms_helper_hotplug_event()` so userspace sees the churn.

## Expected post-fix behavior

The loop terminates after at most N iterations; the worst a malicious sink
achieves is N wasted aux transactions per HPD pulse.
