[PATCH 2/2] nvme-tcp: do not accept C2HData based on blk_rq_payload_bytes() alone

From: Yehyeong Lee

Date: Fri Jul 31 2026 - 10:45:59 EST


Commit 25e5cb780e62 ("nvme-tcp: fix possible crash in write_zeroes
processing") established that blk_rq_payload_bytes() must not be read
without first checking that the request has mappable physical segments,
and changed nvme_tcp_setup_cmd_pdu() to record the result of that rule:

req->data_len = blk_rq_nr_phys_segments(rq) ?
blk_rq_payload_bytes(rq) : 0;

That rule was applied to the send path. nvme_tcp_handle_c2h_data()
still reads blk_rq_payload_bytes() on its own.

The two differ for REQ_OP_WRITE_ZEROES, which has no physical segments
but a non-zero blk_rq_bytes(). Setup therefore leaves req->iter
uninitialised while the receive gate lets a C2HData PDU through, and
nvme_tcp_recv_data() copies into whatever req->iter holds. The
driver-private area is zeroed only when the tag set is allocated and
never on tag reuse, so that is whatever the previous command on the
same tag left behind.

Reproduced with a test target that first leaves a residual iterator on
a tag, which is behaviour the preceding patch rejects separately, and
then sends a C2HData for a WRITE_ZEROES command on that same tag:

BUG: KASAN: wild-memory-access in _copy_to_iter+0x642/0x1330
Write of size 512 at addr ffe728c2175dfa81 by task kworker/0:1H/103

CPU: 0 UID: 0 PID: 103 Comm: kworker/0:1H Not tainted 7.2.0-rc5-NVMETCP-gf5098b6bae76 #1 PREEMPT(lazy)
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: nvme_tcp_wq nvme_tcp_io_work
Call Trace:
<TASK>
dump_stack_lvl+0x53/0x70
kasan_report+0xce/0x100
? _copy_to_iter+0x642/0x1330
kasan_check_range+0x105/0x1b0
__asan_memcpy+0x3c/0x60
_copy_to_iter+0x642/0x1330
? __pfx_sock_has_perm+0x10/0x10
? worker_thread+0x45b/0xd10
? __pfx__copy_to_iter+0x10/0x10
? _raw_spin_lock_bh+0x83/0xe0
? __pfx__raw_spin_lock_bh+0x10/0x10
__skb_datagram_iter+0xf3/0x820
? __pfx_simple_copy_to_iter+0x10/0x10
? __asan_memcpy+0x3c/0x60
? skb_copy_bits+0x58d/0x830
skb_copy_datagram_iter+0x37/0x120
nvme_tcp_recv_skb+0xa07/0x4320
? __pfx_nvme_tcp_recv_skb+0x10/0x10
__tcp_read_sock+0x1ab/0x810
? __pfx_nvme_tcp_recv_skb+0x10/0x10
? __pfx_lock_sock_nested+0x10/0x10
? __pfx___tcp_read_sock+0x10/0x10
nvme_tcp_try_recv+0x152/0x1e0
? __pfx_nvme_tcp_try_recv+0x10/0x10
? __pfx_mutex_unlock+0x10/0x10
nvme_tcp_io_work+0x1e4/0x6c0
? __schedule+0x181a/0x49f0
? __pfx_nvme_tcp_io_work+0x10/0x10
process_one_work+0x633/0x1030

Test req->data_len, which is the value the rule already produced. It
subsumes the old test: data_len is zero whenever blk_rq_payload_bytes()
is zero, and additionally zero when there are no physical segments.
nvme_tcp_setup_cmd_pdu() initialises the iterator only when both
req->curr_bio and req->data_len are set, so the gate tests the same two
conditions rather than data_len alone.

Fixes: 25e5cb780e62 ("nvme-tcp: fix possible crash in write_zeroes processing")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yehyeong Lee <yhlee@xxxxxxxxxxxxxxxxxx>
---
drivers/nvme/host/tcp.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
index e93f015fa785..63a734efc2b6 100644
--- a/drivers/nvme/host/tcp.c
+++ b/drivers/nvme/host/tcp.c
@@ -668,6 +668,7 @@ static int nvme_tcp_process_nvme_cqe(struct nvme_tcp_queue *queue,
static int nvme_tcp_handle_c2h_data(struct nvme_tcp_queue *queue,
struct nvme_tcp_data_pdu *pdu)
{
+ struct nvme_tcp_request *req;
struct request *rq;

rq = nvme_find_rq(nvme_tcp_tagset(queue), pdu->command_id);
@@ -678,7 +679,8 @@ static int nvme_tcp_handle_c2h_data(struct nvme_tcp_queue *queue,
return -ENOENT;
}

- if (!blk_rq_payload_bytes(rq)) {
+ req = blk_mq_rq_to_pdu(rq);
+ if (!req->curr_bio || !req->data_len) {
dev_err(queue->ctrl->ctrl.device,
"queue %d tag %#x unexpected data\n",
nvme_tcp_queue_id(queue), rq->tag);
--
2.43.0