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

drm_atomic_helper: unguarded CRTC helper-callback dereferences β€” funcs->dpms/funcs->commit fallbacks and crtc->helper_private derefs lack the NULL guards of upstream fe6169288666/26564be56d73

Summary

The DFly DRM sync predates two upstream robustness fixes: disable_outputs() falls through to an unconditional funcs->dpms(crtc, DRM_MODE_DPMS_OFF) when prepare/atomic_disable/disable are all NULL, commit_modeset_enables() calls funcs->commit(crtc) unconditionally when atomic_enable is NULL, and mode_fixup()/disable_outputs() dereference crtc->helper_private without a !funcs guard. A driver registering a CRTC with a partial drm_crtc_helper_funcs table and using drm_atomic_helper_commit() gets a NULL indirect call (kernel panic) on the first CRTC disable/enable. Hardening divergence only: exhaustively verified that no in-tree driver can trigger it (i915/amdgpu_dm have their own commit tails; classic DCE tables set the fields; radeon is legacy-helper based). Worst case for a future/out-of-tree driver is a NULL function-pointer call = panic (DoS), not attacker-controlled IP.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3792 Β· 5 files
FileTypeDescriptionSize
README.md β€” 3.2 KB ↓ raw
VERDICT.md β€” 3.2 KB ↓ raw
fix.diff β€” 1.3 KB view raw
verdict.json β€” 2.5 KB view raw
manifest.json β€” 1.1 KB view raw

DF-3792 β€” Missing upstream fix fe6169288666 (+26564be56d73): unguarded

CRTC helper-callback dereferences in disable_outputs() / commit_modeset_enables() / mode_fixup()

DragonFlyBSD sys/dev/drm/drm_atomic_helper.c (~v5.0-rc vintage) does not carry two upstream robustness fixes for the CRTC helper-function table:

  1. fe6169288666 "drm/atomic-helper: Make atomic_enable/disable crtc callbacks optional" (v5.2 window, 2019-03): * disable_outputs() β€” upstream changed the final fallback else funcs->dpms(crtc, DRM_MODE_DPMS_OFF); to else if (funcs->dpms). Local still calls it unconditionally: sys/dev/drm/drm_atomic_helper.c:1007-1014. * commit_modeset_enables() β€” upstream changed else funcs->commit(crtc); to else if (funcs->commit). Local: sys/dev/drm/drm_atomic_helper.c:1253-1260. A driver whose drm_crtc_helper_funcs provides none of prepare/atomic_disable/disable/dpms (resp. neither atomic_enable nor commit) gets a NULL indirect call β†’ kernel panic on any CRTC disable (resp. enable) through the generic commit path.

  2. 26564be56d73 "drm/atomic-helper: Make crtc helper funcs optional" (2019-09): mode_fixup() β€” upstream if (!funcs || !funcs->mode_fixup); local sys/dev/drm/drm_atomic_helper.c:461-463 dereferences crtc->helper_private unconditionally (if (!funcs->mode_fixup)). Same class in disable_outputs() itself (funcs = crtc->helper_private; then funcs->prepare read at :1007) β€” a NULL helper_private panics before any callback dispatch.

Reachability in this tree (verified: no in-tree trigger)

  • i915 β€” intel_helper_funcs (sys/dev/drm/i915/intel_display.c:10939) sets only .atomic_begin/.atomic_flush/.atomic_check, i.e. it would hit the NULL funcs->dpms/funcs->commit fallbacks β€” but i915 never reaches the generic helpers: it has its own intel_atomic_commit / intel_atomic_commit_tail (intel_display.c:12787) which do not call drm_atomic_helper_commit_modeset_disables/enables or disable_outputs().
  • amdgpu (DC) β€” amdgpu_dm_crtc_helper_funcs (amdgpu_dm.c:3403) sets .disable; and amdgpu_dm has its own commit tail (amdgpu_dm_atomic_commit_tail, amdgpu_dm.c:4733) that also never calls the generic disable/enable loops.
  • amdgpu (classic DCE) β€” all dce*_crtc_helper_funcs variants set .dpms, .disable, .commit (e.g. dce_v8_0_crtc_helper_funcs, dce_v8_0.c:2562).
  • radeon β€” legacy CRTC-helper driver (does not use the atomic commit path that reaches these helpers).

So today this is a hardening divergence: any out-of-tree / future driver that registers a CRTC with a partial helper table and uses drm_atomic_helper_commit() panics the kernel on the first modeset. Upstream considered the missing guards a real bug and fixed them; the DFly port simply pre-dates the fix.

Reproduce (hypothetical driver β€” NOT possible on this guest)

Not executable on this guest: no DRM-capable GPU (see env.txt in findings/poc/DF-3791/), and no in-tree driver triggers it. Source-level proof only (diff against upstream fix commits).

