# DF-4196 — lock-free `hwmode.crtc_clock = 0` store vs. reloaded divisor in drm_calc_vbltimestamp_from_scanoutpos (TOCTOU divide-by-zero / kernel #DE)

File under audit: `sys/dev/drm/drm_vblank.c` (pass 2)

## What

`drm_crtc_vblank_off()` clears the mode-timing sentinel
`vblank->hwmode.crtc_clock = 0` at **drm_vblank.c:1169 — after both
`dev->event_lock` (:1165) and `dev->vbl_lock` (:1150) have been released**,
i.e. completely lock-free.

`drm_calc_vbltimestamp_from_scanoutpos()` checks the sentinel at
**drm_vblank.c:618** (`if (mode->crtc_clock == 0) return false;`) and then,
after the opaque external call `dev->driver->get_scanout_position()`
(:637-641 — an MMIO loop up to `DRM_TIMESTAMP_MAXRETRIES` long, which forces
the compiler to *re-load* `mode->crtc_clock`), divides by it at
**drm_vblank.c:671-672**:

```c
delta_ns = div_s64(1000000LL * (vpos * mode->crtc_htotal + hpos),
                   mode->crtc_clock);
```

If the :1169 store lands between :618 and :671, the re-loaded divisor is 0 →
integer divide exception (#DE) → kernel panic.

## Why the existing locks do NOT close the window

The vblank-query reader path is:

    drm_wait_vblank_ioctl / drm_crtc_get_sequence_ioctl   (both DRM_UNLOCKED,
                                                           no DRM_AUTH:
                                                           drm_ioctl.c:639,699)
      → drm_vblank_get()                 drm_vblank.c:988  takes vbl_lock
        → drm_vblank_enable()            drm_vblank.c:946  takes vblank_time_lock
          → drm_update_vblank_count()    drm_vblank.c:961
            → drm_get_last_vbltimestamp → drm_calc_vbltimestamp_from_scanoutpos

That path holds `vbl_lock` + `vblank_time_lock` but **never `event_lock`**,
and the racing store at :1169 executes **after** the modeset thread has
released *all* locks. Every other caller of the calc helper holds
`event_lock` (irq handler :1695, `drm_crtc_arm_vblank_event`) and is
serialized against the :1129-1165 event_lock section — but the store itself
is *outside* that section. CPU A inside the check→MMIO→divide window and
CPU B executing :1165→:1169 conflict on no lock.

Attacker model (unprivileged half of the race): any local user who can open
the DRM device node hammers WAIT_VBLANK/CRTC_GET_SEQUENCE get/put cycles;
the other half (a CRTC-off modeset) is performed by the display server
(DRM master) during ordinary activity (DPMS off, screen lock, mode change,
lid switch). Both DRM vblank query ioctls are `DRM_UNLOCKED` with no auth
requirement (drm_ioctl.c:639,699).

Impact: kernel #DE → panic (local DoS). No memory corruption (uid=0 chain
N/A for this class).

## How it is proven here

`vblank_toctou.c` reproduces the exact interleaving on the DragonFly guest
with two threads + a barrier (deterministic, 3/3 runs):

* thread A (vblank query): passes the :618 check on the live value, then
  blocks inside the stand-in `get_scanout_position()` (the MMIO window);
* thread B (modeset tail): prints that event_lock is released and executes
  the verbatim `vblank->hwmode.crtc_clock = 0;` (:1169);
* thread A resumes and executes the verbatim :671-672 division on the
  re-loaded field → **SIGFPE** (userland analog of the kernel #DE).

`./vblank_toctou race_fixed` runs the same interleaving with the fix
(divisor cached into a local before the check, exactly what fix.diff does to
sys/dev/drm/drm_vblank.c) and survives cleanly.

## Reproduce

```
cc -O2 -Wall -pthread -o vblank_toctou vblank_toctou.c
./vblank_toctou race         # expect: "Floating point exception", rc=136
./vblank_toctou race_fixed   # expect: "SURVIVED ... cached-nonzero", rc=0
```

Full untrimmed output: `run.log` (baseline + fixed + 3x determinism loop).

## Hardware gating (honest scope)

The guest has no DRM-capable GPU (`ls /dev/dri` → ENOENT; only ehci/xhci
modules loaded), so the in-kernel panic could not be exercised directly.
The interleaving itself, the lock-freedom of the store, the forced reload,
and the #DE are all proven at source level + deterministic harness. This
mirrors the DF-3869 verification convention (hardware-gated #DE family).
