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);