[PATCH -next 2/2] scsi: iscsi: Fix use-after-free in iscsi_conn_release() for cleanup_work

Ye Bin <[email protected]>
Newsgroups org.kernel.vger.linux-scsi
Message-ID <[email protected]>
From: Ye Bin <[email protected]>

iscsi_conn_error_event() queues conn->cleanup_work without taking a
reference to the connection. If the connection's refcount drops to zero
while the work is still pending or running, iscsi_conn_release() frees
the connection memory without canceling the work, leading to a
use-after-free when iscsi_cleanup_conn_work_fn() later dereferences the
freed conn struct.

This can happen when a session is torn down via iscsi_remove_session(),
which calls iscsi_iter_destroy_conn_fn() to remove and drop the final
reference on each remaining connection. Unlike iscsi_if_destroy_conn()
(which flushes the work) and iscsi_if_stop_conn() (which cancels or
flushes the work), the iscsi_iter_destroy_conn_fn() path does not handle
the pending cleanup_work before dropping the connection reference.

Trigger flow:

  CPU 0 (error path)               CPU 1 (session teardown)
  -----------------------          --------------------------
  iscsi_conn_error_event(conn)
    queue_work(cleanup_work)
    [no conn reference taken]      iscsi_remove_session()
                                     iscsi_iter_destroy_conn_fn()
                                       iscsi_remove_conn(conn)
                                       iscsi_put_conn(conn)
                                         refcount == 0
                                         iscsi_conn_release()
                                           kfree(conn)

  [workqueue picks up work]
  iscsi_cleanup_conn_work_fn()
    container_of(work, ...)
    mutex_lock(&conn->ep_mutex)     *** USE-AFTER-FREE ***

Fix this by calling cancel_work_sync(&conn->cleanup_work) in
iscsi_conn_release() before freeing the connection. This is safe
because:
  - iscsi_conn_release() is always called in process context
  - iscsi_cleanup_conn_work_fn() never drops a conn reference, so
    there is no deadlock risk
  - If the work is running, cancel_work_sync() waits for it to
    complete before kfree(conn), keeping conn valid during execution
  - If the work is pending, it is simply canceled, which is harmless
    since the connection is being destroyed

Fixes: 23d6fefbb3f6 ("scsi: iscsi: Fix in-kernel conn failure handling")
Signed-off-by: Ye Bin <[email protected]>
---
 drivers/scsi/scsi_transport_iscsi.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
index aea319c1e72f..cdc337b0fc26 100644
--- a/drivers/scsi/scsi_transport_iscsi.c
+++ b/drivers/scsi/scsi_transport_iscsi.c
@@ -2128,6 +2128,13 @@ static void iscsi_conn_release(struct device *dev)
 	struct device *parent = conn->dev.parent;
 
 	ISCSI_DBG_TRANS_CONN(conn, "Releasing conn\n");
+	/*
+	 * iscsi_conn_error_event() may have queued conn->cleanup_work without
+	 * holding a reference to the connection. Cancel any pending or
+	 * running work before freeing the connection to prevent a
+	 * use-after-free.
+	 */
+	cancel_work_sync(&conn->cleanup_work);
 	kfree(conn);
 	put_device(parent);
 }
-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.