DF-5783 / cursor_active_planes_race.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | /* DF-5783 trigger โ legacy cursor fast-path corrupts live crtc->state->active_planes * * Target: DragonFlyBSD sys/dev/drm/i915/intel_display.c (vendored Linux v4.20) * Missing upstream fix: c249c5f64336 "drm/i915: Handle cursor updating * active_planes correctly, v2." (landed Linux v5.0). * * Mechanism (code-reading proof; requires Intel i915 hardware + DRM master): * 1. intel_legacy_cursor_update() (intel_display.c:13623) is the cursor * plane's ->update_plane fast path, taken when the crtc is active and no * modeset is pending (:13644-13646). * 2. At :13686-13689 it calls * intel_plane_atomic_check_with_state(to_intel_crtc_state(crtc->state), * to_intel_crtc_state(crtc->state), ...) * passing the LIVE committed crtc state as BOTH old and new * (the upstream FIXME comment "need a new crtc state?" marks the spot). * 3. intel_plane_atomic_check_with_state() MUTATES the passed crtc_state: * intel_atomic_plane.c:129-132 โ visible ? active_planes |= BIT : * active_planes &= ~BIT(PLANE_CURSOR). For a cursor-off update the * PLANE_CURSOR bit is cleared in the LIVE state. * 4. If a later step fails โ e.g. mutex_lock_interruptible() at :13693 * returns -EINTR when a signal is delivered, or intel_plane_pin_fb() * at :13697 fails โ the function bails via out_unlock/out_free WITHOUT * undoing the mutation and WITHOUT touching hardware: the hardware * cursor is still ON, but crtc->state->active_planes no longer contains * PLANE_CURSOR. * 5. Per upstream c249c5f64336: "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." Consumers in this tree: intel_crtc_disable_planes / * disable_plane decisions, hsw_compute_ips_config (intel_display.c:6666), * watermark calculations (intel_pm.c) โ desynchronized plane-disable and * wm/IPS programming => display engine underruns / hung pipe, i.e. * local DoS of the display subsystem by a DRM master. * * NOTE on the concurrent page-flip aspect: the same commit's v2 note explains * the live state also cannot be swapped/freed here because a concurrent * atomic commit or page flip still references it. * * Trigger sequence (needs DRM master, i.e. unprivileged user who becomes * master on an idle VT, or root X exiting): * a) cursor ON : DRM_IOCTL_MODE_CURSOR2 with a valid bo handle * b) race : DRM_IOCTL_MODE_CURSOR2 with flags=DRM_MODE_CURSOR_BO (fb * handle = 0 => cursor-off geometry) while a signal is * queued so the -EINTR path at :13693 aborts AFTER the * check-phase mutation * c) observe : dmesg shows no cursor disable; following modeset off * leaves hardware cursor scanning on a disabled pipe * (FIFO underrun spam / dead pipe until reboot or * re-modeset with full sanitize). * * On the audit rig (Bochs VGA, no i915) this exits at card discovery. */ #include <sys/types.h> #include <sys/ioctl.h> #include <fcntl.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <dev/drm/drm.h> #include <dev/drm/drm_mode.h> int main(void) { char path[64]; struct drm_version ver; char name[128] = {0}; int fd; for (int i = 0; i < 16; i++) { snprintf(path, sizeof(path), "/dev/dri/card%d", i); if (access(path, F_OK) == 0) goto found; } printf("no /dev/dri/card* โ nothing to test\n"); return 2; found: fd = open(path, O_RDWR); if (fd < 0) { perror("open"); return 2; } memset(&ver, 0, sizeof(ver)); ver.name_len = sizeof(name) - 1; ver.name = name; if (ioctl(fd, DRM_IOCTL_VERSION, &ver) < 0) { perror("version"); return 2; } if (strcmp(name, "i915") != 0) { printf("driver is '%s', not i915 โ cannot test on this rig\n", name); close(fd); return 3; } /* Real hardware sequence (needs master; abbreviated here): * 1. ioctl(DRM_IOCTL_SET_MASTER) * 2. cursor ON with a small ARGB bo (handle H) * 3. fork a child that SIGALRMs us, then issue the cursor-OFF * update (DRM_IOCTL_MODE_CURSOR2, handle=0) so that the signal * lands inside intel_legacy_cursor_update() between the * check-phase mutation (13686-13689) and the mutex/pin steps * (13693/13697) -> -EINTR bail-out with corrupted * crtc->state->active_planes. * 4. immediately DRM_IOCTL_MODE_SETCRTC-disable the pipe and watch * the cursor survive on hardware (underrun storm in dmesg). */ printf("i915 present โ run the master-gated cursor race sequence " "(see header comment) on the target machine.\n"); close(fd); return 0; } |