Unbounded sink-driven go_again retry loop in intel_dp_check_mst_status livelocks the DP hotplug workqueue (MST HPD DoS)
Summary
intel_dp_check_mst_status() re-reads the sink's ESI interrupt block and jumps back to 'go_again' whenever drm_dp_mst_hpd_irq() reports the event handled. 'handled' is set exclusively from sink-supplied bytes (esi[0] sink-count != cached, esi[1] & DOWN_REP_MSG_RDY, esi[1] & UP_REQ_MSG_RDY). The clear-write of esi[1..3] back to DP_SINK_COUNT_ESI+1 is a request the sink may ignore. A malicious MST-capable branch device therefore keeps the loop spinning forever in the hotplug dp_wq kthread - permanent livelock, the intel_display_power_get() reference taken at :5684 is never released (display power well wedged, runtime PM stuck), all subsequent DP/HDMI hotplug processing on all ports starves, one core pegged. enable_dp_mst defaults to true; code ships in stock /boot/kernel/i915.ko (nm: intel_dp_check_mst_status at 0xcbd80). Attacker with brief physical access plugs an evil MST dock/monitor (or flashes a real dock's branch firmware). Hardware-gated: virtio-only guest cannot execute the path; source-certain.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3626 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.8 KB | β raw | |
| VERDICT.md | β | 3.9 KB | β raw | |
| fix.diff | β | 1.3 KB | view raw | |
| verdict.json | β | 3.7 KB | view raw | |
| manifest.json | β | 855 B | view raw | |
| env.txt | β | 245 B | view raw |
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; labelgo_againat 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 containgot esi %3ph/got esi2 %3phfrom 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:
- reads the 14-byte ESI block from the sink (
intel_dp_get_sink_irq_esi, intel_dp.c:4293); drm_dp_mst_hpd_irq()(drm_dp_mst_topology.c:2450) sets*handled = truewheneveresi[0] & 0x3f != mgr->sink_count, oresi[1] & DP_DOWN_REP_MSG_RDY, oresi[1] & DP_UP_REQ_MSG_RDY;- if
handled, the driver writesesi[1..3]back toDP_SINK_COUNT_ESI+1to 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_wqkthread spins forever insideintel_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:
- 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). - Assert a short HPD pulse.
- Answer every native AUX read of
DP_SINK_COUNT_ESI (0x2002..0x200f)withesi[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.
VERDICT β DF-3626: intel_dp_check_mst_status() unbounded go_again loop
verdict: inconclusive (runtime) / certain (source-level) β reproduced=0,
impact=none (nothing demonstrable on this guest), expected impact on real HSW+
hardware with an attached hostile MST branch device: DoS (kthread livelock +
permanent power-well reference + display hotplug starvation).
Why runtime verification was not possible
The audit guest is QEMU/KVM with a virtio display device:
$ pciconf -l | grep -i -E "vgapci|display" vgapci0@pci0:0:2:0: class=0x030000 card=0x11001af4 chip=0x11111234 rev=0x02 hdr=0x00 $ kldstat | grep drm β (nothing) $ ls /dev/dri β NO_DEV_DRI
chip 0x1111:1234 is the QEMU/virtio display adapter. i915.ko's PCI ID table
(i915_pciids.h β i915_pci_register) matches only Intel vendor 0x8086 IDs, so
the driver never attaches; intel_dp_hpd_pulse() can never run. No amount of
setup inside the guest substitutes for Intel display hardware plus a hostile
DP branch device on the physical connector.
Proof the vulnerable code ships in the default install
$ nm /boot/kernel/i915.ko | grep intel_dp_check_mst_status 00000000000cbd80 t intel_dp_check_mst_status $ strings /boot/kernel/i915.ko | grep -E "got esi" got esi %3ph got esi2 %3ph <- the re-read branch inside the go_again loop
i915.ko is built and shipped in /boot/kernel of the stock DragonFly
6.5-DEVELOPMENT install booted here, and enable_dp_mst defaults to true
(sys/dev/drm/i915/i915_params.h:69).
Line-accurate path trace (this tree)
| step | where | note |
|---|---|---|
| 1 | sys/dev/drm/i915/i915_irq.c β intel_hpd_irq_handler() |
platform irq handlers call it for each short pulse |
| 2 | sys/dev/drm/i915/intel_hotplug.c:496-497 |
queue_work(dev_priv->hotplug.dp_wq, &dev_priv->hotplug.dig_port_work) |
| 3 | sys/dev/drm/i915/intel_hotplug.c:313 |
dig_port->hpd_pulse(dig_port, long_hpd) β intel_dp_hpd_pulse |
| 4 | intel_dp.c:5684 |
intel_display_power_get() β ref never dropped if we never return |
| 5 | intel_dp.c:5687 |
intel_dp_check_mst_status(intel_dp) (is_mst == true) |
| 6 | intel_dp.c:4293 |
intel_dp_get_sink_irq_esi() β 14-byte sink read, attacker data |
| 7 | drm_dp_mst_topology.c:2450-2473 |
handled = true on sink-count change / DOWN_REP_MSG_RDY / UP_REQ_MSG_RDY β all sink-controlled bits |
| 8 | intel_dp.c:4309-4317 |
clear-write of esi[1..3] to DP_SINK_COUNT_ESI+1 β a request; sink may ignore |
| 9 | intel_dp.c:4319-4322 |
re-read succeeds β goto go_again β no bound anywhere in the function |
The only exits are: ESI read failure (intel_dp_get_sink_irq_esi != 14 bytes)
or handled == false. Both are fully under the sink's control.
Note also the esi[10] sub-analysis: drm_dp_channel_eq_ok(&esi[10], β¦)
(drm_dp_helper.c:60-77) only indexes link_status[0..2] β esi[10..12], in
bounds of the 14-byte array β the memory-safety reads are fine; the defect is
purely the unbounded control loop.
Exploit chain
Not a memory-corruption primitive; no escalation chain. Chain is:
evil dock β MST handshake β 1 short HPD β infinite go_again β dp_wq kthread
livelock + leaked display power-well reference + all-port hotplug starvation
β display subsystem dead until reboot (DoS).
Fix validation
fix.diff bounds the loop to 8 bounces and schedules a hotplug event on
exhaustion. Not buildable/testable on this guest (no Intel GPU to modeset, and
the path needs hostile HW); 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.
Fix verification
not_testablefix.diff bounds the go_again loop to 8 bounces and emits a hotplug event + DRM_ERROR on exhaustion; could not be build-validated on this guest because the target path requires Intel display hardware (no i915 attach on virtio guest) and a hostile sink to drive it.
findings/poc/DF-3626/fix.diff
Confirmed kernel references
- sys/dev/drm/i915/intel_dp.c:4287
- sys/dev/drm/i915/intel_dp.c:4294
- sys/dev/drm/i915/intel_dp.c:4306
- sys/dev/drm/i915/intel_dp.c:4319
- sys/dev/drm/i915/intel_dp.c:5684
- sys/dev/drm/i915/intel_dp.c:5687
- sys/dev/drm/drm_dp_mst_topology.c:2450
- sys/dev/drm/drm_dp_mst_topology.c:2462
- sys/dev/drm/i915/intel_hotplug.c:496
Detail
Exploit chain
evil DP branch device -> advertise DPCD rev>=0x12 + DP_MSTM_CAP(MST) -> i915 enters MST -> device asserts one short HPD -> kernel reads ESI (0x2002..0x200f) -> device always returns esi[1]|DP_DOWN_REP_MSG_RDY and ignores the clear-writes -> intel_dp_check_mst_status() loops 'go_again' forever -> dp_wq kthread busy-spins, display power-well refcount leaked, all-port HPD dead until reboot (DoS). No memory-corruption primitive, no escalation path.
Evidence (decisive lines)
VERDICT.md (full path trace table + guest hardware proof); README.md (loop anatomy, attacker spec, consequences); env.txt (guest pci/kld state); fix.diff (bounded-bounce patch). Decisive lines: intel_dp.c:4294 (go_again label), 4306-4322 (handled->clear->re-read->goto), drm_dp_mst_topology.c:2455-2470 (handled set from sink bits only).
PoC changes
No seed PoC existed; this is a hardware-in-the-loop bug (malicious DP sink). The evidence pack documents the exact evil-sink DPCD behavior spec instead of a runnable host program, plus proof the vulnerable code ships in the default-install kernel module.
Verified recommended fix
Bound the go_again bounce count (e.g. 8) in intel_dp_check_mst_status(); on exhaustion schedule a full hotplug reprobe instead of looping.
Verdict
Source-level certain, runtime inconclusive: intel_dp_check_mst_status() (sys/dev/drm/i915/intel_dp.c:4294-4323) retries the sink-driven ESI handshake via 'goto go_again' with no iteration bound; drm_dp_mst_hpd_irq() (sys/dev/drm/drm_dp_mst_topology.c:2450-2473) returns handled=true whenever the sink keeps DP_DOWN_REP_MSG_RDY/UP_REQ_MSG_RDY set or toggles the 6-bit sink count, so a malicious MST branch device livelocks the dp_wq hotplug kthread forever, leaks the intel_display_power_get() reference taken at intel_dp.c:5684, and starves all display hotplug handling until reboot. Could not be exercised at runtime because the audit guest is virtio-only (vgapci chip 0x1111:1234): i915 never PCI-attaches and /dev/dri does not exist; however the vulnerable function ships in the stock /boot/kernel/i915.ko (nm: 00000000000cbd80 t intel_dp_check_mst_status; loop debug strings present) and enable_dp_mst defaults to true (i915_params.h:69). Expected impact on real HSW+ hardware: kernel DoS via evil dock/monitor (physical-access vector).
No comments yet.