β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1567

Unvalidated lane_count causes OOB read of link_status[] in DP link-training helpers

  • File: sys/dev/drm/drm_dp_helper.c
  • Lines: 46, 58, 95, 119
  • Severity: Low
  • CVSS: CVSS:3.1/AV:P/AC:L/PR:N/UI:R/S:U:C:N/I:N/A:L
  • CWE: CWE-125 Out-of-bounds Read
  • Confidence: likely

Summary

drm_dp_get_adjust_request_voltage/pre_emphasis compute link_status[(0x206 + lane/2) - 0x202] = link_status[4 + lane/2] (lines 102, 115) without validating lane against DP_LINK_STATUS_SIZE (6).

For lane >= 4 this reads past the 6-byte link_status buffer.

drm_dp_channel_eq_ok / drm_dp_clock_recovery_ok likewise iterate lane < lane_count and reach dp_get_lane_status(link_status, lane) (line 72, 87) which indexes link_status[lane/2]; OOB at lane_count >= 13.

The helpers trust lane_count blindly.

Root cause

In drm_dp_helper.c:95-106 drm_dp_get_adjust_request_voltage:

int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);  /* 0x206 + lane/2 */

then dp_link_status(link_status, i) (line 102) returns link_status[i - DP_LANE0_1_STATUS] = link_status[4 + lane/2].

With DP_LINK_STATUS_SIZE = 6 (drm_dp_helper.h:954), valid indices are 0..5.

For lane = 4, index = 6 β†’ 1 byte past end; lane 5..7 β†’ indices 6..7.

Same in drm_dp_get_adjust_request_pre_emphasis (line 111-115).

dp_get_lane_status (line 51-58) computes i = DP_LANE0_1_STATUS + (lane >> 1) β†’ link_status[lane/2]; OOB for lane/2 >= 6, i.e. lane_count >= 13.

No bounds check anywhere. The lane_count parameter (int) is taken verbatim from the caller.

In amdgpu_atombios_dp_get_dp_link_config (amd/amdgpu/atombios_dp.c:264-287) unsigned max_lane_num = drm_dp_max_lane_count(dpcd); (sink-supplied, only masked by 0x1f = up to 31) and for (lane_num = 1; lane_num <= max_lane_num; lane_num <<= 1) can pick lane_num = 8 (or 16) for high-bandwidth modes with no source-side clamp; this propagates to dig_connector->dp_lane_count (line 413) β†’ dp_info->dp_lane_count (line 749) β†’ amdgpu_atombios_dp_get_adjust_train loop (line 214-216) which calls drm_dp_get_adjust_request_voltage/pre_emphasis with lane 0..7.

radeon/atombios_dp.c:264-266 has the identical pattern.

i915 is NOT reachable: intel_dp_max_common_lane_count (intel_dp.c:204-212) does min3(source_max, sink_max, fia_max), bounding lane_count to source hardware lanes (typically 4).

Threat

Attacker is a malicious DisplayPort sink device (e.g., weaponized USB-C dock, mDP cable with embedded MCU, or modified monitor firmware) plugged into a target running amdgpu or radeon DRM.

The sink reports dpcd[DP_MAX_LANE_COUNT=0x002] = 0x1f (claims 31 max lanes). When the user requests a mode whose bandwidth exceeds 4-lane capacity at the highest link rate (e.g., pixel clock > 720 MHz at 24bpp), amdgpu_atombios_dp_get_dp_link_config selects lane_num = 8.

During subsequent link training, drm_dp_get_adjust_request_voltage reads 1-2 bytes past the link_status[6] array (in struct amdgpu_atombios_dp_link_train_info at atombios_dp.c:487, immediately followed by u8 tries and struct drm_dp_aux *aux β€” so the OOB read samples kernel-controlled struct fields).

The data is consumed locally for voltage-swing/pre-emphasis computation, written to DPCD via drm_dp_dpcd_write, and never returned to userspace.

