Linux SCSI subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Yehyeong Lee" <yhlee@isslab.korea.ac.kr>
Cc: target-devel@vger.kernel.org, linux-scsi@vger.kernel.org
Subject: Re: [PATCH] scsi: target: iscsi: run the Time2Retain handler in process context
Date: Sun, 15 Nov 2026 12:42:38 +0000	[thread overview]
Message-ID: <sashiko-outbox-215504@kernel.org> (raw)
In-Reply-To: <20261115121943.1446744-1-yhlee@isslab.korea.ac.kr>

Thank you for your contribution! Sashiko AI review found 4 potential
issues to consider:
- [High] scsi: target: iscsi: UAF race in
  iscsit_stop_time2retain_timer() on restart
- [High] scsi: target: iscsi: session leak in iscsit_close_session()
  when !can_sleep
- [High] scsi: target: iscsi: UAF in iscsit_close_connection() when
  Time2Retain is 0
- [Medium] scsi: target: iscsi: session_lock dropped in
  iscsit_stop_time2retain_timer()
--

commit cd2ff8f1169a8e47b09d7eb1b677944735610f1c
Author: Yehyeong Lee <yhlee@isslab.korea.ac.kr>

scsi: target: iscsi: run the Time2Retain handler in process context

This commit converts the session Time2Retain timer into a delayed work item on
system_dfl_wq so that iscsit_handle_time2retain_timeout() closes the session
and acquires se_tpg->session_lock in process context rather than timer softirq
context.

> diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c
> index 03d618cd76721..4c6579eb803f3 100644
> --- a/drivers/target/iscsi/iscsi_target.c
> +++ b/drivers/target/iscsi/iscsi_target.c
> @@ -4485,8 +4485,8 @@ int iscsit_close_session(struct iscsit_session *sess, bool can_sleep)
>  
>  	/*
>  	 * If any other processes are accessing this session pointer we must
> -	 * wait until they have completed.  If we are in an interrupt (the
> -	 * time2retain handler) and contain and active session usage count we
> +	 * wait until they have completed.  If we are the Time2Retain work
> +	 * (can_sleep is false) and contain and active session usage count we
>  	 * restart the timer and exit.
>  	 */
>  	if (iscsit_check_session_usage_count(sess, can_sleep)) {

[Severity: High]
Can keeping can_sleep as false when iscsit_handle_time2retain_timeout() calls
iscsit_close_session() leak the session if session_usage_count is non-zero?

In iscsi_target_login_sess_out(), the old_sess_out path starts the Time2Retain
handler and drops se_tpg->session_lock before calling
iscsit_dec_session_usage_count().  When DefaultTime2Retain is 0,
mod_delayed_work() queues time2retain_work with zero delay, so
iscsit_handle_time2retain_timeout() can run on another CPU while
session_usage_count is still elevated.

iscsit_handle_time2retain_timeout() sets ISCSI_TF_EXPIRED (with
ISCSI_TF_RUNNING still set) and calls iscsit_close_session(sess, false).
iscsit_close_session() sets session_reinstatement to 1, fails to stop the
timer because ISCSI_TF_EXPIRED is set, deregisters configfs, and enters the
!can_sleep branch above because session_usage_count is non-zero.  There,
iscsit_start_time2retain_handler() returns immediately without re-arming the
work because ISCSI_TF_RUNNING was never cleared, leaving the half-closed
session leaked with ISCSI_TF_EXPIRED and session_reinstatement set.

Since iscsit_handle_time2retain_timeout() now runs in process context, could
it wait on session_waiting_on_uc_comp instead of taking the non-sleeping
restart path?

[ ... ]

> diff --git a/drivers/target/iscsi/iscsi_target_erl0.c b/drivers/target/iscsi/iscsi_target_erl0.c
> index 24db6b07493e6..9db833f3eeaec 100644
> --- a/drivers/target/iscsi/iscsi_target_erl0.c
> +++ b/drivers/target/iscsi/iscsi_target_erl0.c

[ ... ]

> @@ -791,8 +791,8 @@ void iscsit_start_time2retain_handler(struct iscsit_session *sess)
>  
>  	sess->time2retain_timer_flags &= ~ISCSI_TF_STOP;
>  	sess->time2retain_timer_flags |= ISCSI_TF_RUNNING;
> -	mod_timer(&sess->time2retain_timer,
> -		  jiffies + sess->sess_ops->DefaultTime2Retain * HZ);
> +	mod_delayed_work(system_dfl_wq, &sess->time2retain_work,
> +			 sess->sess_ops->DefaultTime2Retain * HZ);
>  }
>  
>  int iscsit_stop_time2retain_timer(struct iscsit_session *sess)

[Severity: High]
Can this cause a use-after-free in iscsit_close_connection() when
DefaultTime2Retain is 0?

