[PATCH 1/2] nvme-tcp: reject a data-in command that transferred too few bytes
From: Yehyeong Lee
Date: Fri Jul 31 2026 - 10:47:03 EST
nvme_tcp_recv_data() seeds queue->data_remaining from the declared
length of the C2HData PDU it is currently processing, and completes the
request once that one PDU has been consumed. Nothing compares the total
number of bytes actually received against the length the command asked
for: there is no receive-side counter in struct nvme_tcp_request, and
queue->data_remaining is a per-queue field, so it cannot accumulate per
command.
Nothing above nvme-tcp catches it either. blk_mq_end_request() completes
the request for blk_rq_bytes(rq) unconditionally, and neither struct
request nor the nvme host code has a residual concept.
A controller can therefore answer a 4096-byte read with a single
C2HData carrying 512 bytes. The host copies 512 bytes, completes the
request as fully successful, and the block layer reports a complete
read. In the buffered path user space subsequently reads 4096 bytes of
which 3584 were never written by that read and are whatever was already
in the page.
Observed against a test target that answers a 4096-byte read with one
512-byte C2HData: pread() returns 4096, and the trailing 3584 bytes are
content the read never wrote. With this patch the same target instead
produces "short data-in: got 512 of 4096" and the request is not
completed.
Count the bytes actually received and refuse to complete a successful
data-in command whose count does not match the requested length. The
check is applied at all three sites that can complete such a command:
the two NVME_TCP_F_DATA_SUCCESS paths in nvme_tcp_recv_data() and
nvme_tcp_recv_ddgst(), and nvme_tcp_process_nvme_cqe() for a command
completed by a separate response capsule.
Fixes: 3f2304f8c6d6 ("nvme-tcp: add NVMe over TCP host driver")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yehyeong Lee <yhlee@xxxxxxxxxxxxxxxxxx>
---
drivers/nvme/host/tcp.c | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
index ba5c7b3e2a7c..e93f015fa785 100644
--- a/drivers/nvme/host/tcp.c
+++ b/drivers/nvme/host/tcp.c
@@ -80,6 +80,7 @@ struct nvme_tcp_request {
struct bio *curr_bio;
struct iov_iter iter;
+ u32 data_recvd;
/* send state */
size_t offset;
@@ -612,6 +613,29 @@ static void nvme_tcp_error_recovery(struct nvme_ctrl *ctrl)
queue_work(nvme_reset_wq, &to_tcp_ctrl(ctrl)->err_work);
}
+/*
+ * NVMe has no short read: a data-in command that completes
+ * successfully must have transferred everything it asked for.
+ */
+static bool nvme_tcp_data_in_short(struct nvme_tcp_queue *queue,
+ struct request *rq)
+{
+ struct nvme_tcp_request *req = blk_mq_rq_to_pdu(rq);
+
+ if (req->status != cpu_to_le16(NVME_SC_SUCCESS))
+ return false;
+ if (rq_data_dir(rq) != READ || !req->data_len)
+ return false;
+ if (likely(req->data_recvd == req->data_len))
+ return false;
+
+ dev_err(queue->ctrl->ctrl.device,
+ "queue %d tag %#x short data-in: got %u of %u\n",
+ nvme_tcp_queue_id(queue), rq->tag,
+ req->data_recvd, req->data_len);
+ return true;
+}
+
static int nvme_tcp_process_nvme_cqe(struct nvme_tcp_queue *queue,
struct nvme_completion *cqe)
{
@@ -631,6 +655,9 @@ static int nvme_tcp_process_nvme_cqe(struct nvme_tcp_queue *queue,
if (req->status == cpu_to_le16(NVME_SC_SUCCESS))
req->status = cqe->status;
+ if (unlikely(nvme_tcp_data_in_short(queue, rq)))
+ return -EPROTO;
+
if (!nvme_try_complete_req(rq, req->status, cqe->result))
nvme_complete_rq(rq);
queue->nr_cqe++;
@@ -953,6 +980,7 @@ static int nvme_tcp_recv_data(struct nvme_tcp_queue *queue, struct sk_buff *skb,
*len -= recv_len;
*offset += recv_len;
queue->data_remaining -= recv_len;
+ req->data_recvd += recv_len;
}
if (!queue->data_remaining) {
@@ -961,6 +989,8 @@ static int nvme_tcp_recv_data(struct nvme_tcp_queue *queue, struct sk_buff *skb,
queue->ddgst_remaining = NVME_TCP_DIGEST_LENGTH;
} else {
if (pdu->hdr.flags & NVME_TCP_F_DATA_SUCCESS) {
+ if (unlikely(nvme_tcp_data_in_short(queue, rq)))
+ return -EPROTO;
nvme_tcp_end_request(rq,
le16_to_cpu(req->status));
queue->nr_cqe++;
@@ -1009,6 +1039,9 @@ static int nvme_tcp_recv_ddgst(struct nvme_tcp_queue *queue,
pdu->command_id);
struct nvme_tcp_request *req = blk_mq_rq_to_pdu(rq);
+ if (unlikely(nvme_tcp_data_in_short(queue, rq)))
+ return -EPROTO;
+
nvme_tcp_end_request(rq, le16_to_cpu(req->status));
queue->nr_cqe++;
}
@@ -2736,6 +2769,7 @@ static blk_status_t nvme_tcp_setup_cmd_pdu(struct nvme_ns *ns,
req->status = cpu_to_le16(NVME_SC_SUCCESS);
req->offset = 0;
req->data_sent = 0;
+ req->data_recvd = 0;
req->pdu_len = 0;
req->pdu_sent = 0;
req->h2cdata_left = 0;
--
2.43.0