β¬’ 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 1.0 KB ↓ raw
build.sh build-script N/A (source-only) 65 B view raw
run.sh run-script N/A (source-only) 89 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 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.

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)