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)
PoC verification
Evidence pack
findings/poc/DF-5783 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 β 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
-
intel_legacy_cursor_update()βsys/dev/drm/i915/intel_display.c:13623fast path (taken when crtc active, no modeset pending: :13644-13646). -
:13686-13689passes 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)); -
intel_plane_atomic_check_with_state()mutates itscrtc_stateargument:sys/dev/drm/i915/intel_atomic_plane.c:129-132c 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 clearsPLANE_CURSORfrom the LIVEcrtc->state->active_planes. -
Failure exits come after the mutation and do not roll it back:
mutex_lock_interruptible():13693 (-EINTR, signal-raceable) andintel_plane_pin_fb():13697 (pin failure) both bail viaout_unlock/out_free(:13722-13729) while the hardware cursor is still enabled. State now says "no cursor", hardware says "cursor on". -
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_CURSOR2areDRM_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 callDRM_IOCTL_SET_MASTERunprivileged (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_testablefix.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.
Confirmed kernel references
- sys/dev/drm/i915/intel_display.c:13623
- sys/dev/drm/i915/intel_display.c:13644
- sys/dev/drm/i915/intel_display.c:13686
- sys/dev/drm/i915/intel_display.c:13693
- sys/dev/drm/i915/intel_display.c:13697
- sys/dev/drm/i915/intel_atomic_plane.c:110
- sys/dev/drm/i915/intel_atomic_plane.c:129
- sys/dev/drm/drm_ioctl.c:659
- sys/dev/drm/drm_ioctl.c:680
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.
No comments yet.