⬢ DragonFlyBSD Kernel Audit
DF-3771 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/dev/disk/ahci/ahci.c b/sys/dev/disk/ahci/ahci.c
index 0fda0a32..17a7a6e0 100644
--- a/sys/dev/disk/ahci/ahci.c
+++ b/sys/dev/disk/ahci/ahci.c
@@ -972,6 +972,24 @@ ahci_port_state_machine(struct ahci_port *ap, int initial)
 }
 
 
+/*
+ * Fail a single outstanding ccb during port teardown: cancel its timeout
+ * callout and complete it with ATA_S_TIMEOUT.
+ */
+static void
+ahci_port_free_fail_ccb(struct ahci_ccb *ccb)
+{
+	if (ccb->ccb_xa.flags & ATA_F_TIMEOUT_RUNNING) {
+		callout_cancel_sync(&ccb->ccb_timeout);
+		ccb->ccb_xa.flags &= ~ATA_F_TIMEOUT_RUNNING;
+	}
+	ccb->ccb_xa.flags &= ~(ATA_F_TIMEOUT_DESIRED |
+			       ATA_F_TIMEOUT_EXPIRED);
+	ccb->ccb_xa.state = ATA_S_TIMEOUT;
+	ccb->ccb_done(ccb);
+	ccb->ccb_xa.complete(&ccb->ccb_xa);
+}
+
 /*
  * De-initialize and detach a port.
  */
@@ -983,8 +1001,25 @@ ahci_port_free(struct ahci_softc *sc, u_int port)
 	int i;
 
 	/*
-	 * Ensure port is disabled and its interrupts are all flushed.
+	 * Fail all outstanding commands and cancel their timeout callouts
+	 * BEFORE freeing anything (mirrors ahci_port_hardstop() cleanup),
+	 * otherwise a late callout fires into freed memory (use-after-free).
 	 */
+	if (ap->ap_ccbs) {
+		u_int32_t slot;
+
+		while (ap->ap_active) {
+			slot = ffs(ap->ap_active) - 1;
+			ap->ap_active &= ~(1 << slot);
+			--ap->ap_active_cnt;
+			ahci_port_free_fail_ccb(&ap->ap_ccbs[slot]);
+		}
+		while (ap->ap_sactive) {
+			slot = ffs(ap->ap_sactive) - 1;
+			ap->ap_sactive &= ~(1 << slot);
+			ahci_port_free_fail_ccb(&ap->ap_ccbs[slot]);
+		}
+	}
 	if (ap->ap_sc) {
 		ahci_port_stop(ap, 1);
 		ahci_os_stop_port(ap);