⬢ DragonFlyBSD Kernel Audit
DF-5176 / fix.diff
← back to finding ↓ download raw
--- 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;
 }