[PATCH] scsi: libiscsi_tcp: check the data direction of a Data-In PDU

Yehyeong Lee <[email protected]> Sat, 1 Aug 2026 22:36:35 +0900
Newsgroups org.kernel.vger.linux-scsi,org.kernel.vger.linux-kernel,org.kernel.vger.stable
Message-ID <[email protected]>
The Data-In branch of iscsi_tcp_hdr_dissect() resolves the ITT to a task
and copies the PDU's data segment into that command's scatterlist without
asking whether the command was reading. iscsi_tcp_r2t_rsp() in the same
file does ask, and rejects an R2T for a command that is not DMA_TO_DEVICE.

A target that answers a WRITE command's ITT with a Data-In therefore has
the initiator write target-supplied bytes into the pages that write was
about to send. Those are the caller's own pinned pages for an O_DIRECT
write, and page cache pages for a buffered one.

Observed against a test target that emits one 512-byte Data-In naming a
128 KB write's ITT, after the R2T for that write. With O_DIRECT the
caller's buffer ends up holding 512 bytes of the target's data while
pwrite() returns 131072. Buffered is quieter: pwrite() and fsync() both
succeed, nothing is logged, and reading those blocks back returns the
target's bytes out of the page cache without a command going on the wire.

Check the direction before using the scatterlist, the way the R2T path
already does.

Cc: [email protected]
Signed-off-by: Yehyeong Lee <[email protected]>
---
Reproduced on v7.2-rc5 with the in-tree initiator over TCP against tgt 1.0.97
on loopback, HeaderDigest and DataDigest both None, InitialR2T=Yes,
ImmediateData=Yes, FirstBurstLength=65536, MaxXmitDataSegmentLength=8192.

A 128 KB O_DIRECT write goes out as 8192 bytes of immediate data, one R2T for
the remaining 122880 at offset 8192, and 15 Data-Out PDUs.  The test target
inserts one Data-In after that R2T: ITT of the write, DataSN 1, buffer offset
8192, 512 bytes, no S bit.  Its payload is a repeating 16-byte marker so the
extent can be measured exactly.

  arm                 caller's buffer after pwrite()      target backing store
  no injection        marker 0, non-0x5a 0 of 131072      0x5a 8192 of 8192
  injection           marker 32 at offset 8192,           marker 32,
                      non-0x5a 512 of 131072              0x5a 7680 of 8192
  injection, patched  marker 0, non-0x5a 0 of 131072      marker 0

pwrite() returns 131072 in the first two rows.  With the patch the PDU is
rejected with ISCSI_ERR_PROTO, the same return the R2T path uses for the same
kind of violation, and the write fails.  Two runs per arm; a 4 KB write and
the 128 KB write both still complete normally on the patched kernel.

The same injection against a buffered write of the same size:

  arm                 read back after fsync()          target backing store
  no injection        marker 0, non-0x5a 0 of 131072   0x5a 131072 of 131072
  injection           marker 32 at offset 8192,        marker 32, 0x5a 130560
                      non-0x5a 512 of 131072
  injection, patched  marker 0                         nothing written

pwrite() and fsync() both return success in the first two rows and nothing is
logged.  The read back uses a descriptor opened before the write and puts no
command on the wire -- bracketing it with a 512-byte O_DIRECT read at a fixed
LBA shows nothing in between on the target side -- so those bytes come out of
the page cache.  With the patch fsync() fails with EIO and the write does not
land.  Injecting at offset 126976 instead of 8192 behaves the same.

Eight runs of the injected arm at offset 8192 and six at 126976.  The read
back was corrupt in every one.

 drivers/scsi/libiscsi_tcp.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/scsi/libiscsi_tcp.c b/drivers/scsi/libiscsi_tcp.c
index e90805ba868f..0283c4444cd0 100644
--- a/drivers/scsi/libiscsi_tcp.c
+++ b/drivers/scsi/libiscsi_tcp.c
@@ -480,6 +480,9 @@ static int iscsi_tcp_data_in(struct iscsi_conn *conn, struct iscsi_task *task)
 	int datasn = be32_to_cpu(rhdr->datasn);
 	unsigned total_in_length = task->sc->sdb.length;
 
+	if (task->sc->sc_data_direction != DMA_FROM_DEVICE)
+		return ISCSI_ERR_PROTO;
+
 	/*
 	 * lib iscsi will update this in the completion handling if there
 	 * is status.
-- 
2.43.0