[PATCH 2/6] nvmet-tcp: only wait for in-capsule data for inline SGLs
From: shao.mingyin
Date: Tue Nov 10 2026 - 11:19:14 EST
From: Shao Mingyin <shao.mingyin@xxxxxxxxxx>
nvmet-tcp decides whether to wait for in-capsule data using only the
write direction and SGL length. A write using a transport SGL can fit
within inline_data_size without carrying any in-capsule data. The
response path then withholds its R2T, leaving the command stuck.
If request initialization fails, the same length-only test both
withholds the error response and attempts to drain inline data that
will never arrive. This can also consume the following PDU as data
or a digest.
Require an offset data-block SGL in both checks. Share a helper that
only uses command and port fields, as the completion callback can run
before nvmet_req_init() has finished.
Fixes: bdaf13279192 ("nvmet-tcp: fix a segmentation fault during io parsing error")
Fixes: 872d26a391da ("nvmet-tcp: add NVMe over TCP target driver")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Shao Mingyin <shao.mingyin@xxxxxxxxxx>
---
Tested on 7.3.0-next-20261030 (QEMU/TCG guest) with nvmet-tcp over
loopback. The standard nvme-tcp host driver only sends in-capsule data
with an inline SGL, so it can never produce the triggering combination;
the test instead drives a minimal user-space NVMe/TCP host that crafts
the ICReq/Connect/command PDUs itself.
It sends a valid 4 KiB write with a transport SGL (type 0x5A) on an
enabled controller and I/O queue:
unpatched patched
no R2T, write hangs R2T received, H2CData sent,
write completes (status 0x0000)
diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
index 8092ce4914b6..a8a435aaa163 100644
--- a/drivers/nvme/target/tcp.c
+++ b/drivers/nvme/target/tcp.c
@@ -269,6 +269,23 @@ static inline bool nvmet_tcp_has_inline_data(struct nvmet_tcp_cmd *cmd)
!cmd->rbytes_done;
}
+/*
+ * Check whether the SGL of @cmd carries in-capsule data that the host
+ * will send right after the command PDU. Only the offset form of the
+ * data block descriptor is in-capsule; a regular transport SGL is
+ * fetched through an R2T instead.
+ */
+static inline bool nvmet_tcp_sgl_is_inline(struct nvmet_tcp_cmd *cmd)
+{
+ struct nvme_sgl_desc *sgl = &cmd->req.cmd->common.dptr.sgl;
+ u32 len = le32_to_cpu(sgl->length);
+
+ return sgl->type == ((NVME_SGL_FMT_DATA_DESC << 4) |
+ NVME_SGL_FMT_OFFSET) &&
+ nvme_is_write(cmd->req.cmd) && len &&
+ len <= cmd->req.port->inline_data_size;
+}
+
static inline struct nvmet_tcp_cmd *
nvmet_tcp_get_cmd(struct nvmet_tcp_queue *queue)
{
@@ -625,25 +642,19 @@ static void nvmet_tcp_queue_response(struct nvmet_req *req)
struct nvmet_tcp_queue *queue = cmd->queue;
enum nvmet_tcp_recv_state queue_state;
struct nvmet_tcp_cmd *queue_cmd;
- struct nvme_sgl_desc *sgl;
- u32 len;
/* Pairs with store_release in nvmet_prepare_receive_pdu() */
queue_state = smp_load_acquire(&queue->rcv_state);
queue_cmd = READ_ONCE(queue->cmd);
if (unlikely(cmd == queue_cmd)) {
- sgl = &cmd->req.cmd->common.dptr.sgl;
- len = le32_to_cpu(sgl->length);
-
/*
- * Wait for inline data before processing the response.
- * Avoid using helpers, this might happen before
- * nvmet_req_init is completed.
+ * Wait for in-capsule data before processing the response.
+ * This can happen before nvmet_req_init() has completed, so
+ * only look at the SGL descriptor itself.
*/
if (queue_state == NVMET_TCP_RECV_PDU &&
- len && len <= cmd->req.port->inline_data_size &&
- nvme_is_write(cmd->req.cmd))
+ nvmet_tcp_sgl_is_inline(cmd))
return;
}
@@ -981,7 +992,6 @@ static int nvmet_tcp_handle_icreq(struct nvmet_tcp_queue *queue)
static int nvmet_tcp_handle_req_failure(struct nvmet_tcp_queue *queue,
struct nvmet_tcp_cmd *cmd, struct nvmet_req *req)
{
- size_t data_len = le32_to_cpu(req->cmd->common.dptr.sgl.length);
int ret;
/*
@@ -991,8 +1001,7 @@ static int nvmet_tcp_handle_req_failure(struct nvmet_tcp_queue *queue,
* otherwise we will need to prepare a buffer and receive the
* stale data before continuing forward.
*/
- if (!nvme_is_write(cmd->req.cmd) || !data_len ||
- data_len > cmd->req.port->inline_data_size) {
+ if (!nvmet_tcp_sgl_is_inline(cmd)) {
nvmet_prepare_receive_pdu(queue);
return 0;
}