BUG_ON panic in drm_primary_helper_update() when SetPlane targets an enabled CRTC left with zero encoder-bound connectors
Summary
BUG_ON(num_connectors==0) asserts a KMS invariant the core's own legacy set_config fail paths can violate (DF-4469 family) - a failed modeset leaving crtc->enabled with cleared connector bindings converts it into a one-ioctl unpriv-master panic. Well-formed ioctls cannot create the state (speculative). Replace-BUG_ON-with-EINVAL fix. ALSO: the drm_atomic_uapi.c:307-316 NULL-write lead is REFUTED line-by-line (get_connector_state invariant protects every entry; full trace in neg-evidence pack).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4839 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.4 KB | β raw | |
| VERDICT.md | β | 3.6 KB | β raw | |
| fix.diff | β | 774 B | view raw | |
| manifest.json | β | 729 B | view raw | |
| verdict.json | β | 3.3 KB | view raw |
DF-4839 β BUG_ON panic in drm_primary_helper_update on enabled connector-less CRTC
- File: sys/dev/drm/drm_plane_helper.c:265
- Severity: Info (hardening) β Confidence: speculative
- Class: kernel panic / DoS via BUG_ON (CWE-617 reachable assert)
- Reach: unprivileged DRM master on radeon/amdgpu/dce (drm_crtc_init users) via DRM_IOCTL_MODE_SETPLANE, provided the CRTC was left enabled with zero encoder-bound connectors (e.g. after a failed legacy modeset β the DF-4469 family documents how the fail path corrupts connector->encoder bindings).
Build / Run
No PoC exists: the audit guest has no DRM device (QEMU std-VGA only;
/dev/dri* and /dev/drm* do not exist), so DRM_IOCTL_MODE_SETPLANE is not
issuable. Verification was static-only; see VERDICT.md for the full
line-cited trace. On hardware the trigger would be:
/* drm master fd; primary plane id P of an enabled-but-connectorless CRTC */
struct drm_mode_set_plane req = { .plane_id = P, .fb_id = F, .crtc_id = C,
.crtc_x=0,.crtc_y=0,.crtc_w=w,.crtc_h=h,
.src_x=0,.src_y=0,.src_w=w<<16,.src_h=h<<16 };
ioctl(fd, DRM_IOCTL_MODE_SETPLANE, &req); /* -> BUG_ON -> panic */
Expected on vulnerable setup: kernel panic "kernel: BUG_ON ... num_connectors".
Expected with fix.diff: clean -EINVAL.
Fix
fix.diff β replace BUG_ON with an error return (upstream-equivalent
hardening).
DF-4839 β BUG_ON(num_connectors == 0) in drm_primary_helper_update() β Info / speculative (static-only)
Claim
drm_primary_helper_update() (sys/dev/drm/drm_plane_helper.c:215-288) is the
default update_plane handler for the helper-created primary planes of every
driver that registers CRTCs with drm_crtc_init() β in this tree that is
radeon (radeon_display.c), amdgpu (dce_v8_0.c, dce_v10_0.c,
dce_v11_0.c, dce_virtual.c), and any module using the transitional KPI.
It is reached from the unprivileged-DRM-master DRM_IOCTL_MODE_SETPLANE path:
drm_mode_setplane() drm_plane.c:774
setplane_internal() drm_plane.c:735 (all modeset locks held)
__setplane_internal() drm_plane.c:647
plane->funcs->update_plane == drm_primary_helper_update drm_plane.c:690
drm_plane_helper_check_update() drm_plane_helper.c:246 (visible == true
requires crtc->enabled, drm_atomic_helper.c:766-770)
get_connectors_for_crtc(crtc,NULL,0) drm_plane_helper.c:264
BUG_ON(num_connectors == 0); drm_plane_helper.c:265 <-- panic
get_connectors_for_crtc() (drm_plane_helper.c:73-101) counts connectors via
connector->encoder && connector->encoder->crtc == crtc. The BUG_ON fires
whenever crtc->enabled == true (checked moments earlier through the
crtc_state.enable built at drm_plane_helper.c:156-160) but no connector is
currently encoder-bound to that CRTC.
Why this is only Info/speculative
On a correctly-functioning driver an enabled CRTC always has >= 1 bound
connector (the legacy set_config path refuses to enable a CRTC with zero
connectors), so the BUG_ON cannot be reached by well-formed ioctls alone.
However the corrupt state is exactly what the already-filed
DF-4469 (drm_crtc_helper.c racy save_connector_encoders sizing β OOB write
into connector->encoder on the fail path) and the fail-path encoder-unbind
behavior documented around DF-4470/DF-4471 can produce: an enabled CRTC left
with zero live connector bindings after a failed modeset. A second SetPlane
ioctl on that CRTC's primary plane then converts the latent inconsistency into
a hard kernel panic (BUG_ON) β a one-syscall local DoS for a DRM master.
Upstream evolution supports treating this as hardening rather than a bug fix:
this transitional-helper path (and its BUG_ON) has no surviving modern-kernel
equivalent; the whole primary-helper update path was retired as drivers moved
to atomic. Keeping a BUG_ON on a state invariant that the core's own fail
paths can violate is defense-in-depth debt.
Disposition
- Not reproduced live: the audit QEMU guest has no DRM device
(
ls /dev/dri* /dev/drm*β ENOENT; onlyvgapci0QEMU std-VGA, no drm driver attaches), so no SetPlane ioctl can be issued at all. Static trace only. - Severity Info, confidence speculative (needs DF-4469-class state corruption as setup, or any driver defect leaving an enabled connector-less CRTC).
- Fix: return
-EINVALinstead of BUG_ON (see fix.diff). Trivially safe; no kernel-build validation performed because there is no reproduction baseline on this guest (fix_status: not_applicable).
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_testablenot_applicable: no reproduction baseline exists (guest has no DRM hardware); the one-line BUG_ON->EINVAL change is trivially safe and cannot regress correctness since the surrounding code already returns -EINVAL for other invalid primary-plane states
fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4839/VERDICT.md (full line-cited ioctl->BUG_ON trace); fix.diff (BUG_ON -> -EINVAL)
PoC changes
no PoC authored: Info hardening finding; guest lacks any DRM device so SetPlane cannot be exercised (noted in VERDICT.md)
Verified recommended fix
Replace BUG_ON(num_connectors == 0) at drm_plane_helper.c:265 with a DRM_DEBUG_KMS + -EINVAL return
Verdict
Static-only verification. drm_primary_helper_update() (drm_plane_helper.c:215-288, default update_plane for drm_crtc_init() primary planes used by radeon/amdgpu dce_) panics via BUG_ON at drm_plane_helper.c:265 when DRM_IOCTL_MODE_SETPLANE targets the primary plane of an enabled CRTC that has zero encoder-bound connectors. Well-formed ioctl sequences cannot create that state (legacy set_config requires >=1 connector to enable), but the DF-4469-family fail-path corruption in drm_crtc_helper.c can, converting a latent inconsistency into a one-ioctl local DoS for a DRM master. Not reproduced live: the audit guest has no DRM device at all (QEMU std-VGA; /dev/dri and /dev/drm* absent), so SetPlane is not issuable; PoC deliberately skipped per contract for an Info hardening finding. Recommended hardening: return -EINVAL instead of BUG_ON (fix.diff).
No comments yet.