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

i915: intel_legacy_cursor_update() fast path mutates the LIVE crtc state's active_planes before a fallible section β€” failure leaves state desynchronized from hardware (missing upstream c249c5f64336), breaking subsequent plane disable / IPS / watermark decisions

Summary

The cursor fast path passes the LIVE committed crtc state as BOTH old and new to the check that clears/sets active_planes - a cursor-off update followed by an aborting failure (signal-raceable mutex at :13693 or pin failure at :13697) bails without rollback: hardware still scans the cursor while crtc->state->active_planes no longer contains it, so a following modeset-disable fails to disable planes (upstream's own impact statement) and IPS/watermark decisions desync - display-engine DoS. DRM master required (any local opener can take master when free). No hardware in the rig - honestly untested; duplicate-state backport fix authored.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5783 Β· 8 files
FileTypeDescriptionSize
README.md β€” 985 B ↓ raw
VERDICT.md β€” 3.5 KB ↓ raw
cursor_active_planes_race.c β€” 4.8 KB view raw
build.sh β€” 123 B view raw
run.sh β€” 111 B view raw
fix.diff β€” 2.7 KB view raw
verdict.json β€” 3.7 KB view raw
env.txt β€” 273 B view raw

DF-5783 β€” legacy cursor update corrupts live crtc->state->active_planes on failure (missing upstream c249c5f64336)

Build / Run / Expected (on i915 hardware, as DRM master)

cc -O2 -o /tmp/cursor_active_planes_race cursor_active_planes_race.c
/tmp/cursor_active_planes_race     # card/driver discovery; race sequence in header

Vulnerable driver: cursor-off fast-path update aborted by a signal after the check phase leaves crtc->state->active_planes without PLANE_CURSOR while the hardware cursor is still on; the next modeset-disable misses the cursor plane (underruns / dead pipe). Fixed driver (v5.0+ c249c5f64336): mutation happens on a duplicated state and only active_planes is copied back on success.

On this audit rig

Bochs VGA only (no i915) β€” program exits at driver discovery. Verification is static + upstream differential (VERDICT.md).

Contents

cursor_active_planes_race.c, VERDICT.md, fix.diff (backport), verdict.json, manifest.json, env.txt

VERDICT.md
↓ download raw

VERDICT β€” DF-5783

Status: untested (no Intel GPU on the audit rig; verified by line-level static analysis + upstream differential). Impact ceiling: local DoS / persistent display-engine state corruption (underruns, hung pipe, cursor left scanning on a disabled pipe), by a DRM master. Not memory corruption: the corrupted value is a plane bitmask inside the committed crtc state.

Proven chain

  1. intel_legacy_cursor_update() β€” sys/dev/drm/i915/intel_display.c:13623 fast path (taken when crtc active, no modeset pending: :13644-13646).

  2. :13686-13689 passes the live committed crtc state twice: c ret = intel_plane_atomic_check_with_state(to_intel_crtc_state(crtc->state), to_intel_crtc_state(crtc->state), /* FIXME need a new crtc state? */ to_intel_plane_state(plane->state), to_intel_plane_state(new_plane_state));

  3. intel_plane_atomic_check_with_state() mutates its crtc_state argument: sys/dev/drm/i915/intel_atomic_plane.c:129-132 c if (state->visible) crtc_state->active_planes |= BIT(intel_plane->id); else crtc_state->active_planes &= ~BIT(intel_plane->id); A cursor-off request therefore clears PLANE_CURSOR from the LIVE crtc->state->active_planes.

  4. Failure exits come after the mutation and do not roll it back: mutex_lock_interruptible() :13693 (-EINTR, signal-raceable) and intel_plane_pin_fb() :13697 (pin failure) both bail via out_unlock/out_free (:13722-13729) while the hardware cursor is still enabled. State now says "no cursor", hardware says "cursor on".

  5. Upstream fixed exactly this in v5.0, commit c249c5f64336 ("Handle cursor updating active_planes correctly, v2."): "we may clear active_planes if the new cursor update state will disable the cursor, but we fail after. If this is immediately followed by a modeset disable, we may soon not disable the planes correctly when we start depending on active_planes." The v2 note also documents the concurrent page-flip/ atomic-commit hazard of touching the live state. Downstream consumers of the stale bit in this tree: plane-disable decisions, hsw_compute_ips_config (intel_display.c:6666 β€” IPS with no planes), watermark recomputation (intel_pm.c) β†’ underrun/hung-display class failure.

Threat bound (honest)

  • DRM_IOCTL_MODE_CURSOR/MODE_CURSOR2 are DRM_MASTER|DRM_UNLOCKED (sys/dev/drm/drm_ioctl.c:659,680) β€” the trigger needs DRM master. On DragonFly there is no logind-style master handoff; any local user who can open the device while no master exists can call DRM_IOCTL_SET_MASTER unprivileged (standard "untrusted master" model upstream also treats as a security boundary for cursor bugs).
  • Requires Intel i915 hardware; absent on this rig (Bochs VGA), hence untested runtime, static+differential proof only.

Fix

Backport of c249c5f64336 essence: duplicate the crtc state, run the check against the duplicate, and copy only active_planes back into the live state on full success (never on failure). See fix.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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff is the faithful backport of upstream c249c5f64336 against the DFly file layout (incl. the duplicate-state + active_planes-only copy-back and the destroy on the error path). Not built/tested: no Intel GPU on the rig; on hardware the upstream igt kms_cursor_legacy / kms_pipe_crc_basic suites exercise both directions.

fix.diff; upstream commit c249c5f64336 message (quoted in VERDICT.md); intel_atomic_plane.c:129-132 mutation site.
↓ fix.diffper-fix-DF-5783

Confirmed kernel references

Detail

Exploit chain

open /dev/dri/card0 -> DRM_IOCTL_SET_MASTER (unprivileged when master slot free) -> cursor ON via DRM_IOCTL_MODE_CURSOR2 -> issue cursor-OFF update raced with a signal so intel_legacy_cursor_update() aborts at the interruptible mutex after the check-phase mutation -> crtc->state->active_planes corrupted vs hardware -> immediately disable the crtc: cursor plane not disabled correctly -> FIFO underruns / dead pipe (DoS) until full re-modeset.

Evidence (decisive lines)

VERDICT.md (chain with path:line); fix.diff (backport of upstream c249c5f64336); cursor_active_planes_race.c (trigger sketch); differential in ../DF-5782/differential/ (proves base == upstream v4.20, i.e. fix absent); env.txt (Bochs-only rig).

PoC changes

n/a β€” trigger authored fresh (card discovery + documented master-gated race sequence); could not run against i915 on this rig.

Verified recommended fix

Backport upstream c249c5f64336: run the cursor fast-path check against a duplicated crtc state and copy only active_planes back on success (fix.diff).

Verdict

intel_legacy_cursor_update() (intel_display.c:13623) passes the LIVE committed crtc state as both old and new to intel_plane_atomic_check_with_state (:13686-13689, the upstream 'FIXME need a new crtc state?' spot), whose check path clears/sets active_planes on that state (intel_atomic_plane.c:129-132). A cursor-off fast-path update that fails afterwards (mutex_lock_interruptible -EINTR at :13693 or intel_plane_pin_fb failure at :13697) bails out without rolling the mutation back: hardware still scans the cursor while crtc->state->active_planes no longer contains PLANE_CURSOR. Upstream fixed this exact bug in v5.0 (c249c5f64336): subsequent modeset-disable then fails to disable the planes correctly, desynchronizing IPS/watermark/plane-disable programming => display-engine underruns/hung pipe (local DoS by a DRM master). Not memory corruption. NOT executed: rig has Bochs VGA only; verified by static path proof + upstream differential.