⬢ DragonFlyBSD Kernel Audit
DF-4369 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/raid/hpt27xx/hpt27xx_osm_bsd.c
+++ b/sys/dev/raid/hpt27xx/hpt27xx_osm_bsd.c
@@ -408,10 +408,25 @@ static void hpt_bus_scan_cb(struct cam_p
 static int	hpt_rescan_bus(void);
 
+/*
+ * Timeout runs in softclock context with no lock held.  os_cmddone()
+ * callout_stop()s the timer and then releases pCmd (ldm_free_cmds) and
+ * ext (cmdext_put); callout_stop() does NOT interlock against a timeout
+ * that is already in-progress on another CPU, so by the time we run the
+ * command may already be back on the LDM freelist -- and if it has been
+ * recycled (e.g. as a flush command whose priv points at a kernel stack
+ * variable, or an internal LDM command) pCmd->priv no longer identifies
+ * our OS_CMDEXT.  Serialize with the completion paths (they hold the same
+ * lock, which is recursive) and re-validate priv before touching it.
+ */
 static void hpt_timeout(void *arg)
 {
 	PCOMMAND pCmd = (PCOMMAND)arg;
 	POS_CMDEXT ext = (POS_CMDEXT)pCmd->priv;
+	PVBUS_EXT vbus_ext = ext->vbus_ext;
 
-	KdPrint(("pCmd %p timeout", pCmd));
+	KdPrint(("pCmd %p timeout", pCmd));
 
-	ldm_reset_vbus((PVBUS)ext->vbus_ext->vbus);
+	hpt_lock_vbus(vbus_ext);
+	/* Command still ours (not completed-and-recycled)? */
+	if (pCmd->done == os_cmddone && (POS_CMDEXT)pCmd->priv == ext)
+		ldm_reset_vbus((PVBUS)vbus_ext->vbus);
+	hpt_unlock_vbus(vbus_ext);
 }
 
 static void os_cmddone(PCOMMAND pCmd)