/*
 * 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);
}
