⬢ DragonFlyBSD Kernel Audit
DF-4196 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/drm/drm_vblank.c
+++ b/sys/dev/drm/drm_vblank.c
@@ -590,6 +590,7 @@
 	struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
 	int vpos, hpos, i;
 	int delta_ns, duration_ns;
+	int crtc_clock, crtc_htotal;
 
 	if (!drm_core_check_feature(dev, DRIVER_MODESET))
 		return false;
@@ -612,10 +613,20 @@
 	else
 		mode = &crtc->hwmode;
 
+	/*
+	 * Snapshot the mode timings into locals: drm_crtc_vblank_off() clears
+	 * vblank->hwmode.crtc_clock after releasing event_lock/vbl_lock, so a
+	 * re-load of mode->crtc_clock between the zero check below and the
+	 * division further down (the opaque ->get_scanout_position() call in
+	 * between forces a reload) could divide by zero and panic the kernel.
+	 */
+	crtc_clock = mode->crtc_clock;
+	crtc_htotal = mode->crtc_htotal;
+
 	/* If mode timing undefined, just return as no-op:
 	 * Happens during initial modesetting of a crtc.
 	 */
-	if (mode->crtc_clock == 0) {
+	if (crtc_clock == 0) {
 		DRM_DEBUG("crtc %u: Noop due to uninitialized mode.\n", pipe);
 		WARN_ON_ONCE(drm_drv_uses_atomic_modeset(dev));
 
@@ -668,8 +679,8 @@
 	 * since start of scanout at first display scanline. delta_ns
 	 * can be negative if start of scanout hasn't happened yet.
 	 */
-	delta_ns = div_s64(1000000LL * (vpos * mode->crtc_htotal + hpos),
-			   mode->crtc_clock);
+	delta_ns = div_s64(1000000LL * (vpos * crtc_htotal + hpos),
+			   crtc_clock);
 
 	/* Subtract time delta from raw timestamp to get final
 	 * vblank_time timestamp for end of vblank.