mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [BUG] scsi: target: iscsi: Text/SendTargets command reuse -> NULL-ptr-deref / UAF in iscsit_build_sendtargets_response() (remote)
@ 2026-10-20 13:20 Yu Zhang
  0 siblings, 0 replies; only message in thread
From: Yu Zhang @ 2026-10-20 13:20 UTC (permalink / raw)
  To: Mike Christie, Dmitry Bogdanov, Martin K . Petersen
  Cc: linux-scsi, target-devel, linux-kernel, Yu Zhang

A remote iSCSI initiator can crash the target (NULL pointer dereference in the
TX thread) by sending two Text PDUs that reuse one ITT.  The same reuse also
yields a use-after-free and conn_cmd_list corruption.  Everything below is
against current mainline (drivers/target/iscsi/iscsi_target.c); the paths are
unchanged from v6.x.

Root cause
----------
The ISCSI_OP_TEXT dispatch, for a Text PDU whose TTT != 0xFFFFFFFF, looks up an
*existing* command by ITT with no state check:

	case ISCSI_OP_TEXT:
		if (hdr->ttt != cpu_to_be32(0xFFFFFFFF)) {
			cmd = iscsit_find_cmd_from_itt(conn, hdr->itt);
			if (!cmd)
				goto reject;
		} else {
			cmd = iscsit_allocate_cmd(conn, TASK_INTERRUPTIBLE);
			...
		}
		ret = iscsit_handle_text_cmd(conn, cmd, buf);

iscsit_find_cmd_from_itt() returns any command on conn_cmd_list with a matching
init_task_tag, regardless of its state.  A SendTargets command stays on
conn_cmd_list after its response has been sent (i_state ISTATE_SENT_STATUS); it
is freed only once the initiator's ExpStatSN acknowledges it
(iscsit_ack_from_expstatsn()).  So an initiator that does not acknowledge it can
hand the same command back for reprocessing.

iscsit_setup_text_cmd() then frees the text buffer and clears the pointer, but
leaves the previously set ICF_SENDTARGETS_SINGLE flag on the command:

	kfree(cmd->text_in_ptr);
	cmd->text_in_ptr	= NULL;

iscsit_process_text_cmd(), seeing a NULL text buffer and a non-default TTT, takes
the empty-continuation path and re-executes the command:

	unsigned char *text_in = cmd->text_in_ptr, *text_ptr;
	if (!text_in) {
		cmd->targ_xfer_tag = be32_to_cpu(hdr->ttt);
		if (cmd->targ_xfer_tag == 0xFFFFFFFF) {
			...
			goto reject;
		}
		goto empty_sendtargets;
	}

On the TX thread iscsit_build_sendtargets_response() then dereferences the NULL
text buffer, because ICF_SENDTARGETS_SINGLE is still set:

	unsigned char *text_in = cmd->text_in_ptr, *text_ptr = NULL;
	...
	if (cmd->cmd_flags & ICF_SENDTARGETS_SINGLE) {
		text_ptr = strchr(text_in, '=');	/* text_in == NULL */
		if (!text_ptr) {			/* checked too late */
			...
		}

strchr() dereferences the NULL pointer before the !text_ptr check can run.

Note that LIO never assigns a target transfer tag for Text (it always sets
cmd->targ_xfer_tag = 0xFFFFFFFF in iscsit_setup_text_cmd() and rejects
multi-sequence Text in the same function), so a well-behaved initiator never
sends a Text PDU with TTT != 0xFFFFFFFF; the reuse path is only reachable by a
hostile/buggy initiator.

Reproduced
----------
KASAN kernel, demo-mode LIO iSCSI target on loopback (authentication=0,
generate_node_acls=1), iblock backstore.  A custom initiator logs in to Full
Feature Phase (normal session), then sends two immediate Text PDUs:

  PDU #1: F=1, ITT=0xaabbccdd, TTT=0xFFFFFFFF, data "SendTargets=iqn.<x>"
          -> allocates the command, sets ICF_SENDTARGETS_SINGLE, links it on
             conn_cmd_list (harmless, normal SendTargets response is sent).
  PDU #2: F=1, ITT=0xaabbccdd, TTT=0x00000000, empty data, ExpStatSN not
          acknowledging PDU #1 -> finds the same command, frees text_in_ptr,
          re-executes it.

The target panics immediately:

  BUG: KASAN: null-ptr-deref in strchr+0x13/0x50
  Read of size 1 at addr 0000000000000000 by task iscsi_ttx/3430
  CPU: 0 ... 7.1.6
  Call Trace:
   strchr+0x13/0x50
   iscsit_build_sendtargets_response.constprop.0+0x18b/0x950
   iscsit_build_text_rsp+0x94/0x380
   iscsit_response_queue+0x3b3/0xd50
   iscsi_target_tx_thread+0x2b6/0x500
   kthread+0x211/0x270
   ret_from_fork+0x333/0x590
  BUG: kernel NULL pointer dereference, address: 0000000000000000
  Oops: 0000 [#1] SMP KASAN NOPTI
  RIP: 0010:strchr+0x13/0x50
  Kernel panic - not syncing: Fatal exception

Because SendTargets is how initiators perform discovery, and discovery sessions
are frequently unauthenticated, this is reachable by a remote peer that can open
a session to the portal; with panic_on_oops it is a remote crash.

Two further consequences of the same reuse (same root cause):
 - Use-after-free: if PDU #2 is pipelined while the TX thread is still walking
   g_tiqn_list for PDU #1, iscsit_setup_text_cmd()'s kfree(cmd->text_in_ptr)
   frees the "SendTargets=iqn..." buffer that iscsit_build_sendtargets_response()
   is reading -> KASAN use-after-free read.
 - conn_cmd_list corruption: if PDU #2 carries a non-empty "SendTargets=..."
   payload with the same ITT, iscsit_process_text_cmd() runs the unconditional
   list_add_tail(&cmd->i_conn_node, &conn->conn_cmd_list) on a command that is
   already linked.

Note for a fix
--------------
Guarding text_in before the strchr() in iscsit_build_sendtargets_response()
addresses only the NULL dereference; the use-after-free and the list corruption
come from reprocessing an already-processed/linked Text command and are not
covered by that guard.  A complete fix likely has to reject reuse of an existing
Text command via the TTT != 0xFFFFFFFF path (LIO never hands out a Text TTT, so
there is no legitimate continuation to honor).  Exact approach left to you.

Reported-by: Yu Zhang <yuz08559@gmail.com>

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-20 13:20 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-20 13:20 [BUG] scsi: target: iscsi: Text/SendTargets command reuse -> NULL-ptr-deref / UAF in iscsit_build_sendtargets_response() (remote) Yu Zhang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®