* [PATCH 1/3] scsi: target: iscsi: factor out iscsit_stop_kthreads()
2026-11-15 8:57 [PATCH 0/3] scsi: target: iscsi: fix session use-after-free on non-leading login Daniel Wade
@ 2026-11-15 8:57 ` Daniel Wade
2026-11-15 8:57 ` [PATCH 2/3] scsi: target: iscsi: don't attach a connection to a closing session Daniel Wade
2026-11-15 8:57 ` [PATCH 3/3] scsi: target: iscsi: don't re-arm Time2Retain on " Daniel Wade
2 siblings, 0 replies; 4+ messages in thread
From: Daniel Wade @ 2026-11-15 8:57 UTC (permalink / raw)
To: Martin K. Petersen
Cc: Maurizio Lombardi, Krzysztof Kozlowski, Sujal Tuladhar,
linux-scsi, target-devel, linux-kernel
iscsi_target_do_tx_login_io() stops a connection's RX and TX kthreads
and releases its thread-set bitmap slot inline on its error path. The
next patch needs that same teardown from iscsi_post_login_handler(), so
move it to a helper beside iscsit_start_kthreads().
No functional change.
Cc: stable@vger.kernel.org
Signed-off-by: Daniel Wade <danjwade95@gmail.com>
---
drivers/target/iscsi/iscsi_target_login.c | 17 +++++++++++++++++
drivers/target/iscsi/iscsi_target_login.h | 1 +
drivers/target/iscsi/iscsi_target_nego.c | 17 ++---------------
3 files changed, 20 insertions(+), 15 deletions(-)
diff --git a/drivers/target/iscsi/iscsi_target_login.c b/drivers/target/iscsi/iscsi_target_login.c
index c282b6a7029..f4df5518171 100644
--- a/drivers/target/iscsi/iscsi_target_login.c
+++ b/drivers/target/iscsi/iscsi_target_login.c
@@ -643,6 +643,23 @@ int iscsit_start_kthreads(struct iscsit_conn *conn)
return ret;
}
+void iscsit_stop_kthreads(struct iscsit_conn *conn)
+{
+ if (conn->rx_thread && conn->rx_thread_active) {
+ send_sig(SIGINT, conn->rx_thread, 1);
+ complete(&conn->rx_login_comp);
+ kthread_stop(conn->rx_thread);
+ }
+ if (conn->tx_thread && conn->tx_thread_active) {
+ send_sig(SIGINT, conn->tx_thread, 1);
+ kthread_stop(conn->tx_thread);
+ }
+ spin_lock(&iscsit_global->ts_bitmap_lock);
+ bitmap_release_region(iscsit_global->ts_bitmap, conn->bitmap_id,
+ get_order(1));
+ spin_unlock(&iscsit_global->ts_bitmap_lock);
+}
+
void iscsi_post_login_handler(
struct iscsi_np *np,
struct iscsit_conn *conn,
diff --git a/drivers/target/iscsi/iscsi_target_login.h b/drivers/target/iscsi/iscsi_target_login.h
index 03c7d695d58..02c5f6ecbec 100644
--- a/drivers/target/iscsi/iscsi_target_login.h
+++ b/drivers/target/iscsi/iscsi_target_login.h
@@ -20,6 +20,7 @@ extern int iscsit_get_login_rx(struct iscsit_conn *, struct iscsi_login *);
extern int iscsit_put_login_tx(struct iscsit_conn *, struct iscsi_login *, u32);
extern void iscsit_free_conn(struct iscsit_conn *);
extern int iscsit_start_kthreads(struct iscsit_conn *);
+extern void iscsit_stop_kthreads(struct iscsit_conn *);
extern void iscsi_post_login_handler(struct iscsi_np *, struct iscsit_conn *, u8);
extern void iscsi_target_login_sess_out(struct iscsit_conn *, bool, bool);
extern int iscsi_target_login_thread(void *);
diff --git a/drivers/target/iscsi/iscsi_target_nego.c b/drivers/target/iscsi/iscsi_target_nego.c
index b03ed154ca3..d56b28446d3 100644
--- a/drivers/target/iscsi/iscsi_target_nego.c
+++ b/drivers/target/iscsi/iscsi_target_nego.c
@@ -362,21 +362,8 @@ static int iscsi_target_do_tx_login_io(struct iscsit_conn *conn, struct iscsi_lo
return 0;
err:
- if (login->login_complete) {
- if (conn->rx_thread && conn->rx_thread_active) {
- send_sig(SIGINT, conn->rx_thread, 1);
- complete(&conn->rx_login_comp);
- kthread_stop(conn->rx_thread);
- }
- if (conn->tx_thread && conn->tx_thread_active) {
- send_sig(SIGINT, conn->tx_thread, 1);
- kthread_stop(conn->tx_thread);
- }
- spin_lock(&iscsit_global->ts_bitmap_lock);
- bitmap_release_region(iscsit_global->ts_bitmap, conn->bitmap_id,
- get_order(1));
- spin_unlock(&iscsit_global->ts_bitmap_lock);
- }
+ if (login->login_complete)
+ iscsit_stop_kthreads(conn);
return -1;
}
--
2.39.0.windows.2
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 2/3] scsi: target: iscsi: don't attach a connection to a closing session
2026-11-15 8:57 [PATCH 0/3] scsi: target: iscsi: fix session use-after-free on non-leading login Daniel Wade
2026-11-15 8:57 ` [PATCH 1/3] scsi: target: iscsi: factor out iscsit_stop_kthreads() Daniel Wade
@ 2026-11-15 8:57 ` Daniel Wade
2026-11-15 8:57 ` [PATCH 3/3] scsi: target: iscsi: don't re-arm Time2Retain on " Daniel Wade
2 siblings, 0 replies; 4+ messages in thread
From: Daniel Wade @ 2026-11-15 8:57 UTC (permalink / raw)
To: Martin K. Petersen
Cc: Maurizio Lombardi, Krzysztof Kozlowski, Sujal Tuladhar,
linux-scsi, target-devel, linux-kernel
iscsi_login_non_zero_tsih_s2() finds a session on tpg_sess_list, skips it
if it is already marked for teardown, and takes a usage count it holds
across the whole login negotiation. That skip is sampled once, before
the negotiation, and never re-evaluated.
If the session's last connection goes away while the login is still
negotiating, iscsit_close_connection() sees ->nconn reach zero, commits
to the free under ->conn_lock and calls iscsit_close_session(), which
sleeps in iscsit_check_session_usage_count() on the reference the login
holds. iscsi_post_login_handler() then attaches the new connection,
arms its NOP-In timer and drops that reference, which is what releases the
free. conn->sess is left dangling and the timer walks the freed session:
BUG: KASAN: slab-use-after-free in iscsit_allocate_cmd+0x6de/0x780
iscsit_handle_nopin_timeout <- call_timer_fn <- run_timer_softirq
Freed by task ...: kfree <- iscsit_close_session
<- iscsit_close_connection
<- iscsit_take_action_for_connection_exit
MaxConnections does not prevent this.
iscsi_login_post_auth_non_zero_tsih() tests
(atomic_read(&sess->nconn) + 1) > MaxConnections after the reference is
taken, and ->nconn is 0 here, so (0 + 1) > 1 is false even at the
default of 1.
iscsit_close_connection() tests ->nconn under sess->conn_lock and
iscsi_post_login_handler() attaches under the same lock, so publishing the
close decision in that critical section and re-testing it in the one that
attaches makes the two mutually exclusive. ->session_close is already
that flag: only ever set, never cleared, and the logout and explicit close
branches already require it. Set it in the ERL=0 fall-back branch too and
refuse the login when it is set, dropping only the reference the login
took so iscsit_close_session() can finish the free it committed to.
The refused connection is torn down here because its kthreads were started
before the final login response went out.
Tested on mainline with KASAN: unpatched, the reproducer produces the
report above and then a general protection fault; with the series applied
the login is refused and it no longer triggers.
Fixes: e48354ce078c ("iscsi-target: Add iSCSI fabric support for target v4.1")
Cc: stable@vger.kernel.org
Signed-off-by: Daniel Wade <danjwade95@gmail.com>
---
drivers/target/iscsi/iscsi_target.c | 6 ++++
drivers/target/iscsi/iscsi_target_login.c | 41 +++++++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c
index 124ff269b8e..ee7df7f2723 100644
--- a/drivers/target/iscsi/iscsi_target.c
+++ b/drivers/target/iscsi/iscsi_target.c
@@ -4431,6 +4431,12 @@ int iscsit_close_connection(
*/
if (!atomic_read(&sess->session_reinstatement) &&
atomic_read(&sess->session_fall_back_to_erl0)) {
+ /*
+ * Publish the close decision under ->conn_lock so a non-leading
+ * login attaching in iscsi_post_login_handler() sees it. The
+ * branches below already require ->session_close.
+ */
+ atomic_set(&sess->session_close, 1);
spin_unlock_bh(&sess->conn_lock);
complete_all(&sess->session_wait_comp);
iscsit_close_session(sess, true);
diff --git a/drivers/target/iscsi/iscsi_target_login.c b/drivers/target/iscsi/iscsi_target_login.c
index f4df5518171..de0cd95aff0 100644
--- a/drivers/target/iscsi/iscsi_target_login.c
+++ b/drivers/target/iscsi/iscsi_target_login.c
@@ -660,6 +660,33 @@ void iscsit_stop_kthreads(struct iscsit_conn *conn)
spin_unlock(&iscsit_global->ts_bitmap_lock);
}
+/*
+ * Drop a connection whose kthreads are already running but that must not enter
+ * full feature phase. conn->sess must not be touched once this returns.
+ */
+static void iscsi_post_login_fail_conn(struct iscsit_conn *conn)
+{
+ /*
+ * iscsi_target_tx_thread() only leaves cleanup to this context while
+ * conn_state is TARG_CONN_STATE_IN_LOGIN; otherwise it would reach
+ * iscsit_close_connection() and walk the session being refused.
+ */
+ conn->conn_state = TARG_CONN_STATE_IN_LOGIN;
+ iscsit_stop_kthreads(conn);
+ iscsit_dec_conn_usage_count(conn);
+
+ if (conn->sock) {
+ sock_release(conn->sock);
+ conn->sock = NULL;
+ }
+ if (conn->conn_transport->iscsit_wait_conn)
+ conn->conn_transport->iscsit_wait_conn(conn);
+ if (conn->conn_transport->iscsit_free_conn)
+ conn->conn_transport->iscsit_free_conn(conn);
+
+ iscsit_free_conn(conn);
+}
+
void iscsi_post_login_handler(
struct iscsi_np *np,
struct iscsit_conn *conn,
@@ -690,6 +717,20 @@ void iscsi_post_login_handler(
conn->param_list = NULL;
spin_lock_bh(&sess->conn_lock);
+ /*
+ * The skip filter in iscsi_login_non_zero_tsih_s2() sampled this
+ * before the negotiation. Re-test under the lock that serialises
+ * against the ->nconn test in iscsit_close_connection().
+ */
+ if (atomic_read(&sess->session_close)) {
+ spin_unlock_bh(&sess->conn_lock);
+ pr_err("iSCSI session is closing, failing login on CID: %u\n",
+ conn->cid);
+ iscsi_post_login_fail_conn(conn);
+ /* Last access to @sess: this lets close_session() free it. */
+ iscsit_dec_session_usage_count(sess);
+ return;
+ }
atomic_set(&sess->session_continuation, 0);
if (sess->session_state == TARG_SESS_STATE_FAILED) {
pr_debug("Moving to"
--
2.39.0.windows.2
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 3/3] scsi: target: iscsi: don't re-arm Time2Retain on a closing session
2026-11-15 8:57 [PATCH 0/3] scsi: target: iscsi: fix session use-after-free on non-leading login Daniel Wade
2026-11-15 8:57 ` [PATCH 1/3] scsi: target: iscsi: factor out iscsit_stop_kthreads() Daniel Wade
2026-11-15 8:57 ` [PATCH 2/3] scsi: target: iscsi: don't attach a connection to a closing session Daniel Wade
@ 2026-11-15 8:57 ` Daniel Wade
2 siblings, 0 replies; 4+ messages in thread
From: Daniel Wade @ 2026-11-15 8:57 UTC (permalink / raw)
To: Martin K. Petersen
Cc: Maurizio Lombardi, Krzysztof Kozlowski, Sujal Tuladhar,
linux-scsi, target-devel, linux-kernel
iscsi_target_login_sess_out() re-arms the Time2Retain timer of the
session whose login just failed. In the window closed by the previous
patch that session may already be on its way out: iscsit_close_session()
stops that timer, then sleeps in iscsit_check_session_usage_count() on
the reference this path is about to drop, and frees the session as soon
as it is dropped. A re-arm therefore lands between the stop and the
free, and leaves an armed timer on freed memory.
->session_close is set before the free is committed to, so test it here
too and skip the re-arm.
Fixes: e48354ce078c ("iscsi-target: Add iSCSI fabric support for target v4.1")
Cc: stable@vger.kernel.org
Signed-off-by: Daniel Wade <danjwade95@gmail.com>
---
drivers/target/iscsi/iscsi_target_login.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/target/iscsi/iscsi_target_login.c b/drivers/target/iscsi/iscsi_target_login.c
index de0cd95aff0..1f41e5274f2 100644
--- a/drivers/target/iscsi/iscsi_target_login.c
+++ b/drivers/target/iscsi/iscsi_target_login.c
@@ -1168,7 +1168,13 @@ void iscsi_target_login_sess_out(struct iscsit_conn *conn,
*/
if (!zero_tsih && conn->sess) {
spin_lock_bh(&conn->sess->conn_lock);
- if (conn->sess->session_state == TARG_SESS_STATE_FAILED) {
+ /*
+ * iscsit_close_session() stopped this timer before sleeping on
+ * the usage count this path drops, so re-arming it would leave
+ * the timer armed on freed memory.
+ */
+ if (conn->sess->session_state == TARG_SESS_STATE_FAILED &&
+ !atomic_read(&conn->sess->session_close)) {
struct se_portal_group *se_tpg =
&conn->tpg->tpg_se_tpg;
--
2.39.0.windows.2
^ permalink raw reply [flat|nested] 4+ messages in thread