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.
Recommended fix
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)
PoC verification
Evidence pack
findings/poc/DF-1567 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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
fixedfix.diff compiled in batch kernel build rc=0 -Werror
fix.diff compiled in batch kernel build rc=0 -Werror
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)
No comments yet.