DF-5177 / fix.diff
--- a/sys/dev/drm/include/linux/hrtimer.h +++ b/sys/dev/drm/include/linux/hrtimer.h @@ -58,6 +58,7 @@ bool is_rel; struct globaldata *gd; enum hrtimer_restart (*function)(struct hrtimer *); + struct taskqueue *tq; /* home taskqueue, pinned at init */ struct lwkt_token timer_token; }; --- a/sys/dev/drm/linux_hrtimer.c +++ b/sys/dev/drm/linux_hrtimer.c @@ -34,7 +34,7 @@ __hrtimer_function(systimer_t info, int in_ipi, struct intrframe *frame) { struct hrtimer *timer = info->data; - struct taskqueue *tq = taskqueue_thread[mycpuid]; + struct taskqueue *tq = timer->tq; BUG_ON(taskqueue_enqueue(tq, &timer->task) != 0); } @@ -81,6 +81,7 @@ timer->cancel = false; timer->gd = NULL; TASK_INIT(&timer->task, 1, __hrtimer_task, timer); + timer->tq = taskqueue_thread[mycpuid]; lwkt_token_init(&timer->timer_token, "timer token"); } @@ -109,7 +110,6 @@ lwkt_gettoken(&timer->timer_token); timer->cancel = false; - timer->running = false; timer->active = true; timer->gd = mycpu; systimer_init_oneshot(&timer->st, __hrtimer_function, timer, @@ -123,28 +123,50 @@ { int ret = 0; - lwkt_gettoken(&timer->timer_token); - if (timer->active) { - struct taskqueue *tq = taskqueue_thread[timer->gd->gd_cpuid]; - bool running = timer->running; - + /* + * Loop until the timer is fully quiescent. A concurrent + * hrtimer_start() (legal from any cpu, e.g. while our callback + * is in flight) re-arms the timer and reassigns timer->gd, so a + * single unlocked pass can snapshot a stale gd/taskqueue and + * return while the callback is still executing (leading to a + * use-after-free when the caller frees the timer). + */ + for (;;) { + struct taskqueue *tq; + struct globaldata *gd; + + lwkt_gettoken(&timer->timer_token); + if (!timer->active) { + lwkt_reltoken(&timer->timer_token); + break; + } + tq = timer->tq; + gd = timer->gd; ret = 1; timer->cancel = true; - lwkt_reltoken(&timer->timer_token); - if (running) { - /* Nothing */ - } else if (timer->gd == mycpu) { + + /* + * Keep the token held across systimer_del() (including + * the cpu migration) so a concurrent hrtimer_start() + * cannot reassign timer->gd / re-arm timer->st while we + * are deleting it on the owning cpu. + */ + if (gd == mycpu) { systimer_del(&timer->st); } else { struct globaldata *oldcpu = mycpu; - lwkt_setcpu_self(timer->gd); + lwkt_setcpu_self(gd); systimer_del(&timer->st); lwkt_setcpu_self(oldcpu); } + lwkt_reltoken(&timer->timer_token); + while (taskqueue_cancel(tq, &timer->task, NULL)) taskqueue_drain(tq, &timer->task); - } else { - lwkt_reltoken(&timer->timer_token); + /* + * A concurrent start may have re-armed; the loop + * re-checks timer->active under the token. + */ } return ret; } |