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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4202 Β· 11 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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 when drm_mode_create() (drm_modes.c:66-80) returns NULL β€” kzalloc failure or drm_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 as list_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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-4202

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.