The software activity LED support arms a per-EM-port timer (emp->timer, ahci_sw_activity_blink) from ahci_sw_activity() on I/O, and the timer callback re-arms itself while the LED is blinking. The timer lives in struct ahci_em_priv inside the port's ahci_port_priv, which is allocated with devm_kzalloc() and freed when the port is torn down. ahci_port_stop() never deletes this timer - nothing in libahci does - so when the SW activity LED is in use and the AHCI port is stopped (driver unbind / module unload) while the blink timer is still pending, ahci_sw_activity_blink() runs after ahci_port_priv has been freed and dereferences the freed ahci_em_priv (use-after-free). Shut the timers down in ahci_port_stop() before the port private data is released. Iterate every em_priv slot and skip the ones that ahci_init_sw_activity() never set up (emp->timer.function is NULL), so a timer armed on a fan-out link whose port multiplier was detached before port stop is not missed. Because the callback re-arms itself, use timer_shutdown_sync() so a concurrent re-arm cannot resurrect it. Not tested on real hardware (needs an AHCI controller with SGPIO enclosure management and the software activity LED enabled). Fixes: 18f7ba4c2f4b ("libata/ahci: enclosure management support") Cc: stable@vger.kernel.org Signed-off-by: Hyunjung Ko --- Changes in v4: - Iterate all pp->em_priv[] slots instead of the current links, so a blink timer armed on a fan-out link whose PMP was detached before port stop (e.g. by sata_pmp_detach() on failed EH recovery) is still shut down (Sashiko AI review). This drops the now-redundant ATA_FLAG_SW_ACTIVITY gate, since the emp->timer.function check already skips unset-up slots. - Correct the Fixes tag to the commit that introduced emp->timer (Sashiko AI review). Changes in v3: - Shorten the code comment (Niklas Cassel). Changes in v2: - Gate the shutdown loop and skip em_priv slots whose timer was never set up (Sashiko AI review); superseded by the all-slots iteration in v4. v3: https://lore.kernel.org/linux-ide/20261110090303.2415681-1-hj351016@gmail.com/ drivers/ata/libahci.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index 001a6f5..6d73c7f 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -2605,8 +2605,23 @@ static void ahci_port_stop(struct ata_port *ap) { const char *emsg = NULL; struct ahci_host_priv *hpriv = ap->host->private_data; + struct ahci_port_priv *pp = ap->private_data; void __iomem *host_mmio = hpriv->mmio; + struct ahci_em_priv *emp; int rc; + int i; + + /* + * Stop the SW activity blink timers before devres frees pp, skipping + * the slots that ahci_init_sw_activity() never set up. Iterate all + * em_priv slots rather than the current links, so a timer armed on a + * fan-out link whose PMP was detached before port stop is not missed. + */ + for (i = 0; i < EM_MAX_SLOTS; i++) { + emp = &pp->em_priv[i]; + if (emp->timer.function) + timer_shutdown_sync(&emp->timer); + } /* de-initialize port */ rc = ahci_deinit_port(ap, &emsg); -- 2.43.0