drivers/target/iscsi/iscsi_target.c:iscsit_close_connection() {
    ...
		if (!atomic_read(&sess->session_continuation))
			iscsit_start_time2retain_handler(sess);

		if (atomic_read(&sess->session_close)) {
			spin_unlock_bh(&sess->conn_lock);
			complete_all(&sess->session_wait_comp);
			iscsit_close_session(sess, true);
		} else {
			spin_unlock_bh(&sess->conn_lock);
		}
    ...
}

Previously, mod_timer() armed a softirq timer while spin_lock_bh() kept local
softirqs disabled across iscsit_start_time2retain_handler() and the subsequent
sess accesses.  With mod_delayed_work(), a zero delay bypasses the timer wheel
and queues time2retain_work immediately on an unbound worker, and neither
iscsit_handle_time2retain_timeout() nor iscsit_close_session() takes
sess->conn_lock:

CPU0                                  CPU1 (kworker)
iscsit_close_connection()
  spin_lock_bh(&sess->conn_lock)
  iscsit_start_time2retain_handler(sess)
    mod_delayed_work(..., 0)
                                      iscsit_handle_time2retain_timeout()
                                        iscsit_close_session(sess, false)
                                          kfree(sess)
  atomic_read(&sess->session_close)
  spin_unlock_bh(&sess->conn_lock)  <-- use-after-free

> @@ -811,7 +811,7 @@ int iscsit_stop_time2retain_timer(struct iscsit_session *sess)
>  	sess->time2retain_timer_flags |= ISCSI_TF_STOP;
>  	spin_unlock(&se_tpg->session_lock);
>  
> -	timer_delete_sync(&sess->time2retain_timer);
> +	cancel_delayed_work(&sess->time2retain_work);
>  
>  	spin_lock(&se_tpg->session_lock);
>  	sess->time2retain_timer_flags &= ~ISCSI_TF_RUNNING;

[Severity: High]
Can replacing timer_delete_sync() with cancel_delayed_work() here allow an
already-dispatched work item to close and free the session after the timer is
stopped and restarted?

Because cancel_delayed_work() does not wait for a running work item,
iscsit_stop_time2retain_timer() clears ISCSI_TF_RUNNING while a worker thread
can still be preempted before taking se_tpg->session_lock.  If the timer is
then restarted (for example via the old_sess_out path in
iscsi_target_login_sess_out() when a continuation login fails),
iscsit_start_time2retain_handler() clears ISCSI_TF_STOP and re-queues
sess->time2retain_work:

CPU0 (kworker)                        CPU1
iscsit_handle_time2retain_timeout()
  /* preempted before session_lock */
                                      iscsi_login_non_zero_tsih_s2()
                                        iscsit_stop_time2retain_timer()
                                          flags |= ISCSI_TF_STOP
                                          cancel_delayed_work()
                                          flags &= ~ISCSI_TF_RUNNING
                                      ...
                                      iscsi_target_login_sess_out()
                                        iscsit_start_time2retain_handler()
                                          flags &= ~ISCSI_TF_STOP
                                          flags |= ISCSI_TF_RUNNING
                                          mod_delayed_work()
  spin_lock_bh(&se_tpg->session_lock)
  /* ISCSI_TF_STOP is now clear */
  flags |= ISCSI_TF_EXPIRED
  spin_unlock_bh(&se_tpg->session_lock)
  iscsit_close_session(sess, false)
    iscsit_stop_time2retain_timer()   <-- returns -1 (ISCSI_TF_EXPIRED)
    /* cancel_delayed_work_sync() skipped because !can_sleep */
    kfree(sess)                       <-- time2retain_work still queued

When CPU0 acquires se_tpg->session_lock, ISCSI_TF_STOP is no longer set, so
the stale work sets ISCSI_TF_EXPIRED and calls iscsit_close_session(sess,
false).  Because ISCSI_TF_EXPIRED is set and can_sleep is false, neither
iscsit_stop_time2retain_timer() nor cancel_delayed_work_sync() cancels the
newly armed time2retain_work before kfree(sess) frees the session (or if a
new connection is already active on the session, iscsit_close_session() hits
BUG() on sess->nconn).

[Severity: Medium]
Why does iscsit_stop_time2retain_timer() still drop and re-acquire
se_tpg->session_lock around cancel_delayed_work()?

The commit message notes that cancel_delayed_work() is called with
se_tpg->session_lock held because it does not sleep, but the unlock and lock
around it remain from timer_delete_sync().  Dropping se_tpg->session_lock
splits the update of time2retain_timer_flags across two critical sections, so
an unlocked caller of iscsit_start_time2retain_handler() such as
iscsit_close_connection() can observe ISCSI_TF_RUNNING still set in that
window and return without arming the work just before ISCSI_TF_RUNNING is
cleared.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261115121943.1446744-1-yhlee@isslab.korea.ac.kr?part=1

      reply	other threads:[~2026-11-15 12:42 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-15 12:19 [PATCH] scsi: target: iscsi: run the Time2Retain handler in process context Yehyeong Lee
2026-11-15 12:42 ` sashiko-bot [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-215504@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-scsi@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=target-devel@vger.kernel.org \
    --cc=yhlee@isslab.korea.ac.kr \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox