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

radeon MST connector leaks one EDID blob per probe and per destroy (radeon_connector->edid overwritten without kfree)

Summary

get_ddc_modes stores the fresh allocation from drm_dp_mst_get_edid (a NEW edid every call) over the previous blob without freeing it, and neither MST destroy path kfrees it: every forced probe (unpriv DRM ioctls) leaks 128-512 bytes; unbounded heap growth. kfree-before-overwrite + destroy-path fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4632 Β· 2 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 1.6 KB ↓ raw

DF-4632 β€” radeon MST connector leaks one EDID blob per probe and per destroy

What

radeon_dp_mst_get_ddc_modes() (sys/dev/drm/radeon/radeon_dp_mst.c:186-204) does:

edid = drm_dp_mst_get_edid(connector, &master->mst_mgr, radeon_connector->port);
radeon_connector->edid = edid;            /* <-- old pointer dropped, no kfree */

drm_dp_mst_get_edid() (sys/dev/drm/drm_dp_mst_topology.c:2553-2571) returns a fresh allocation on every call β€” drm_edid_duplicate(port->cached_edid) when cached, drm_get_edid() otherwise. The previous radeon_connector->edid allocation is never freed, and neither destroy path frees it either (radeon_dp_destroy_mst_connector :307-320 and radeon_dp_mst_connector_destroy :248-258 both omit kfree(edid)).

Trigger (unprivileged)

Every drm_helper_probe_single_connector_modes β†’ get_modes for an MST connector allocates a new EDID (128–512 bytes, drm_edid block(s)) and abandons the previous one. A user with /dev/dri/card0 access can force probes in a loop (DRM_IOCTL_MODE_GETCONNECTOR without a cached mode set, or the sysfs status read path) β€” each iteration leaks one EDID blob. Every hub connect/disconnect cycle additionally leaks the last EDID of each destroyed child connector.

Impact: unbounded kernel heap growth (memory-exhaustion DoS) driven by an unprivileged desktop user; needs a DP MST topology (radeon DCE5+). Same reach class as DF-1778's malicious-hub model.

Upstream

Upstream radeon has the same historical pattern but its probe path for MST was reworked; regardless, the in-tree fix is a one-liner (kfree before overwrite + kfree in the destroy path).

Fix

--- a/sys/dev/drm/radeon/radeon_dp_mst.c
+++ b/sys/dev/drm/radeon/radeon_dp_mst.c
@@ -191,6 +191,7 @@ static int radeon_dp_mst_get_ddc_modes(struct drm_connector *connector)
    int ret = 0;

    edid = drm_dp_mst_get_edid(connector, &master->mst_mgr, radeon_connector->port);
+   kfree(radeon_connector->edid);
    radeon_connector->edid = edid;
@@ (radeon_dp_mst_connector_destroy / radeon_dp_destroy_mst_connector)
+   kfree(radeon_connector->edid);   /* before kfree(radeon_connector) */
VERDICT.md
↓ download raw

DF-4632 VERDICT β€” EDID leak per probe/destroy (radeon MST)

status: untested β€” no radeon GPU / DP MST hub on the audit guest. The leak is source-certain (no runtime behavior to discover):

  1. drm_dp_mst_get_edid() returns a NEW allocation on every call (sys/dev/drm/drm_dp_mst_topology.c:2562-2567: drm_edid_duplicate() of the cached EDID, or a fresh drm_get_edid()), with no caller-side retention.
  2. radeon_dp_mst_get_ddc_modes() stores it into radeon_connector->edid (radeon_dp_mst.c:194) without freeing the previous value. get_modes runs on every forced connector probe (drm_helper_probe_single_connector_modes), i.e. an unprivileged /dev/dri/card0 user can drive it in a loop.
  3. Neither destroy path (radeon_dp_mst_connector_destroy :248-258 nor radeon_dp_destroy_mst_connector :307-320) kfrees radeon_connector->edid, so the final blob is leaked at connector teardown as well.

Impact ceiling: memory-exhaustion DoS (128-512 bytes per probe, unbounded); trivially fixed by kfree-before-overwrite + kfree in destroy. Severity Low (hardware-gated, slow leak, no info disclosure β€” the memory is never exposed, only abandoned).

Recommended fix: see README.md diff.

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.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

no radeon hardware on guest; cannot validate

per-fix-DF-4632

Confirmed kernel references

Detail

Evidence (decisive lines)

radeon_dp_mst.c:193-194 overwrite without kfree; drm_dp_mst_topology.c:2562-2567 fresh allocation per call

PoC changes

No runnable PoC: needs radeon + DP MST hardware.

Verified recommended fix

kfree(radeon_connector->edid) before the overwrite in radeon_dp_mst_get_ddc_modes, and kfree it in both MST connector destroy paths

Verdict

radeon_dp_mst_get_ddc_modes (radeon_dp_mst.c:194) overwrites radeon_connector->edid with a fresh allocation returned by drm_dp_mst_get_edid (drm_dp_mst_topology.c:2553-2571 allocates anew on every call) without freeing the previous blob; neither MST destroy path kfrees it either. Unbounded kernel heap growth driven by unprivileged connector probes on a DP MST topology; impact ceiling is memory exhaustion (no contents disclosure). Source-certain; untested because the audit guest has no radeon GPU.