DF-4196 / verdict.json
{ "finding_id": "DF-4196", "status": "inconclusive", "reproduced": 0, "impact": "dos", "confidence": "likely", "verdict": "Hardware-gated on this guest (no DRM/GPU device: /dev/dri absent, only ehci+xhci modules), so the in-kernel panic could not be exercised directly; the defect itself is proven deterministically on the DragonFly guest with a faithful two-thread interleaving harness: drm_crtc_vblank_off() stores vblank->hwmode.crtc_clock = 0 at drm_vblank.c:1169 completely lock-free (event_lock released at :1165, vbl_lock at :1150), while the no-event_lock reader path drm_vblank_get->drm_vblank_enable->drm_update_vblank_count->drm_calc_vbltimestamp_from_scanoutpos passes the crtc_clock==0 check at :618, spends microseconds in the opaque get_scanout_position() call (:637, which forces a compiler re-load of mode->crtc_clock), and then divides by the re-loaded field at :671-672 -> integer #DE -> kernel panic. Harness: 3/3 SIGFPE (rc=136) baseline, 3/3 survival with the cached-divisor fix. Unprivileged half of the race is any local user with the DRM node open hammering DRM_UNLOCKED WAIT_VBLANK/CRTC_GET_SEQUENCE (drm_ioctl.c:639,699); the modeset half is ordinary display-server activity (DPMS/screen-lock/mode change). Not memory corruption; uid=0 chain N/A for this class.", "exploit_chain": "", "evidence": "run.log: race -> Floating point exception rc=136 3/3 and race_fixed -> SURVIVED ... cached-nonzero rc=0 3/3; VERDICT.md lock table for drm_crtc_vblank_off (:1129-1169) and the reader path analysis showing no common lock; build.log", "kernel_refs": [ "sys/dev/drm/drm_vblank.c:1169", "sys/dev/drm/drm_vblank.c:618", "sys/dev/drm/drm_vblank.c:637", "sys/dev/drm/drm_vblank.c:671", "sys/dev/drm/drm_vblank.c:988", "sys/dev/drm/drm_vblank.c:961", "sys/dev/drm/drm_ioctl.c:639", "sys/dev/drm/drm_ioctl.c:699" ], "poc_changes": "harness written fresh (no seed): verbatim :618 check / :671-672 division (dividing the shared field, not a local) and verbatim :1169 store, orchestrated deterministically with a barrier inside a noinline opaque callee that receives the mode pointer (preserving the forced-reload property)", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 5, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -pthread -o vblank_toctou vblank_toctou.c", "run_cmd": "./vblank_toctou race; ./vblank_toctou race_fixed", "code_hash": "ae9c87ecf0bbd1f9f0cf6af5f1ac00039f535dbc5fdc27810e821e516a2efd12", "notes": "Guest has no GPU (ls /dev/dri -> ENOENT) so kernel-level trigger impossible here, matching the DF-3869 hardware-gating convention. All other calc callers hold event_lock (drm_handle_vblank:1695, drm_crtc_arm_vblank_event) and were verified safe; i915/amdgpu CRC callers (i915_irq.c:1772, amdgpu_dm_crc.c:150) are also exposed but debugfs/root-gated. Upstream Linux has the same lock-free store (inherited defect, worth reporting upstream too).", "recommended_fix": "Cache crtc_clock/crtc_htotal into locals before the zero-check in drm_calc_vbltimestamp_from_scanoutpos and divide by the cached local (see fix.diff); alternatively zero hwmode.crtc_clock while still holding vbl_lock+vblank_time_lock in drm_crtc_vblank_off", "fix_status": "inconclusive", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff caches the divisor into a local (exactly the race_fixed harness path): baseline harness SIGFPE 3/3 vs fixed harness survival 3/3. Full DRM kernel build/boot cannot be exercised on this guest (no GPU), so kernel-level fix validation is not possible; harness-level validation passed cleanly.", "fix_evidence": "run.log determinism loop (race#1-3 rc=136 / fixed#1-3 rc=0); fix.diff" } |