Linux SCSI subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Yehyeong Lee" <yhlee@isslab.korea.ac.kr>
Cc: linux-scsi@vger.kernel.org
Subject: Re: [PATCH v3 3/3] scsi: libiscsi: take a transmit reference before a task is published
Date: Sun, 15 Nov 2026 13:00:15 +0000	[thread overview]
Message-ID: <sashiko-outbox-215510@kernel.org> (raw)
In-Reply-To: <20261115122122.1446791-4-yhlee@isslab.korea.ac.kr>

Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [High] scsi: libiscsi: UAF on out.segment when iscsi_xmit_task() drops
  completed task
- [High] scsi: libiscsi: incomplete state and ordering check in
  iscsi_itt_to_ctask()
- [High] scsi: libiscsi: completed requeued task sent in
  iscsi_xmit_task()
--

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

scsi: libiscsi: take a transmit reference before a task is published

This commit takes a transmit reference on an iSCSI task before setting its
state to ISCSI_TASK_RUNNING, checks task->state in iscsi_itt_to_ctask(),
and avoids resending or leaking tasks completed by the receive path during
transmission.

> diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
> index 220e89d541af6..5045f6f5ca01b 100644
> --- a/drivers/scsi/libiscsi.c
> +++ b/drivers/scsi/libiscsi.c

[ ... ]