Fix

Apply fix.diff (backport of the three upstream guard changes).

VERDICT.md
↓ download raw

DF-3792 VERDICT

Status: inconclusive (runtime verification impossible on this guest β€” no DRM device; also no in-tree trigger exists). Severity: Info (hardening). Confidence: certain that the divergence exists (direct diff against upstream fix commits fe6169288666 and 26564be56d73).

Narrative

Pass-2 diff of sys/dev/drm/drm_atomic_helper.c (4021 LOC) against upstream v4.19/v5.0/v5.1/v5.2/v5.4 references shows the local file is an ~v5.0-rc snapshot minus several later fixes. Three unguarded callback dereferences were fixed upstream after this snapshot:

  1. disable_outputs() CRTC loop β€” local sys/dev/drm/drm_atomic_helper.c:1007-1014: prepare/atomic_disable/disable all NULL β†’ falls into funcs->dpms(crtc, DRM_MODE_DPMS_OFF) with dpms possibly NULL β†’ call through NULL β†’ panic. Upstream fe6169288666 (v5.2 window) changed the tail to else if (funcs->dpms).
  2. commit_modeset_enables() CRTC loop β€” local :1253-1260: atomic_enable NULL β†’ funcs->commit(crtc) with commit possibly NULL. Same upstream fix added else if (funcs->commit).
  3. mode_fixup() β€” local :461-463 reads funcs->mode_fixup with funcs = crtc->helper_private possibly NULL (upstream 26564be56d73 added !funcs ||). Same NULL-funcs exposure at :1007 (new_crtc_state->enable && funcs->prepare).

These are NULL function-pointer calls (page-fault/panic), not attacker- controlled IP β€” no exploitation path beyond DoS, and then only for a driver that registers a CRTC with a partial drm_crtc_helper_funcs table and uses the generic drm_atomic_helper_commit() machinery.

In-tree reachability: exhaustively checked, none

  • i915 intel_helper_funcs (intel_display.c:10939-10944) is exactly the partial table that would trigger it β€” but i915 uses its own intel_atomic_commit/intel_atomic_commit_tail (intel_display.c:12787) which never call disable_outputs()/commit_modeset_enables().
  • amdgpu_dm has its own commit tail (amdgpu_dm.c:4733) and its CRTC helper table sets .disable (amdgpu_dm.c:3403-3407).
  • classic amdgpu DCE tables all set .dpms/.disable/.commit (e.g. dce_v8_0.c:2562-2571), and classic amdgpu is legacy-helper based.
  • radeon is legacy-crtc-helper based (never reaches the atomic commit path).

Conclusion: Info β€” the guards protect against future/out-of-tree drivers; upstream treats them as required robustness (both commits are merged fixes).

Why inconclusive at runtime

Guest has QEMU std VGA only (pciconf: chip=0x11111234), no virtio_gpu driver in the DFly tree, drm.ko not loaded, /dev/dri absent β€” no DRM device exists to exercise any path in this file (full survey in findings/poc/DF-3791/env.txt).

Exploit chain

None (NULL callback dispatch = panic at worst; no control over the called address).

Apply fix.diff (backport of the three upstream guards).

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

No DRM device / no triggering driver on the guest; guards are a straight backport of upstream-accepted fixes.

findings/poc/DF-3792/fix.diff (git-apply-able vs this tree, authored post-verification, never applied to sys/).
↓ fix.diffper-fix-DF-3792

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md; README.md (in-tree reachability survey); fix.diff; env evidence in findings/poc/DF-3791/env.txt

PoC changes

No PoC produced: triggering requires a DRM driver with a partial crtc helper table; none exists in-tree and no DRM device exists on the guest.

Verified recommended fix

Backport the three NULL guards (see fix.diff in this pack).

Verdict

Source-level certain, runtime-inconclusive, Info severity: sys/dev/drm/drm_atomic_helper.c:1011-1014 calls funcs->dpms() unconditionally when prepare/atomic_disable/disable are all NULL, :1257-1260 calls funcs->commit() unconditionally when atomic_enable is NULL, and :461-463/:1007 dereference crtc->helper_private without a !funcs guard β€” all three guards were added upstream by fe6169288666 and 26564be56d73 (v5.2 window) and are missing from this ~v5.0-rc snapshot. In-tree drivers cannot trigger it (i915 and amdgpu_dm use their own commit tails; classic DCE/radeon set the hooks), so impact is limited to out-of-tree/future drivers with partial crtc helper tables: a NULL function-pointer call, i.e. kernel panic/DoS, not attacker-controlled IP. Guest has no DRM device at all (QEMU std VGA, no driver, /dev/dri absent), so nothing could be executed.