Unchecked drm_cvt_mode() NULL results: mode->type write through NULL in amdgpu_connector_lcd_native_mode (:406) and drm_mode_probed_add(NULL) list_add in amdgpu_connector_add_common_modes (:460) (radeon DF-4035 twin)
Summary
drm_cvt_mode returns NULL when drm_mode_create fails (kzalloc or IDR failure). Both call sites use the result unconditionally: one writes mode->type through NULL; the other passes NULL to drm_mode_probed_add = list_add_tail - a NULL+offset write. Both run from the LVDS/eDP no-EDID-modes fallback on every probe; add_common_modes alone makes 17 consecutive allocations per probe, so kernel memory exhaustion during a probe panics. Upstream fixed the identical radeon pattern with if (mode) guards. Guard fix at both sites (+ the drm_mode_duplicate twin at :391).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4202 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.1 KB | β raw | |
| VERDICT.md | β | 2.1 KB | β raw | |
| trigger_df4202.c | β | 3.7 KB | view raw | |
| build.sh | β | 87 B | view raw | |
| run.sh | β | 131 B | view raw | |
| build.log | β | 241 B | view raw | |
| run.log | β | 105 B | view raw | |
| env.txt | β | 244 B | view raw | |
| fix.diff | β | 994 B | view raw | |
| manifest.json | β | 1.0 KB | view raw | |
| verdict.json | β | 3.1 KB | view raw |
DF-4202 β unchecked drm_cvt_mode() NULL results (radeon DF-4035 twin)
File: sys/dev/drm/amd/amdgpu/amdgpu_connectors.c:405-407, 459-460 (pass 2)
Severity: Low (OOM-gated NULL deref panic in LVDS/eDP get_modes)
Status: not reproduced at runtime β hardware-gated + OOM-gated;
source-level certainty.
Root cause
drm_cvt_mode() returns NULL when its internal drm_mode_create()
fails β kzalloc failure or drm_mode_object_add() IDR failure
(drm_modes.c:66-80 and drm_modes.c:164-169: "If failure, we will return
directly" NULL). Both amdgpu call sites dereference unconditionally:
/* amdgpu_connector_lcd_native_mode() */
mode = drm_cvt_mode(dev, native_mode->hdisplay, native_mode->vdisplay,
60, true, false, false); /* :405 */
mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; /* :406 NULL deref */
DRM_DEBUG_KMS("... %s\n", mode->name); /* :407 */
/* amdgpu_connector_add_common_modes() β loops 17 common modes */
mode = drm_cvt_mode(dev, common_modes[i].w, common_modes[i].h,
60, false, false, false); /* :459 */
drm_mode_probed_add(connector, mode); /* :460 list_add(NULL) */
drm_mode_probed_add is list_add_tail(&mode->head, ...) β a write
through NULL+offset.
Reach
amdgpu_connector_lvds_get_modes() (:668, :676) and
amdgpu_connector_dp_get_modes() eDP/LVDS branch (:1251, :1259) β the
no-EDID-modes fallback that every laptop panel without a usable EDID takes
on every probe β under kernel memory exhaustion. 17+ allocation attempts
per probe Γ repeated probes (poll + GETCONNECTOR) make the window real
under memory pressure. Upstream Linux carries the identical unguarded
code (radeon upstream added if (mode) guards for the same pattern).
Evidence
trigger_df4202.cβ hammer probe loop with optional memory-hog children (./trigger_df4202 oom); builds and runs on the guest (no DRM device there β env.txt).fix.diffβif (mode)guards at both sites; verified to apply cleanly to a pristine copy of the real file.
Fix
See fix.diff.
DF-4202 VERDICT β unchecked drm_cvt_mode() NULL derefs
Verdict: not reproduced at runtime (hardware-gated + OOM-gated); source-level certainty: certain. Impact if exercised: kernel NULL-deref panic (DoS) in the connector get_modes path.
Why certain at source level
drm_cvt_mode()(drm_modes.c:143-169) returns NULL exactly whendrm_mode_create()(drm_modes.c:66-80) returns NULL β kzalloc failure ordrm_mode_object_add()(IDR) failure. The function's own doc comment states it "Returns NULL when no mode could be allocated."- Site 1 (
amdgpu_connector_lcd_native_mode, amdgpu_connectors.c:405-407):mode->type = ...writes through NULL immediately. - Site 2 (
amdgpu_connector_add_common_modes, :459-460):drm_mode_probed_add(connector, mode)is defined aslist_add_tail(&mode->head, &connector->probed_modes)β a NULL+offset write. - Both functions run from the LVDS/eDP no-EDID fallback on every connector probe (lvds_get_modes :668/:676, dp_get_modes :1251/:1259), i.e. the standard path for a laptop panel whose EDID read fails, and the common modes loop alone makes 17 consecutive mode allocations per probe.
Runtime attempt
trigger_df4202.c built cleanly (guest cc 8.3) and ran; the audit guest
exposes no DRM device (/dev/dri absent, no AMD/ATI PCI devices β
env.txt), so the amdgpu get_modes path cannot execute there. Deterministic
reproduction needs an amdgpu LVDS/eDP machine driven into kernel memory
exhaustion while probing β same OOM gating as the radeon twin DF-4035,
which upstream fixed by adding the exact if (mode) guards this pack's
fix.diff adds.
Fix validation
Not possible on this guest. fix.diff verified to apply cleanly to a
pristine copy of the real file (cumulative with DF-4200/DF-4201 diffs).
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
not_testablefix.diff applies cleanly to a pristine copy of the real amdgpu_connectors.c (verified, cumulative with DF-4200/4201 diffs); kernel-build validation not possible on this guest (no AMD GPU).
fix.diff; local patch --dry-run + applied-copy verification noted in VERDICT.md
Confirmed kernel references
Detail
Exploit chain
memory-exhausted amdgpu LVDS/eDP machine + connector probe without EDID modes -> drm_mode_create NULL -> NULL deref / list_add on NULL -> kernel panic
Evidence (decisive lines)
VERDICT.md (NULL-return proof chain in drm_modes.c); trigger_df4202.c + run.log (guest run, hardware-gated); fix.diff (if (mode) guards); env.txt
PoC changes
trigger written from scratch (raw ioctls, probe hammer + optional forked memory hogs, self-contained struct defs); __u32/__u64 typedefs added after first guest compile
Verified recommended fix
if (mode) guards at both sites (see fix.diff); also guard the drm_mode_duplicate result at :391 for completeness.
Verdict
DF-4035 radeon twin in amdgpu: drm_cvt_mode() returns NULL on drm_mode_create() failure (kzalloc/IDR exhaustion β drm_modes.c:66-80,164-169) and both call sites dereference or list_add the result unconditionally: amdgpu_connector_lcd_native_mode:405-407 (mode->type write through NULL) and amdgpu_connector_add_common_modes:459-460 (drm_mode_probed_add(NULL) = list_add_tail on NULL+offset). Reached from the LVDS/eDP no-EDID-modes fallback on every connector probe (lvds_get_modes:668/676, dp_get_modes:1251/1259), 17+ allocations per probe β a real window under kernel memory exhaustion (upstream fixed the identical radeon pattern with if (mode) guards). Not reproduced at runtime: guest has no AMD GPU (env.txt) and the deterministic trigger additionally needs OOM; trigger (with optional memory-hog mode) built and ran on the guest.
No comments yet.