DragonFlyBSD Kernel Audit
DF-0974 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/bus/cam/cam_xpt.c b/sys/bus/cam/cam_xpt.c
index 0000000..1111111 100644
--- a/sys/bus/cam/cam_xpt.c
+++ b/sys/bus/cam/cam_xpt.c
@@ -2893,6 +2893,13 @@
 	if ((device->flags & CAM_DEV_UNCONFIGURED) != 0)
 		return (1);
 
+	/*
+	 * cur_entry may have been deregistered (callback NULLed) between
+	 * task enqueue and now; skip dispatch if so.
+	 */
+	if (cur_entry->callback == NULL)
+		return (1);
+
 	xpt_compile_path(&path,
 			 NULL,
 			 device->target->bus->path_id,
@@ -2921,6 +2928,10 @@
 
 	cur_entry = (struct async_node *)arg;
 
+	/* See note in xptsetasyncfunc(). */
+	if (cur_entry->callback == NULL)
+		return (1);
+
 	xpt_compile_path(&path, /*periph*/NULL,
 			 bus->sim->path_id,
 			 CAM_TARGET_WILDCARD,
@@ -3351,11 +3362,29 @@
 			 */
 			added &= ~cur_entry->event_enable;
 			if (csa->event_enable == 0) {
+				/*
+				 * Mark the entry inert but DO NOT free it.
+				 *
+				 * A task pending in xpt_action_sasync_cb() may
+				 * still hold a pointer to this entry (captured
+				 * at task-creation time at line ~3383). Freeing
+				 * here would yield a use-after-free when that
+				 * task runs and dereferences task->data1.
+				 *
+				 * The entry is removed from the list so future
+				 * traversals skip it; the callback pointer is
+				 * NULLed so any in-flight task's dispatch
+				 * becomes a no-op. The memory is intentionally
+				 * leaked (bounded by the number of deregister
+				 * calls) until struct async_node gains a proper
+				 * refcount.
+				 */
+				cur_entry->event_enable = 0;
+				cur_entry->callback = NULL;
 				SLIST_REMOVE(async_head, cur_entry,
 					     async_node, links);
 				atomic_add_int(
 					&csa->ccb_h.path->device->refcount, -1);
-				kfree(cur_entry, M_CAMXPT);
 			} else {
 				cur_entry->event_enable = csa->event_enable;
 			}