# DF-4625 fix: make cancel_delayed_work_sync() converge for self-rearming # delayed work. The shim currently (a) cancels the callout BEFORE waiting # out the running callback -- a callback that re-arms itself (drm's # output_poll_execute, drm_probe_helper.c:653-654) then arms a fresh timer # nothing cancels, (b) _cancel_work() breaks as soon as the work is idle, # so the pending timer is invisible to it, and (c) queue_work() clears # work->canceled (linux_workqueue.c:135) when the timer eventually fires, # so the full callback re-executes up to DRM_OUTPUT_POLL_PERIOD (10 s) # AFTER cancel_delayed_work_sync() returned -- i.e. after # drm_kms_helper_poll_disable()/poll_fini() promised the work is dead # (mid-suspend MMIO probe, or UAF on a torn-down drm_device). # # Validated on the guest: with this loop, cancel returns with # timer pending=0 and no post-cancel execution (see run.log, FIXED build). --- a/sys/dev/drm/linux_workqueue.c +++ b/sys/dev/drm/linux_workqueue.c @@ -416,11 +416,24 @@ bool cancel_delayed_work_sync(struct delayed_work *dwork) { struct work_struct *work = &dwork->work; + bool ret; - work->canceled = true; - callout_cancel(&dwork->timer); + /* + * Self-rearming delayed work (e.g. drm's output_poll_execute) must + * not survive this call: the callback can re-arm the timer AFTER + * our callout_cancel() but BEFORE _cancel_work() observes the work + * idle, and queue_work() clears work->canceled when the timer + * eventually fires. Loop until the timer is dead AND the work is + * neither queued nor running. + */ + for (;;) { + ret = _cancel_work(work, true); + if (!callout_cancel(&dwork->timer) && + !work->on_queue && !work->running) + break; + } - return _cancel_work(work, true); + return ret; } bool