DF-3987 / 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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | /* * DF-3987 trigger.c — arithmetic model of the DC-1 poisoning chain * (distinct sinks from DF-1319: dp_test_send_phy_test_pattern write OOB * and hpd_rx_irq_check_link_loss_status read OOB, driven by * link->cur_link_settings.lane_count / verified_link_cap.lane_count * poisoned by dp_test_send_link_training and PERSISTED by * dp_retrain_link_dp_test at dc_link_hwss.c:319). * * HARDWARE PRECONDITION (not present on the audit guest — bochs VGA only): * amdgpu (DC) driving a DisplayPort link; a malicious sink / DP test * appliance raises a short HPD with DEVICE_SERVICE_IRQ.AUTOMATED_TEST. * * Chain, with source cites: * dc_link_handle_hpd_irq dc_link_dp.c:2045-2054 * -> handle_automated_test dc_link_dp.c:1965-2010 * test_request = DP_TEST_REQUEST (sink byte). LINK_TRAINING (bit0) * and PHY_TEST_PATTERN (bit2) are handled in the SAME call: * :1978 if (test_request.bits.LINK_TRAINING) * :1986 dp_test_send_link_training(link); * :1994 if (test_request.bits.PHY_TEST_PATTERN) * :1995 dp_test_send_phy_test_pattern(link); * * dp_test_send_link_training dc_link_dp.c:1768-1788 * :1772 core_link_read_dpcd(DP_TEST_LANE_COUNT, &lane_count, 1) * -> raw sink byte 0..255, no check vs LANE_COUNT_DP_MAX(4) * :1784 link->verified_link_cap.lane_count = raw byte (POISON 1) * :1787 dp_retrain_link_dp_test(link, &link_settings, false) * dc_link_hwss.c:270.. :319 * link->cur_link_settings = *link_setting; (POISON 2) * * dp_test_send_phy_test_pattern dc_link_dp.c:1794-1916 [*NEW SINK A*] * :1879 link_settings.link = link->cur_link_settings; * :1881 for (lane = 0; lane < cur_link_settings.lane_count; lane++) * :1886 link_settings.lane_settings[lane].VOLTAGE_SWING = ... * struct dc_link_training_settings.lane_settings[LANE_COUNT_DP_MAX] * (dc_dp_types.h:102-105) = 4 x 12 bytes = 48 bytes on the STACK of * dp_test_send_phy_test_pattern. lane_count = 255 writes 255*12 = * 3060 bytes -> ~3012 bytes past the array (up-stack smash; every * 4-byte enum word is constrained to 0..3, so saved frames/return * addresses become non-canonical -> deterministic panic, not * controlled RIP). * ALSO :1885 get_nibble_at_index(&dpcd_lane_adjustment[0].raw, lane) * reads dpcd_lane_adjustment[lane/2] for lane up to 254 -> 127 bytes * past the 2-element array (stack read feeding the written values). * * hpd_rx_irq_check_link_loss_status dc_link_dp.c:1576-1643 [*NEW SINK B*] * :1596 for (lane = 0; lane < link->cur_link_settings.lane_count; ...) * :1600 get_nibble_at_index(&hpd_irq_dpcd_data->bytes.lane01_status.raw, * lane) -> buf[lane/2] * union hpd_irq_data is 6 bytes (dc_dp_types.h:341-351). lane=255 reads * index 127 -> 122 bytes past the 6-byte local in the caller's frame * (dc_link_handle_hpd_rx_irq :2014). Read-only; branch-only use. * * This userland model reproduces the exact index/size arithmetic and prints * the corruption extents for every sink-fed byte value class. */ #include <stdio.h> #include <stdint.h> #define LANE_COUNT_DP_MAX 4 #define LANE_SETTINGS_ELEMBYTES 12 /* 3 enums x 4 bytes */ #define HPD_IRQ_DATA_BYTES 6 /* union hpd_irq_data raw[6] */ struct model { uint8_t test_lane_count; /* DP_TEST_LANE_COUNT sink byte */ }; static void model_chain(uint8_t raw) { struct model m = { .test_lane_count = raw }; unsigned lane_count = m.test_lane_count; /* as persisted */ unsigned lane; uint8_t lane_settings_used = 0; size_t write_end = 0, read_idx_max = 0; /* SINK A: dp_test_send_phy_test_pattern :1881-1895 */ for (lane = 0; lane < lane_count; lane++) lane_settings_used++; write_end = lane_count * LANE_SETTINGS_ELEMBYTES; /* SINK A aux: dpcd_lane_adjustment[lane/2] :1885 */ read_idx_max = lane_count ? (lane_count - 1) / 2 : 0; printf("DP_TEST_LANE_COUNT=0x%02x -> cur/verified lane_count=%u\n", raw, lane_count); if (lane_count > LANE_COUNT_DP_MAX) { size_t oobw = write_end - (LANE_COUNT_DP_MAX * LANE_SETTINGS_ELEMBYTES); printf(" [A] lane_settings[%u] write past [4]: %zu bytes " "OOB WRITE (total span %zu B, values 0..3 per word) " "[stack SMASH -> panic]\n", lane_settings_used, oobw, write_end); if (read_idx_max >= 2) printf(" [A2] dpcd_lane_adjustment read idx %zu " "past [2]: %zu bytes OOB READ\n", read_idx_max, read_idx_max - 1); } else { printf(" [A] in-bounds (lane_count<=4)\n"); } /* SINK B: hpd_rx_irq_check_link_loss_status :1596-1615 */ if (lane_count > LANE_COUNT_DP_MAX) { size_t idx = (lane_count - 1) / 2; printf(" [B] lane01_status read idx %zu vs union size %d: " "%s (%zd bytes past) [stack READ, branch-only]\n", idx, HPD_IRQ_DATA_BYTES, idx >= HPD_IRQ_DATA_BYTES ? "OOB READ" : "in-bounds", idx >= HPD_IRQ_DATA_BYTES ? (ssize_t)(idx + 1 - HPD_IRQ_DATA_BYTES) : 0); } printf("\n"); } int main(void) { int i; printf("DF-3987 model: poisoned cur_link_settings.lane_count ->\n" " (A) dp_test_send_phy_test_pattern lane_settings[] OOB WRITE\n" " (B) hpd_rx_irq_check_link_loss_status OOB READ\n\n"); /* every interesting sink-fed class: 1..4 legit, 0x05 non-power-2, * 0x1F = MAX_LANE_COUNT 5-bit max (retrieve_link_cap path), * 0x20, 0x7F, 0xFF raw byte max */ uint8_t cases[] = { 1, 2, 4, 5, 0x1f, 0x20, 0x7f, 0xff }; for (i = 0; i < (int)sizeof(cases); i++) model_chain(cases[i]); printf("TRIGGER_RC=0 (model OK; hardware-gated on this guest: " "no AMD GPU)\n"); return (0); } |