[PATCH] scsi: aha152x: fix use-after-free of the bottom-half work

Fan Wu <[email protected]> Mon, 3 Aug 2026 08:03:45 +0000
Newsgroups org.kernel.vger.linux-scsi,org.kernel.vger.linux-kernel,org.kernel.vger.stable
Message-ID <[email protected]>
The interrupt handler schedules a module-global work_struct that neither
aha152x_release() nor the scsi_add_host() failure path cancels before
freeing the host, so a worker armed by a just-returned interrupt can run
after the host is freed.  Because the worker is shared across hosts,
draining it on one host's release cannot stop another host's interrupt
handler from re-arming it.

Make the work_struct per-host, initialize it once at probe, and cancel it
from both free paths.  Each host's interrupt handler then arms only its own
work, so cancel_work_sync() is sufficient to drain it before the host is
freed.

This issue was found by an in-house static analysis tool.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: [email protected]
Assisted-by: Codex:gpt-5.6
Signed-off-by: Fan Wu <[email protected]>
---
 drivers/scsi/aha152x.c | 21 +++++++++++----------
 1 file changed, 11 insertions(+), 10 deletions(-)

diff --git a/drivers/scsi/aha152x.c b/drivers/scsi/aha152x.c
index e3ccb6b..1616ea7 100644
--- a/drivers/scsi/aha152x.c
+++ b/drivers/scsi/aha152x.c
@@ -509,6 +509,9 @@ struct aha152x_hostdata {
 	struct pnp_dev *pnpdev;
 #endif
 	struct list_head host_list;
+
+	struct work_struct work;
+		/* per-host bottom-half worker */
 };
 
 
@@ -635,6 +638,7 @@ static struct {
 
 /* setup & interrupt */
 static irqreturn_t intr(int irq, void *dev_id);
+static void run(struct work_struct *work);
 static void reset_ports(struct Scsi_Host *shpnt);
 static void aha152x_error(struct Scsi_Host *shpnt, char *msg);
 static void done(struct Scsi_Host *shpnt, unsigned char status_byte,
@@ -759,6 +763,7 @@ struct Scsi_Host *aha152x_probe_one(struct aha152x_setup *setup)
 	}
 
 	spin_lock_init(&QLOCK);
+	INIT_WORK(&HOSTDATA(shpnt)->work, run);
 	RECONNECT   = setup->reconnect;
 	SYNCHRONOUS = setup->synchronous;
 	PARITY      = setup->parity;
@@ -856,6 +861,7 @@ struct Scsi_Host *aha152x_probe_one(struct aha152x_setup *setup)
 	return shpnt;
 
 out_host_put:
+	cancel_work_sync(&HOSTDATA(shpnt)->work);
 	list_del(&HOSTDATA(shpnt)->host_list);
 	scsi_host_put(shpnt);
 
@@ -870,6 +876,7 @@ void aha152x_release(struct Scsi_Host *shpnt)
 	scsi_remove_host(shpnt);
 	if (shpnt->irq)
 		free_irq(shpnt->irq, shpnt);
+	cancel_work_sync(&HOSTDATA(shpnt)->work);
 
 #if !defined(AHA152X_PCMCIA)
 	if (shpnt->io_port)
@@ -1314,21 +1321,16 @@ static void done(struct Scsi_Host *shpnt, unsigned char status_byte,
 		printk(KERN_ERR "aha152x: done() called outside of command\n");
 }
 
-static struct work_struct aha152x_tq;
-
 /*
  * Run service completions on the card with interrupts enabled.
  *
  */
 static void run(struct work_struct *work)
 {
-	struct aha152x_hostdata *hd;
+	struct aha152x_hostdata *hd = container_of(work, struct aha152x_hostdata, work);
+	struct Scsi_Host *shost = container_of((void *)hd, struct Scsi_Host, hostdata);
 
-	list_for_each_entry(hd, &aha152x_host_list, host_list) {
-		struct Scsi_Host *shost = container_of((void *)hd, struct Scsi_Host, hostdata);
-
-		is_complete(shost);
-	}
+	is_complete(shost);
 }
 
 /*
@@ -1371,8 +1373,7 @@ static irqreturn_t intr(int irqno, void *dev_id)
 		HOSTDATA(shpnt)->service=1;
 
 		/* Poke the BH handler */
-		INIT_WORK(&aha152x_tq, run);
-		schedule_work(&aha152x_tq);
+		schedule_work(&HOSTDATA(shpnt)->work);
 	}
 	DO_UNLOCK(flags);
 
-- 
2.34.1