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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4839 Β· 5 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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; only vgapci0 QEMU 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 -EINVAL instead 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_testable
baseline no→ patch + rebuild →patched clean

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

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