DragonFlyBSD Kernel Audit
DF-0236 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/kern/kern_wdog.c b/sys/kern/kern_wdog.c
--- a/sys/kern/kern_wdog.c
+++ b/sys/kern/kern_wdog.c
@@ -91,23 +91,41 @@
 wdog_reset_all(void *unused)
 {
 	struct watchdog *wd;
+	struct watchdog *snap[SMP_MAXCPU];
+	int n = 0;
+	int i;
 	int period, min_period = INT_MAX;
 
+	/*
+	 * DF-0236: driver callbacks (wd->wdog_fn) are arbitrary kernel code
+	 * that may sleep or perform slow MMIO.  Snapshot the list of
+	 * registered watchdogs under the spinlock, then drop the lock before
+	 * invoking any callback so a misbehaving/sleeping driver cannot
+	 * deadlock the global wdogmtx or stall the rest of the system.
+	 */
 	spin_lock(&wdogmtx);
-	if (LIST_EMPTY(&wdoglist))
-		goto done;
+	if (LIST_EMPTY(&wdoglist)) {
+		spin_unlock(&wdogmtx);
+		return;
+	}
 	LIST_FOREACH(wd, &wdoglist, link) {
-		period = wdog_reset(wd);
+		if (n < SMP_MAXCPU)
+			snap[n++] = wd;
+	}
+	spin_unlock(&wdogmtx);
+
+	for (i = 0; i < n; i++) {
+		period = wdog_reset(snap[i]);
 		if (period < min_period)
 			min_period = period;
 	}
+
+	spin_lock(&wdogmtx);
 	if (wdog_auto_enable) {
 		callout_reset(&wdog_callout, min_period * hz / 2,
 			      wdog_reset_all, NULL);
 	}
 	wdog_auto_period = min_period;
-
-done:
 	spin_unlock(&wdogmtx);
 }