⬢ DragonFlyBSD Kernel Audit
DF-5083 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/disk/nata/ata-dma.c	2026-09-26 23:19:21.437252143 +0000
+++ b/sys/dev/disk/nata/ata-dma.c	2026-09-26 23:19:44.228957985 +0000
@@ -106,11 +106,40 @@
 			   0, &ch->dma->sg_tag))
 	goto error;
 
-    if (bus_dma_tag_create(ch->dma->dmatag,ch->dma->alignment,ch->dma->boundary,
+    /*
+     * DF-5083: DragonFly bus_dmamap_load() strips BUS_DMA_NOWAIT when a
+     * real map is supplied (busdma_machdep.c, "follow old semantics") and
+     * defers the transfer when the tag's bounce-page pool is short,
+     * returning EINPROGRESS with map->callback / map->callback_arg
+     * (ata_dmaload()'s STACK-allocated cba) / map->buf (the request
+     * buffer) kept armed.  When bounce pages later free up, busdma_swi()
+     * re-runs the load and invokes the callback against ata_dmaload()'s
+     * dead stack frame and a buffer whose request has already completed:
+     * ata_dmasetprd() then reads args->dmatab from stale stack memory and
+     * writes PRD entries wherever that word points (kernel memory
+     * corruption), and re-walks the freed/reused buffer KVA.
+     *
+     * nata cannot handle deferral, so make it impossible by construction:
+     *  - BUS_DMA_PRIVBZONE: a private bounce zone that no other driver
+     *    can drain (a channel has exactly one data map and requests are
+     *    serialized by the channel state machine, so the single map can
+     *    never compete with itself); and
+     *  - one extra page of tag maxsize, so the lazily allocated pool
+     *    (atop(round_page(maxsize)) pages at bus_dmamap_create) covers a
+     *    worst-case page-straddling transfer of max_iosize bytes
+     *    (atop(max_iosize) + 1 pages).
+     *
+     * NOTE: chipset drivers that raise ch->dma->max_iosize after
+     * ata_dmainit() (ata-ahci.c, ata-marvell.c, ata-promise.c,
+     * ata-siliconimage.c) must size their own pool accordingly; that is
+     * addressed separately in those files.
+     */
+    if (bus_dma_tag_create(ch->dma->dmatag, ch->dma->alignment,
+			   ch->dma->boundary,
 			   ch->dma->max_address, BUS_SPACE_MAXADDR,
-			   ch->dma->max_iosize,
+			   ch->dma->max_iosize + PAGE_SIZE,
 			   ATA_DMA_ENTRIES, ch->dma->segsize,
-			   0, &ch->dma->data_tag))
+			   BUS_DMA_PRIVBZONE, &ch->dma->data_tag))
 	goto error;
 
     if (bus_dmamem_alloc(ch->dma->sg_tag, (void **)&ch->dma->sg, 0,
@@ -251,7 +280,18 @@
     if ((error = bus_dmamap_load(ch->dma->data_tag, ch->dma->data_map,
 				 data, count, ch->dma->setprd, &cba,
 				 BUS_DMA_NOWAIT)) || (error = cba.error))
+	{
+	/*
+	 * EINPROGRESS must be impossible with the private-zone data tag
+	 * (see ata_dmaalloc()).  It would leave the deferred callback
+	 * armed against this function's dead stack frame -- fail hard
+	 * rather than corrupt kernel memory later.
+	 */
+	if (error == EINPROGRESS)
+	    panic("ata_dmaload: deferred DMA load (DF-5083) "
+		  "-- private bounce pool too small");
 	return error;
+    }
 
     *entries = cba.nsegs;