amdgpu_atombios_dp_link_train_cr has no overall iteration bound: malicious DP sink oscillating adjust-request voltage wedges the modeset path forever (unkillable task + mode_config.mutex) β line-for-line twin of DF-4589 (radeon)
Summary
The CR while(1)'s four exits are ALL sink-controlled, and the tries counter resets on ANY voltage change - a spec-violating sink alternating its ADJUST_REQUEST voltage while never reporting CR_DONE defeats every terminator; each iteration sleeps >=100us (up to ~508ms via crafted DPCD[0x0E]). Runs in the modeset path holding mode_config.mutex: unkillable task plus every subsequent DRM ioctl blocks forever - permanent local physical DoS of the display subsystem. Hardware-gated on guest; trigger compile-checked. Max-iteration-cap fix (mirrors DF-4589).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4652 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.4 KB | β raw | |
| VERDICT.md | β | 5.1 KB | β raw | |
| trigger.c | β | 5.6 KB | view raw | |
| build.sh | β | 169 B | view raw | |
| run.sh | β | 175 B | view raw | |
| build.log | β | 44 B | view raw | |
| run.log | β | 321 B | view raw | |
| guest_probe.log | β | 321 B | view raw | |
| env.txt | β | 820 B | view raw | |
| fix.diff | β | 575 B | view raw | |
| manifest.json | β | 1.4 KB | view raw | |
| verdict.json | β | 3.7 KB | view raw |
DF-4652 β amdgpu CR link-training loop has no overall iteration bound
Finding file: sys/dev/drm/amd/amdgpu/atombios_dp.c (pass 2) Line span: 605-644 (clock-recovery loop), decisive: 619-635. Twin of DF-4589 (radeon, sys/dev/drm/radeon/atombios_dp.c:684-722) β the two loops are line-for-line identical in shape.
What the bug is
amdgpu_atombios_dp_link_train_cr() loops until clock recovery succeeds,
the DPCD read fails, every lane reports DP_TRAIN_MAX_SWING_REACHED, or the
sink requests the same voltage 5 consecutive times (dp_info->tries,
reset to 0 on any change at atombios_dp.c:634-635). A spec-violating DP
sink that never achieves CR, always requests voltage swing below level 3,
and alternates its adjust-request voltage between two levels (0.4V/0.6V)
defeats every terminator: the 5-try counter is reset on each alternation
and the loop spins forever, sleeping >= 100 us (or up to ~508 ms with a
crafted DPCD[0x0E] TRAINING_AUX_RD_INTERVAL) plus an AUX transaction per
iteration (drm_dp_helper.c:121-134).
The channel-equalization loop is bounded (tries > 5 unconditional break
at atombios_dp.c:685 with unconditional increment at :695); only the CR
loop is unbounded.
Impact
amdgpu_atombios_dp_link_train() (atombios_dp.c:710) runs from
amdgpu_atombios_encoder_setup_dig() (atombios_encoders.c:1441,
ATOM_ENABLE branch), invoked from amdgpu_atombios_encoder_dpms()
(atombios_encoders.c:1477) β the .dpms encoder-helper callback
registered by dce_v6/v8/v10/v11 (e.g. dce_v10_0.c:3421). That is the DRM
modeset path taken with dev->mode_config.mutex held
(drm_crtc.c:601/776, drm_crtc_helper.c:101/139) β either the HPD
workqueue thread or the userspace thread that issued MODE_SETCRTC / a
DPMS property change. Outcome: an unkillable task in a
non-interruptible kernel loop plus a permanently wedged display subsystem
(all subsequent DRM ioctls on the card block). Precondition is a crafted
DisplayPort sink (MCU dongle) β the same physical malicious-sink threat
model already accepted for DF-1730 / DF-1778 / DF-4589. No memory
corruption (lane counts are handled under DF-1790's separate finding).
Reproduce (hardware required)
- Host with an amdgpu (AMD) GPU driven through the atom BIOS table path
(
amdgpu_atombios_dp_process_aux_ch). - Attach a DP dongle whose DPCD link-status replies follow trigger.c (never CR_DONE; alternate ADJUST_REQUEST voltage 0/1; DPCD[0x0E]=0).
- Trigger a modeset on the connector (hotplug, or
xrandr --auto). - EXPECTED (vulnerable): the issuing task hangs forever in amdgpu_atombios_dp_link_train_cr; dmesg shows neither "clock recovery tried 5 times" nor "clock recovery failed"; further DRM ioctls block. PATCHED: loop exits after the hard cap with "clock recovery exceeded max loop attempts" + "clock recovery failed".
On the audit guest this is hardware-gated: no AMD GPU exists (Bochs 0x1234:0x1111 β run.log / env.txt), so the verdict is not_reproduced with the defect proven from source.
Build / run on guest
./build.sh # compiles the documented sink state machine ./run.sh # probes guest hardware; records absence in run.log
Fix
fix.diff adds an overall iteration cap (50) to the CR loop, mirroring the DF-4589 fix for the radeon twin.
DF-4652 VERDICT β amdgpu clock-recovery loop unbounded (DF-4589 twin)
STATUS: not_reproduced (hardware-gated). Defect certain from source. IMPACT ceiling: unkillable modeset task holding mode_config.mutex -> permanent display-subsystem wedge (local physical DoS). No memory corruption.
Reproduced?
NO β on this guest it CANNOT run: reproduction needs an AMD GPU driven by the amdgpu atom path plus a crafted DisplayPort sink. The audit guest has no 0x1002 PCI device at all (Bochs VGA 0x1234:0x1111 β run.log, env.txt), and QEMU cannot emulate a malicious DP sink behind atom ProcessAuxChannelTransaction. This mirrors DF-4589 (radeon twin), which was hardware-gated identically.
Why the defect is certain from source
amdgpu_atombios_dp_link_train_cr() (sys/dev/drm/amd/amdgpu/
atombios_dp.c:588-655) is a while (1) loop whose four exits are ALL
sink-controlled:
- aux read failure β :608-612 (sink answers fine -> never taken)
- CR achieved β :614-617 (sink never sets CR_DONE -> never taken)
- all lanes at MAX_SWING_REACHED β :619-626 get_adjust_train() (:629-233 / :206-241) sets DP_TRAIN_MAX_SWING_REACHED only when the sink's requested voltage reaches level 3 (v >= DP_VOLTAGE_MAX, :229-230). A sink that always requests level 0/1 keeps this exit closed forever.
- 5 consecutive identical voltages β :628-635 if ((train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == voltage) ++tries; if (tries == 5) break; else tries = 0; <-- THE DEFECT (:634-635)
A sink that ALTERNATES its ADJUST_REQUEST voltage between level 0 and
level 1 on every link-status read makes the equality test fail on every
other iteration, resetting tries to 0 forever. The loop is infinite:
each iteration sleeps >= 100 us (up to mdelay(rd_interval*4) with a
crafted DPCD[0x0E], drm_dp_helper.c:121-134) and performs one DPCD read
via the atom AUX channel β slow-spin, never exit. Full iteration trace
table in trigger.c.
Twin check (the reason this finding exists): the radeon twin
radeon_dp_link_train_cr() (sys/dev/drm/radeon/atombios_dp.c:684-722,
DF-4589) is line-for-line identical in shape: same while (1), same
four exits, same else dp_info->tries = 0; at :713-714. The amdgpu copy
is the same defect; DF-4589's analysis carries over verbatim.
Contrast: the channel-equalization loop in the same file IS bounded β
tries is incremented unconditionally every iteration and checked with
> 5 (atombios_dp.c:685, :695), exiting after at most 7 iterations.
Only the CR loop is unbounded.
Caller context / why it wedges the whole display subsystem
amdgpu_atombios_dp_link_train_cr <- amdgpu_atombios_dp_link_train() atombios_dp.c:710,755 <- amdgpu_atombios_encoder_setup_dig(ATOM_ENABLE) atombios_encoders.c:1441 <- amdgpu_atombios_encoder_dpms() atombios_encoders.c:1477 <- .dpms encoder-helper callbacks of dce_v6/v8/v10/v11 (e.g. dce_v10_0.c:3421) <- drm_helper_encoder_dpms / modeset-commit paths taken with dev->mode_config.mutex held (drm_crtc.c:601/776; drm_crtc_helper.c:101/139)
So the spinning task is either the HPD workqueue thread or the user thread that issued MODE_SETCRTC / a DPMS property change. Result: an unkillable task (non-interruptible kernel loop) and every subsequent DRM ioctl on the card blocking on mode_config.mutex. Precondition: a crafted DP sink (MCU dongle) β the physical malicious-peripheral threat model already accepted for DF-1730 / DF-1778 / DF-4589. Not reachable from pure software input; no memory corruption (the >4-lane DPCD issue is a separate, known finding: DF-1790).
What I ran (attempts)
- Guest probe: no AMD GPU present β hardware-gated (run.log, guest_probe.log, env.txt). DONE.
- Compile-check of the documented sink state machine on the guest:
cc -Wall -Wextra -O2clean (build.log). - Source trace of every exit condition and the caller chain (above); verified the CE loop is bounded; verified the radeon twin is line-identical (diff by eye against DF-4589's cited lines).
Fix validation
fix.diff adds an overall iteration cap (50) to the CR loop β the same fix
shape as DF-4589's. git apply --check passes against the unmodified
sys/ tree. Baseline cannot run on the guest (no amdgpu hardware), so the
patched behavior ("clock recovery exceeded max loop attempts" then
"clock recovery failed", modeset unwinds normally) is validated by
inspection: the cap is checked at the top of every iteration and exits
unconditionally.
Recommended fix: hard iteration cap on the CR loop (see fix.diff).
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 not validated at runtime on guest: baseline cannot run (no amdgpu GPU). fix.diff applies cleanly (git apply --check) against atombios_dp.c:591-606 and bounds the loop unconditionally with a top-of-loop counter check; validated by inspection.
findings/poc/DF-4652/fix.diff
Confirmed kernel references
- sys/dev/drm/amd/amdgpu/atombios_dp.c:605
- sys/dev/drm/amd/amdgpu/atombios_dp.c:614
- sys/dev/drm/amd/amdgpu/atombios_dp.c:620
- sys/dev/drm/amd/amdgpu/atombios_dp.c:628
- sys/dev/drm/amd/amdgpu/atombios_dp.c:634
- sys/dev/drm/amd/amdgpu/atombios_dp.c:685
- sys/dev/drm/amd/amdgpu/atombios_encoders.c:1441
- sys/dev/drm/amd/amdgpu/atombios_encoders.c:1477
- sys/dev/drm/drm_dp_helper.c:121
Detail
Evidence (decisive lines)
VERDICT.md (exit-condition enumeration + caller-chain proof + twin diff); trigger.c (deterministic oscillating-sink state machine with iteration trace table); build.log (guest cc -Wall -Wextra -O2 compile clean); run.log/guest_probe.log/env.txt (no 0x1002 device on guest); fix.diff (50-iteration hard cap, git apply --check clean)
PoC changes
No seed pack existed; trigger authored as a documented malicious-sink state machine mirroring DF-4589's pack (guest DragonFly cc compile-check passed, zero warnings; runtime needs amdgpu HW + crafted DP dongle).
Verified recommended fix
Add an overall iteration cap (50) to the CR loop so a spec-violating sink cannot wedge the modeset path (see fix.diff).
Verdict
amdgpu_atombios_dp_link_train_cr() (sys/dev/drm/amd/amdgpu/atombios_dp.c:605-644) has no overall iteration bound β it is the line-for-line twin of the radeon loop filed as DF-4589: all four exits are sink-controlled and the 5-try counter is reset to 0 on ANY voltage change (atombios_dp.c:634-635), so a crafted DP sink that alternates its adjust-request voltage between levels < 3 while never achieving clock recovery spins the loop forever (>= 100 us delay + DPCD read per iteration, drm_dp_helper.c:121-134). The caller chain (atombios_encoders.c:1441 -> amdgpu_atombios_encoder_dpms :1477 -> dce_v6/8/10/11 .dpms) runs in the DRM modeset path holding mode_config.mutex (drm_crtc.c:601/776), producing an unkillable task and a permanently wedged display subsystem. Not reproduced on the audit guest: no AMD GPU exists (Bochs 0x1234:0x1111, run.log/env.txt) and QEMU cannot emulate the crafted DP sink, so execution is hardware-gated; the loop analysis is exhaustive and the defect is certain from source (the CE loop by contrast IS bounded at atombios_dp.c:685/695). Realistic impact ceiling: local physical DoS via malicious DP sink (same threat model as DF-4589/DF-1778/DF-1730), no memory corruption.
No comments yet.