* [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®