⬢ DragonFlyBSD Kernel Audit
DF-5347 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/platform/pc64/x86_64/busdma_machdep.c b/sys/platform/pc64/x86_64/busdma_machdep.c
--- a/sys/platform/pc64/x86_64/busdma_machdep.c
+++ b/sys/platform/pc64/x86_64/busdma_machdep.c
@@ -442,6 +442,15 @@
 	if (map != NULL && map != (void *)-1) {
 		if (STAILQ_FIRST(&map->bpages) != NULL)
 			return (EBUSY);
+		/*
+		 * A map with a pending deferred load (queued on the
+		 * bounce zone waiting list or on the global callback
+		 * list) always has pagesneeded != 0.  Freeing it here
+		 * would leave dangling references on those lists
+		 * (use-after-free).
+		 */
+		if (map->pagesneeded != 0 || map->pagesreserved != 0)
+			return (EBUSY);
 		kfree(map, M_DEVBUF);
 	}
 	dmat->map_count--;
@@ -1023,11 +1032,29 @@
 _bus_dmamap_unload(bus_dma_tag_t dmat, bus_dmamap_t map)
 {
 	struct bounce_page *bpage;
+	struct bounce_zone *bz;
 
 	while ((bpage = STAILQ_FIRST(&map->bpages)) != NULL) {
 		STAILQ_REMOVE_HEAD(&map->bpages, links);
 		free_bounce_page(dmat, bpage);
 	}
+
+	/*
+	 * If the map is queued for a deferred load, dequeue it and
+	 * release its bounce page reservation.  Otherwise the deferred
+	 * load would later run with stale buf/buflen/callback state
+	 * after the driver has already aborted the transaction.
+	 */
+	if (map->pagesneeded != 0 || map->pagesreserved != 0) {
+		bz = dmat->bounce_zone;
+		if (bz != NULL) {
+			BZ_LOCK(bz);
+			STAILQ_REMOVE(&bz->bounce_map_waitinglist, map,
+				      bus_dmamap, links);
+			BZ_UNLOCK(bz);
+		}
+		return_bounce_pages(dmat, map);
+	}
 }
 
 void
@@ -1308,7 +1335,7 @@
 	BZ_UNLOCK(bz);
 
 	if (wait_map != NULL)
-		add_map_callback(map);
+		add_map_callback(wait_map);
 }
 
 static bus_addr_t
@@ -1442,6 +1469,17 @@
 	while ((map = STAILQ_FIRST(&bounce_map_callbacklist)) != NULL) {
 		STAILQ_REMOVE_HEAD(&bounce_map_callbacklist, links);
 		spin_unlock(&bounce_map_list_spin);
+		/*
+		 * Skip maps that were unloaded while queued: their
+		 * pagesneeded/pagesreservation were reset and the
+		 * driver considers the transaction dead.
+		 */
+		if (map->pagesneeded == 0 &&
+		    STAILQ_FIRST(&map->bpages) == NULL) {
+			kprintf("busdma: dropping callback for unloaded map\n");
+			spin_lock(&bounce_map_list_spin);
+			continue;
+		}
 		bus_dmamap_load(map->dmat, map, map->buf, map->buflen,
 				map->callback, map->callback_arg, /*flags*/0);
 		spin_lock(&bounce_map_list_spin);