⬢ DragonFlyBSD Kernel Audit
DF-5592 / fix.diff
← back to finding ↓ download raw
DF-5592 fix: pin the cam_sim while it is linked on cam_simq so the
completion softint (camisr) can never race the driver's detach-time
cam_sim_free(); make cam_sim_release() an atomic cmpset protocol like
its xpt_release_bus()/xpt_release_device() neighbors.

Protocol:
  * xpt_done() takes one sim reference (atomic_add_int under
    cam_simq_spin) when it links the sim onto cam_simq; the flag
    CAM_SIM_ON_DONEQ now exactly means 'linked on cam_simq'.
  * camisr() clears the flag for the sims it pops (under the same
    spin) and drops that reference only AFTER processing+unlock.
  * cam_sim_release() drops references with atomic_cmpset_int; the
    final 1->0 transition runs in xpt_sim_finalize() under
    cam_simq_spin so it cannot interleave with xpt_done()'s pin.
    A linked sim always has refcount >= 2 and can never hit the
    final kfree() while queued for / undergoing completion.
  * cam_dead_sim gets a permanently large refcount so camisr's pin
    drops can never 'finalize' the static structure.

--- cam_sim.c.orig	2026-10-08 09:56:18.479887051 +0000
+++ cam_sim.c	2026-10-08 09:56:32.167711585 +0000
@@ -243,15 +243,34 @@
 {
 	if (flags & CAM_SIM_SOFTC)
 		sim->softc = NULL;
-	if (sim->refcount == 1) {
-		if (sim->devq) {
-			cam_simq_release(sim->devq);
-			sim->devq = NULL;
+
+	/*
+	 * Atomic compare-and-set release protocol, mirroring the
+	 * xpt_release_bus()/xpt_release_device() pattern in cam_xpt.c.
+	 * A sim that is linked on cam_simq (pending completion processing
+	 * by the swi_cambio softint) holds one extra reference (see
+	 * xpt_done()), so it cannot reach the final kfree() here while
+	 * camisr still operates on it.
+	 */
+	for (;;) {
+		int count = sim->refcount;
+
+		cpu_ccfence();
+		if (count == 1) {
+			if (xpt_sim_finalize(sim)) {
+				if (sim->devq) {
+					cam_simq_release(sim->devq);
+					sim->devq = NULL;
+				}
+				kfree(sim, M_CAMSIM);
+				return;
+			}
+		} else {
+			if (atomic_cmpset_int(&sim->refcount,
+					      count, count - 1)) {
+				return;
+			}
 		}
-		sim->refcount = 0;
-		kfree(sim, M_CAMSIM);
-	} else {
-		--sim->refcount;
 	}
 }
 
--- cam_xpt_sim.h.orig	2026-10-08 09:56:18.479887051 +0000
+++ cam_xpt_sim.h	2026-10-08 09:56:41.675589720 +0000
@@ -45,6 +45,7 @@
 void		xpt_release_devq(struct cam_path *path, u_int count,
 				 int run_queue);
 void		xpt_done(union ccb *done_ccb);
+int		xpt_sim_finalize(struct cam_sim *sim);
 #endif
 
 #endif /* _CAM_CAM_XPT_SIM_H */
--- cam_xpt.c.orig	2026-10-08 09:56:18.479887051 +0000
+++ cam_xpt.c	2026-10-08 09:56:32.187711330 +0000
@@ -1466,6 +1466,12 @@
 	cam_dead_sim.lock = &cam_dead_lock;
 	lockinit(&cam_dead_lock, "XPT dead_sim lock", 0, LK_CANRECURSE);
 	cam_dead_sim.flags |= CAM_SIM_DEREGISTERED;
+	/*
+	 * camisr() drops the cam_simq pin of every sim it processes,
+	 * including dead_sim; keep its refcount permanently above one so
+	 * it can never be finalized/freed.
+	 */
+	cam_dead_sim.refcount = 0x40000000;
 
 	/*
 	 * The xpt layer is, itself, the equivelent of a SIM.
@@ -4909,6 +4915,13 @@
 			if ((sim->flags & CAM_SIM_ON_DONEQ) == 0) {
 				spin_lock(&cam_simq_spin);
 				if ((sim->flags & CAM_SIM_ON_DONEQ) == 0) {
+					/*
+					 * Pin the sim for as long as it
+					 * is linked on cam_simq; camisr()
+					 * drops this reference after it
+					 * has finished processing.
+					 */
+					atomic_add_int(&sim->refcount, 1);
 					TAILQ_INSERT_TAIL(&cam_simq, sim,
 							  links);
 					sim->flags |= CAM_SIM_ON_DONEQ;
@@ -7395,15 +7408,44 @@
 	spin_lock(&cam_simq_spin);
 	TAILQ_INIT(&queue);
 	TAILQ_CONCAT(&queue, &cam_simq, links);
+	/*
+	 * Clear ON_DONEQ under cam_simq_spin so the flag exactly tracks
+	 * "linked on cam_simq" (xpt_done() sets it under the same spin).
+	 * The pin taken by xpt_done() stays held until processing ends.
+	 */
+	TAILQ_FOREACH(sim, &queue, links)
+		sim->flags &= ~CAM_SIM_ON_DONEQ;
 	spin_unlock(&cam_simq_spin);
 
 	while ((sim = TAILQ_FIRST(&queue)) != NULL) {
 		TAILQ_REMOVE(&queue, sim, links);
 		CAM_SIM_LOCK(sim);
-		sim->flags &= ~CAM_SIM_ON_DONEQ;
 		camisr_runqueue(sim);
 		CAM_SIM_UNLOCK(sim);
+		/* drop the cam_simq pin taken by xpt_done() */
+		cam_sim_release(sim, 0);
+	}
+}
+
+/*
+ * Perform the final 1->0 reference transition of a cam_sim under
+ * cam_simq_spin, so it cannot interleave with xpt_done()'s pin
+ * acquisition.  Returns 1 on success (caller may free the sim);
+ * returns 0 if the sim is (or just got) linked on cam_simq -- i.e.
+ * camisr holds a pin -- or if the reference changed concurrently.
+ */
+int
+xpt_sim_finalize(struct cam_sim *sim)
+{
+	int rv = 0;
+
+	spin_lock(&cam_simq_spin);
+	if ((sim->flags & CAM_SIM_ON_DONEQ) == 0 &&
+	    atomic_cmpset_int(&sim->refcount, 1, 0)) {
+		rv = 1;
 	}
+	spin_unlock(&cam_simq_spin);
+	return (rv);
 }
 
 static void