# DF-3987 VERDICT — source-level certain, runtime hardware-gated (inconclusive)

## What was audited (pass 2, excluding DF-1319)

`sys/dev/drm/amd/display/dc/core/dc_link_dp.c` second pass. DF-1319 (known)
covers: `dp_test_send_link_training` reads a raw DP_TEST_LANE_COUNT byte and
the transient `lt_settings` overflow it causes inside the link-training
helpers (`dpcd_set_lt_pattern_and_lane_settings` :168/:194/:212-218,
`get_lane_status_and_drive_settings` :432). This finding is about where that
same unvalidated byte goes AFTER link training — two sinks DF-1319's trace
never touches.

## Root cause chain (path:line)

1. `dp_test_send_link_training` — dc_link_dp.c:1772-1781 reads raw sink bytes
   `DP_TEST_LANE_COUNT` / `DP_TEST_LINK_RATE` (1 byte each, no validation
   against `LANE_COUNT_DP_MAX`=4 — dc_dp_types.h:31-37).
2. :1784-1785 persists the byte into `link->verified_link_cap.{lane_count,
   link_rate}`.
3. :1787 `dp_retrain_link_dp_test(link, &link_settings, false)` —
   dc_link_hwss.c:270-340 — performs the retrain (DF-1319's transient
   overflow fires here) and at **:319** executes
   `link->cur_link_settings = *link_setting;` — the raw byte now LIVES in
   `cur_link_settings` and survives until the next full mode set.
4. `handle_automated_test` (dc_link_dp.c:1965-2010) processes
   `DP_TEST_REQUEST` bits sequentially in ONE HPD-RX-IRQ invocation:
   LINK_TRAINING (bit0) at :1978-1986 then PHY_TEST_PATTERN (bit2) at
   :1994-1995. A sink setting both bits poisons `cur_link_settings` and
   immediately feeds it to `dp_test_send_phy_test_pattern`.

## New sink A — stack WRITE overflow (dp_test_send_phy_test_pattern)

dc_link_dp.c:1879-1895:
```c
link_settings.link = link->cur_link_settings;
for (lane = 0; lane < (unsigned int)(link->cur_link_settings.lane_count); lane++) {
        ...
        link_settings.lane_settings[lane].VOLTAGE_SWING = ...   /* :1886 */
        link_settings.lane_settings[lane].PRE_EMPHASIS = ...    /* :1889 */
        link_settings.lane_settings[lane].POST_CURSOR2 = ...    /* :1892 */
}
```
`struct dc_link_training_settings` (dc_dp_types.h:102-105) has
`lane_settings[LANE_COUNT_DP_MAX]` = 4 × 12 bytes. With the persisted byte
0xFF the loop writes 255 × 12 = 3060 bytes into a 48-byte array on this
function's stack — ~3012 bytes of linear up-stack smash (saved frames,
return addresses). Values are constrained (each 4-byte enum word ∈ 0..3 from
2-bit DPCD nibbles / POST_CURSOR2 bits), so return addresses become
non-canonical → deterministic kernel panic; controlled RIP is not reachable
with these values. Auxiliary OOB READ at :1885
(`get_nibble_at_index(&dpcd_lane_adjustment[0].raw, lane)` for lane up to
254 reads `dpcd_lane_adjustment[lane/2]`, 2-element array) feeds the written
values from up to 126 bytes past the array.

## New sink B — stack READ overflow (hpd_rx_irq_check_link_loss_status)

dc_link_dp.c:1596-1615 loops
`lane < link->cur_link_settings.lane_count` calling
`get_nibble_at_index(&hpd_irq_dpcd_data->bytes.lane01_status.raw, lane)`
(:1600) → `buf[lane/2]`. `union hpd_irq_data` is 6 bytes
(dc_dp_types.h:341-351). With the persisted byte 0xFF the loop reads index
127 → 122 bytes past the 6-byte caller-local (`dc_link_handle_hpd_rx_irq`
:2014). Branch-only use (link-loss decision) → garbage-driven retrain, no
direct disclosure, no fault (stack).

Also note :1590-1591: the only guard is `lane_count == 0`, not > 4.

## Verification on the guest (honest)

- trigger.c (this pack) models the exact index/size arithmetic; builds and
  runs on the guest (`run.log`, TRIGGER_RC=0, extents for byte classes
  {5, 0x1F, 0x20, 0x7F, 0xFF}).
- The real chain is hardware-gated on this guest: `pciconf -lv` shows zero
  0x1002 (AMD) devices (bochs VGA only), `amdgpu.ko` exists in
  /boot/kernel but is NOT loaded (kldstat), so dc_link_dp.c code never
  executes. Status: **inconclusive** per family convention (DF-1319,
  DF-3759, DF-3604).
- Guest /usr/src dc_link_dp.c md5 `028326495b845611c38ec70dcd655bf3`
  == audited tree (env.txt).

## Threat model

Malicious DP sink / MST hub / test appliance (or a man-in-the-middle on an
unencrypted DP AUX? — AUX is not remotely reachable; physical or
dongle-borne). One short-HPD with AUTOMATED_TEST + LINK_TRAINING +
PHY_TEST_PATTERN → stack smash → panic. Same threat model DF-1319 was filed
under (High). Escalation beyond panic is implausible: written words ∈ {0..3}.

## Fix

fix.diff: (1) validate DP_TEST_LANE_COUNT ∈ {1,2,4} and DP_TEST_LINK_RATE
against the known enum at the source (dp_test_send_link_training) — kills
this finding AND the DF-1319 entry path; (2) defensive `lane <
LANE_COUNT_DP_MAX` caps at both new sinks. git-apply clean against the
audited tree; not runtime-validable (no AMD GPU).

## 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.