> @@ -1455,6 +1464,10 @@ struct iscsi_task *iscsi_itt_to_ctask(struct iscsi_session *session,
>  	if (!task || !task->sc)
>  		return NULL;
>  
> +	/* Pairs with the release in the transmit path. */
> +	if (smp_load_acquire(&task->state) == ISCSI_TASK_PENDING)
> +		return NULL;
> +
>  	if (iscsi_cmd(task->sc)->age != session->age) {
>  		iscsi_session_printk(KERN_ERR, session,
>  				  "task's session age %d, expected %d\n",

[Severity: High]
Can iscsi_itt_to_ctask() still return an unsent or already-completed task
here?

First, iscsi_alloc_task() writes task->state = ISCSI_TASK_PENDING and
task->sc = sc under frwd_lock with plain stores and no barrier between
them, while iscsi_itt_to_ctask() reads task->sc before
smp_load_acquire(&task->state):

CPU0 (frwd_lock)                  CPU1 (back_lock)
iscsi_alloc_task()                iscsi_itt_to_ctask()
  task->state = ISCSI_TASK_PENDING
  task->sc = sc
                                    if (!task || !task->sc)
                                    smp_load_acquire(&task->state)
                                      --> sees ISCSI_TASK_FREE

Because smp_load_acquire() only orders loads after it and
iscsi_alloc_task() has no release or write barrier before setting task->sc,
CPU1 can observe the new task->sc while still reading the prior
ISCSI_TASK_FREE state, bypassing the ISCSI_TASK_PENDING check and freeing
the task while it stays on conn->cmdqueue.

Second, while the transmit path holds a second reference on task (for
example across session->tt->xmit_task() or in conn->task), completing the
task on the receive path sets task->state to ISCSI_TASK_COMPLETED and
decrements refcount from 2 to 1 without clearing task->sc.  Because
iscsi_itt_to_ctask() only rejects ISCSI_TASK_PENDING, a subsequent
ISCSI_OP_SCSI_DATA_IN PDU for that ITT still resolves the completed task:

drivers/scsi/libiscsi_tcp.c:iscsi_tcp_hdr_dissect() {
	...
	case ISCSI_OP_SCSI_DATA_IN:
		spin_lock(&session->back_lock);
		task = iscsi_itt_to_ctask(session, conn, hdr->itt);
		if (!task)
			rc = ISCSI_ERR_BAD_ITT;
		else
			rc = iscsi_tcp_data_in(conn, task);
		spin_unlock(&session->back_lock);
	...
}

iscsi_tcp_data_in() points tcp_conn->in.segment at task->sc->sdb.table.sgl
and drops back_lock without a task reference; when the transmit path then
puts its final reference, iscsi_free_task() calls scsi_done() and frees the
scatterlist while iscsi_tcp_recv_skb() writes payload bytes into it
(use-after-free).  A duplicate ISCSI_OP_SCSI_CMD_RSP in that window will
also overwrite sc->result and sense data before scsi_done() runs.

> @@ -1556,9 +1569,20 @@ static int iscsi_xmit_task(struct iscsi_session *session, struct iscsi_conn *con
>  			WARN_ON_ONCE(1);
>  			return 0;
>  		}
> +		if (!was_requeue)
> +			/* Pairs with the acquire in the RX task lookups. */
> +			smp_store_release(&task->state, ISCSI_TASK_RUNNING);
>  	} else {
>  		/* Already have a ref from when we failed to send it last call */
>  		conn->task = NULL;
> +		/* The RX path may have completed it while the tx was stopped. */
> +		spin_lock_bh(&session->back_lock);
> +		if (task->state != ISCSI_TASK_RUNNING) {
> +			__iscsi_put_task(session, task);
> +			spin_unlock_bh(&session->back_lock);
> +			return 0;
> +		}
> +		spin_unlock_bh(&session->back_lock);
>  	}

[Severity: High]
When conn->task is NULL and was_requeue is true, can iscsi_xmit_task()
still pass a completed task to session->tt->xmit_task()?

When iscsi_tcp_r2t_rsp() queues a task onto conn->requeue, it takes an
extra reference on task.  If an early response completes the task before
iscsi_data_xmit() pops it from conn->requeue, task->state becomes
ISCSI_TASK_COMPLETED while the R2T reference keeps the task alive:

CPU0 (RX, back_lock)              CPU1 (xmit worker, frwd_lock)
iscsi_tcp_r2t_rsp()
  iscsi_get_task(task)
  iscsi_requeue_task(task)
iscsi_complete_task()
  task->state = ISCSI_TASK_COMPLETED
  __iscsi_put_task() (refcount 2->1)
                                  iscsi_data_xmit()
                                    iscsi_xmit_task(..., was_requeue=true)
                                      if (!conn->task)
                                        iscsi_get_task(task)
                                      if (was_requeue)
                                        iscsi_put_task(task)
                                      session->tt->xmit_task(task)

Because the !conn->task branch does not check task->state under back_lock
when was_requeue is true, it calls session->tt->xmit_task() on the
completed task.  If xmit_task() then returns -EAGAIN after
iscsi_sw_tcp_pdu_init() points tcp_sw_conn->out.segment at task->hdr and
task->sc->sdb.table.sgl, the post-xmit check sees task->state !=
ISCSI_TASK_RUNNING, skips saving conn->task, and drops the final reference,
freeing task and task->sc while tcp_sw_conn->out.segment still references
them.

> @@ -1584,10 +1608,12 @@ static int iscsi_xmit_task(struct iscsi_session *session, struct iscsi_conn *con
>  	spin_unlock_bh(&session->frwd_lock);
>  	rc = session->tt->xmit_task(task);
>  	spin_lock_bh(&session->frwd_lock);
> +	/* regular RX path uses back_lock */
> +	spin_lock_bh(&session->back_lock);
>  	if (!rc) {
>  		/* done with this task */
>  		task->last_xfer = jiffies;
> -	} else {
> +	} else if (task->state == ISCSI_TASK_RUNNING) {
>  		/*
>  		 * get an extra ref that is released next time we access it
>  		 * as conn->task above.
> @@ -1596,7 +1622,8 @@ static int iscsi_xmit_task(struct iscsi_session *session, struct iscsi_conn *con
>  		conn->task = task;
>  	}
>  
> -	iscsi_put_task(task);
> +	__iscsi_put_task(session, task);
> +	spin_unlock_bh(&session->back_lock);
>  	return rc;
>  }

[Severity: High]
On iscsi_tcp, if session->tt->xmit_task() returns -EAGAIN after partially
sending a PDU and the receive path has completed the task in the meantime,
does dropping task here cause a use-after-free on tcp_sw_conn->out.segment?

During session->tt->xmit_task(), iscsi_sw_tcp_pdu_init() sets the
per-connection tcp_sw_conn->out.segment and out.data_segment to point at
task->hdr and task->sc->sdb.table.sgl.  If the socket buffer fills up
mid-PDU (-EAGAIN) and an incoming response completes the task either before
back_lock is taken here or while the task was parked in conn->task,
__iscsi_put_task() drops the last reference:

CPU0 (xmit worker)                CPU1 (RX, back_lock)
iscsi_xmit_task()
  session->tt->xmit_task(task)
    iscsi_sw_tcp_pdu_init()
      out.segment -> task->sc->sdb
    iscsi_sw_tcp_pdu_xmit()
      -> -EAGAIN (unsent bytes left)
                                  iscsi_complete_task(task)
                                    task->state = ISCSI_TASK_COMPLETED
                                    __iscsi_put_task() (refcount 2->1)
  spin_lock_bh(&session->back_lock)
  /* task->state != RUNNING:
   * conn->task not set
   */
  __iscsi_put_task() (refcount 1->0)
    iscsi_free_task()
      scsi_done(sc) (frees sgl)
...
iscsi_data_xmit() (next requeued task)
  iscsi_tcp_task_xmit()
    session->tt->xmit_pdu()
      iscsi_sw_tcp_xmit()
        --> reads freed sgl in out.segment  <-- use-after-free

iscsi_tcp_cleanup_task() only drains tcp_task->r2tqueue and does not reset
tcp_sw_conn->out.segment:

drivers/scsi/libiscsi_tcp.c:iscsi_tcp_task_xmit() {
	...
flush:
	/* Flush any pending data first. */
	rc = session->tt->xmit_pdu(task);
	if (rc < 0)
		return rc;
	...
}

When the next task on conn->requeue runs iscsi_tcp_task_xmit(), it flushes
the remaining bytes from tcp_sw_conn->out.segment before calling init_pdu(),
reading from the freed scatterlist and pages.

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

      reply	other threads:[~2026-11-15 13:00 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-15 12:21 [PATCH v3 0/3] scsi: libiscsi: tighten task completion Yehyeong Lee
2026-11-15 12:21 ` [PATCH v3 1/3] scsi: libiscsi: only complete a task that has been sent Yehyeong Lee
2026-11-15 12:21 ` [PATCH v3 2/3] scsi: libiscsi: do not complete a task sent with a reserved ITT Yehyeong Lee
2026-11-15 12:21 ` [PATCH v3 3/3] scsi: libiscsi: take a transmit reference before a task is published Yehyeong Lee
2026-11-15 13:00   ` 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-215510@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-scsi@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --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