drivers/scsi/hpsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
An IOACCEL command that completes with a retryable error is retried from
hard-IRQ context through hpsa_retry_cmd(), which calls INIT_WORK() on the
command and queue_work_on(h->resubmit_wq). Unlike the rescan, monitor and
event delayed workers, which are cancelled and self-guarded by
remove_in_progress before their workqueues are destroyed, resubmit_wq is
armed from the interrupt completion path and has no such guard.
hpsa_remove_one() destroys resubmit_wq, together with rescan_ctlr_wq and
monitor_ctlr_wq, before calling __hpsa_shutdown(), where hpsa_free_irqs()
unregisters the interrupt handler. Between the workqueue destruction and
hpsa_free_irqs() the handler can still fire and call queue_work_on() on
resubmit_wq. queue_work_on() refuses new work on a workqueue being torn
down, dropping the retry with a WARN_ONCE; and once the structure has been
freed through call_rcu(), a later queue_work_on() may dereference freed
memory, a potential use-after-free. The window is reachable because
scsi_remove_host(), which runs between the two points, can itself generate
I/O (SYNCHRONIZE CACHE, per the existing comment).
Destroy resubmit_wq after __hpsa_shutdown() instead. This restores the
ordering from before commit 6636e7f455b3 ("hpsa: Use local workqueues
instead of system workqueues"), which moved the
destroy_workqueue(h->resubmit_wq) call in hpsa_remove_one() ahead of the
controller shutdown; resubmit_wq was originally torn down after
hpsa_shutdown(), as introduced by commit 080ef1cc7fdf ("hpsa: use
workqueue to resubmit failed ioaccel commands").
Keeping resubmit_wq alive until after __hpsa_shutdown() is safe.
Each retry stays associated with its SCSI request until the command is
completed with scsi_done(); hpsa_remove_one() runs scsi_remove_host()
first, which tears down each request queue and waits for outstanding
requests, so it cannot return while retry work (including work that a
later IOACCEL completion requeues) remains outstanding. __hpsa_shutdown()
then masks and frees the interrupts, and free_irq() waits for any handler
still running on another CPU, so by the time destroy_workqueue() runs on
resubmit_wq the completion path can no longer queue new work.
This issue was found by an in-house static analysis tool.
Fixes: 6636e7f455b3 ("hpsa: Use local workqueues instead of system workqueues")
Cc: stable@vger.kernel.org
Assisted-by: Codex:gpt-5.6
Signed-off-by: Fan Wu <fanwu01@zju.edu.cn>
---
drivers/scsi/hpsa.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/scsi/hpsa.c b/drivers/scsi/hpsa.c
index 3654b12c5d5a..8e039011aeaf 100644
--- a/drivers/scsi/hpsa.c
+++ b/drivers/scsi/hpsa.c
@@ -9057,7 +9057,6 @@ static void hpsa_remove_one(struct pci_dev *pdev)
cancel_delayed_work_sync(&h->rescan_ctlr_work);
cancel_delayed_work_sync(&h->event_monitor_work);
destroy_workqueue(h->rescan_ctlr_wq);
- destroy_workqueue(h->resubmit_wq);
destroy_workqueue(h->monitor_ctlr_wq);
hpsa_delete_sas_host(h);
@@ -9073,6 +9072,7 @@ static void hpsa_remove_one(struct pci_dev *pdev)
/* includes hpsa_free_irqs - init_one 4 */
/* includes hpsa_disable_interrupt_mode - pci_init 2 */
__hpsa_shutdown(pdev);
+ destroy_workqueue(h->resubmit_wq);
hpsa_free_device_info(h); /* scan */
--
2.34.1
© 2016 - 2026 Red Hat, Inc.