# DF-4625 — poll worker's self-rearm survives cancel_delayed_work_sync → post-cancel execution (mid-suspend MMIO / teardown UAF)

## What

`output_poll_execute()` (sys/dev/drm/drm_probe_helper.c:570-655) re-arms
itself at :653-654 (`if (repoll) schedule_delayed_work(delayed_work,
DRM_OUTPUT_POLL_PERIOD)` = 10 s). Upstream Linux relies on
`cancel_delayed_work_sync()` reliably killing self-rearming work. The
DragonFly drm shim's `cancel_delayed_work_sync()`
(sys/dev/drm/linux_workqueue.c:415-424) does not:

1. `:420-421` it sets `canceled` and runs `callout_cancel(&dwork->timer)`
   **first**. If the callback is currently executing, the timer is idle —
   `callout_cancel` (kern_timeout.c:1032) finds nothing, consumes/clears
   the CANCEL state and returns.
2. `_cancel_work` (:340-388) then parks in `lksleep` waiting for
   `running == false`.
3. The running callback (our poll worker) reaches :654 and calls
   `schedule_delayed_work()` → `callout_reset()` **arms a fresh timer**
   (nothing blocks a reset; the CANCEL flag was consumed in step 1) —
   then returns.
4. `_cancel_work` wakes, sees `on_queue == 0 && running == 0` and
   **breaks** — a pending timer is invisible to it. `cancel_delayed_work_sync`
   returns with the timer armed.
5. Up to 10 s later the timer fires → `_delayed_work_fn` → `queue_work()`,
   which **clears `work->canceled`** (linux_workqueue.c:135) and re-queues
   — the full poll cycle executes *after* its caller was promised the work
   is dead.

Victim contract: `drm_kms_helper_poll_disable()` (:695-700, `cancel_delayed_work_sync`
at :699) and `drm_kms_helper_poll_fini()` (:735-739) — called from
amdgpu suspend (`amdgpu_device.c:985`, `:2813`), radeon suspend
(`radeon_device.c:1233`, `:1620`), i915 (`i915_drv.c:1983`), and teardown
(`i915_drv.c:1663`, amdgpu `dce_v8/10/11/virtual`, `radeon_display.c:1652`,
`drm_modeset_helper.c:185`).

Impact: a full connector-detect cycle racing device suspend (MMIO against
a powered-down GPU → hang/panic) or executing on a torn-down drm_device
(use-after-free of `dev->mode_config` / freed connectors).

## Build / run (DragonFly guest, fresh boot)

    sh build.sh    # verbatim-extracts the shim from /usr/src, builds
                   # df4625.ko (baseline) and fixed/df4625fix.ko
    sh run.sh      # kldload both; verdicts land in dmesg

The KLD compiles `process_all_work`, `wq_worker_thread`, `queue_work`,
`_delayed_work_fn`, `queue_delayed_work`, `_cancel_work`, and the
`cancel_*` trio **verbatim** against the real kernel primitives
(lockmgr / lksleep / callout_reset / callout_cancel / lwkt), then runs a
poll-worker-shaped self-rearming delayed work and cancels it mid-callback.
The modules deliberately refuse to unload (the verbatim worker thread
loops forever — the already-filed DF-1977 wart); reboot after use.

## Expected output (dmesg)

baseline:
    df4625: canceller: cancel_delayed_work_sync() RETURN (timer pending=1 on_queue=0 running=0)
    df4625: EXEC #2 entered
    df4625: DF-4625 REPRODUCED: work executed AFTER cancel_delayed_work_sync() returned (exec_count 1 -> 2)
fixed:
    df4625: canceller: cancel_delayed_work_sync() RETURN (timer pending=0 on_queue=0 running=0)
    df4625: FIX VALIDATED: no execution after cancel_delayed_work_sync() returned within the window (exec_count=1)

The `timer pending=1` at cancel-return is the structural miss made
directly observable; `EXEC #2` is the post-cancel execution.

## Files

df4625.c                 KLD: environment + scenario (fixed variant via -DDF4625_FIXED)
build.sh / run.sh        exact build/run
fix.diff                 converging cancel_delayed_work_sync (linux_workqueue.c)
df4625_extracted.inc     the verbatim shim extraction
build.log / run.log / env.txt   full untrimmed evidence
