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