Impact is a kernel memory-safety defect with no info leak and no crash; practical effect is incorrect link-training parameters leading to training failure (DoS of the attached display).

Requires physical access (or supply-chain compromise of a peripheral) plus amdgpu/radeon plus a high-bandwidth mode.

Clamp lane_count at the entry of each helper, since link_status only carries meaningful data for lanes 0..3 (DP_LINK_STATUS_SIZE = 6 covers two lanes per byte for both status and adjust-request pairs).

Defense-in-depth: bounds-check the index in dp_link_status.

--- a/sys/dev/drm/drm_dp_helper.c
+++ b/sys/dev/drm/drm_dp_helper.c
@@ -46,6 +46,9 @@
 static u8 dp_link_status(const u8 link_status[DP_LINK_STATUS_SIZE], int r)
 {
+   if (r < DP_LANE0_1_STATUS ||
+       r - DP_LANE0_1_STATUS >= DP_LINK_STATUS_SIZE)
+       return 0;
    return link_status[r - DP_LANE0_1_STATUS];
 }

@@ -60,6 +63,9 @@ bool drm_dp_channel_eq_ok(const u8 link_status[DP_LINK_STATUS_SIZE],
    u8 lane_status;
    int lane;

+   if (lane_count <= 0 || lane_count > 4)
+       return false;
+
    lane_align = dp_link_status(link_status,
                    DP_LANE_ALIGN_STATUS_UPDATED);
    if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0)
@@ -80,6 +86,9 @@ bool drm_dp_clock_recovery_ok(const u8 link_status[DP_LINK_STATUS_SIZE],
    int lane;
    u8 lane_status;

+   if (lane_count <= 0 || lane_count > 4)
+       return false;
+
    for (lane = 0; lane < lane_count; lane++) {
        lane_status = dp_get_lane_status(link_status, lane);
        if ((lane_status & DP_LANE_CR_DONE) == 0)

The kernel-side lane_count value should never exceed 4 per the DP spec (1.1a/1.2/1.3/1.4 single-stream). Returning false from the *_ok helpers on out-of-range lane_count causes link training to retry/fallback, which is the safe behavior.

The driver-side root cause (amdgpu/radeon not taking min(sink_max, source_max) when computing dp_lane_count) should be fixed in parallel by mirroring intel_dp_max_common_lane_count's min3() pattern.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1567 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix for the cited bug 408 B view raw
VERDICT.md verdict source-confirmation analysis 706 B ↓ raw
build.sh build-script N/A (source-only) 61 B view raw
run.sh run-script N/A (source-only) 87 B view raw
VERDICT.md verdict source-confirmation analysis
↓ download raw

DF-1567 VERDICT

Verdict: REPRODUCED (source-confirmed)

Impact: Low (driver-level NULL deref / OOB / leak / DoS β€” hardware-gated)

Mechanism: drm_dp_helper.c:46-58 dp_link_status/dp_get_lane_status index link_status[lane/2] or link_status[4+lane/2] without lane<DP_LINK_STATUS_SIZE=6 check. drm_dp_get_adjust_request_voltage (95) and pre_emph

Citation: sys/dev/drm/drm_dp_helper.c:46-119

Fix: Applied fix.diff β€” compiles in batch kernel build (rc=0, -Werror).

Verification method: Source-only line-by-line trace of cited path:line. Low-severity driver bug; PoC trigger requires specific hardware or root context. Confirmed the cited vulnerable pattern exists in source.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff compiled in batch kernel build rc=0 -Werror

fix.diff compiled in batch kernel build rc=0 -Werror
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (Low severity)

Evidence (decisive lines)

Source-confirmed: unvalidated lane_count causes OOB read of link_status[] (drm_dp_helper.c:46-119)

Verified recommended fix

Source-confirmed: unvalidated lane_count causes OOB read of link_status[] (drm_dp_helper.c:46-119)

Verdict

Source-confirmed: unvalidated lane_count causes OOB read of link_status[] (drm_dp_helper.c:46-119)