* [PATCH v2 0/4] nvmet: fix read data, status and length checks in fc, zns and pci-epf
@ 2026-10-22 13:29 Andrea Parri
2026-10-22 13:29 ` [PATCH v2 1/4] nvmet-fc: only send read data for successful commands Andrea Parri
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Andrea Parri @ 2026-10-22 13:29 UTC (permalink / raw)
To: Justin Tee, Naresh Gottumukkala, Paul Ely, Christoph Hellwig,
Sagi Grimberg, Chaitanya Kulkarni
Cc: Andrea Parri, James Smart, Johannes Thumshirn, Damien Le Moal,
Keith Busch, Manivannan Sadhasivam, Krzysztof Wilczyński,
Niklas Cassel, linux-nvme, linux-kernel
A connected host can currently receive stale target memory through two
nvmet transports, and ZNS answers a Zone Management Receive whose NUMD
byte count wraps.
The FC transport sends a read buffer even when the command completes with
an error. Some errors occur before the command handler initializes that
buffer, so the host receives data left in the allocation.
The PCI endpoint transport allocates each read segment at the length of
the host's SGL data descriptor and sends every segment in full, even when
the descriptors describe more than the command transfers. The excess
reaches the host uninitialized, on any successful read.
Patch 1 makes the FC transport send read data only for successful
commands, matching the TCP and RDMA transports. Patch 2 rejects NUMD
values whose byte count wraps the u32 length calculation instead of
accepting the wrapped request.
Patch 3 fixes a related PCI endpoint bug that patch 4 depends on. The
transport reports every error it detects itself as a successful
completion, because nvmet_pci_epf_complete_iod() reloads the status from
a CQE that only the nvmet core fills in, so even a write whose data
could not be fetched is reported as written. Patch 4 then makes the
transport reject SGLs whose data descriptors do not add up to the
command's data length.
Patches 1 and 2 were tested on top of nvme-7.4 over the loop, tcp, rdma
(rdma_rxe) and fc (fcloop) transports, with a zoned null_blk namespace
for patch 2. Without them, a failed Identify over fc returns data and a
wrapping NUMD is accepted; with them, neither occurs on any of the four
transports.
blktests nvme/072 and nvme/074 cover patches 1 and 2:
https://lore.kernel.org/r/20261022130927.3583166-1-parri.andrea@gmail.com
Their results in virtme-ng VMs, on v7.3-rc4-108-g551c722f4080 without
the fixes and on nvme-7.4 with this series applied:
loop tcp rdma fc
unpatched 072 pass pass pass FAIL failed Identify transferred
4096 bytes
074 FAIL FAIL FAIL FAIL NUMD 0x4000000f not rejected
patched both pass pass pass pass
Niklas Cassel tested patches 3 and 4 on PCI endpoint hardware with fio
over SGLs, with no change in performance; that exercises the normal
paths, not the new error paths. The pci-epf side of patch 2 is
build-tested only.
An LLM found the bugs fixed here and wrote the fixes. Following commit
1e035297ffda ("docs: remove Assisted-by references"), this is noted here
rather than in Assisted-by trailers.
Changes since v1:
- Dropped commit 31b8a6af19b7 ("nvmet: zns: don't leak stale memory in
zone management receive"), applied to nvme-7.4.
- Patch 2: check NUMD at the start of
nvmet_bdev_execute_zone_mgmt_recv() and in nvmet_io_cmd_transfer_len(),
before any (numd + 1) << 2 is computed, and drop the over-long comment
line (Christoph).
- Patches 3 and 4: add Niklas's Tested-by.
- Patches 1, 3 and 4: no code changes.
- All patches: drop the Assisted-by trailers; LLM use is noted above.
- Rebased on nvme-7.4.
v1: https://lore.kernel.org/r/20261012141239.156408-1-parri.andrea@gmail.com
Andrea Parri (4):
nvmet-fc: only send read data for successful commands
nvmet: zns: reject a NUMD whose byte count wraps
nvmet: pci-epf: report transport errors in command completions
nvmet: pci-epf: require SGLs to match the command data length
drivers/nvme/target/core.c | 3 +++
drivers/nvme/target/fc.c | 8 ++++----
drivers/nvme/target/pci-epf.c | 30 +++++++++++++++++++++++++++---
drivers/nvme/target/zns.c | 12 ++++++++++--
4 files changed, 44 insertions(+), 9 deletions(-)
base-commit: 2e8e30b3cf9f165e1a1d079331543cb7f2ad0e0c
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 1/4] nvmet-fc: only send read data for successful commands
2026-10-22 13:29 [PATCH v2 0/4] nvmet: fix read data, status and length checks in fc, zns and pci-epf Andrea Parri
@ 2026-10-22 13:29 ` Andrea Parri
2026-10-22 13:29 ` [PATCH v2 2/4] nvmet: zns: reject a NUMD whose byte count wraps Andrea Parri
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Andrea Parri @ 2026-10-22 13:29 UTC (permalink / raw)
To: Justin Tee, Naresh Gottumukkala, Paul Ely, Christoph Hellwig,
Sagi Grimberg, Chaitanya Kulkarni
Cc: Andrea Parri, Johannes Thumshirn, James Smart, Keith Busch,
linux-nvme, linux-kernel, stable
When a read command completes with an error,
__nvmet_fc_fcp_nvme_cmd_done() still pushes the whole data buffer to
the host before the response. nvmet_fc_alloc_tgt_pgs() allocates that
buffer with sgl_alloc() and no __GFP_ZERO, and a command rejected by
nvmet_check_transfer_len() leaves it untouched, so the host receives
stale target memory.
The buffer is as large as the data length in the command IU, so the
amount of stale memory returned is up to the host.
The tcp and rdma targets send data only when cqe->status is zero (see
nvmet_tcp_need_data_out() and nvmet_rdma_need_data_out()), so no nvmet
command can rely on returning data with an error status. Do the same
for fc. The ERSP then reports a transferred length of fod->offset,
which is 0, matching what nvme_fc_fcpio_done() checks on the host.
Tested over nvme-fcloop: a failed read command no longer transfers
data and still completes with its error status, and successful reads
are unaffected.
Fixes: c53432030d86 ("nvme-fabrics: Add target support for FC transport")
Cc: stable@vger.kernel.org
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
drivers/nvme/target/fc.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/nvme/target/fc.c b/drivers/nvme/target/fc.c
index eecdffefa24fa..b95893adae36c 100644
--- a/drivers/nvme/target/fc.c
+++ b/drivers/nvme/target/fc.c
@@ -2487,11 +2487,11 @@ __nvmet_fc_fcp_nvme_cmd_done(struct nvmet_fc_tgtport *tgtport,
} else {
/*
- * try to push the data even if the SQE status is non-zero.
- * There may be a status where data still was intended to
- * be moved
+ * A failed command may not have filled its read buffer, so
+ * only push data for success, as the tcp and rdma targets do.
*/
- if ((fod->io_dir == NVMET_FCP_READ) && (fod->data_sg_cnt)) {
+ if (fod->io_dir == NVMET_FCP_READ && fod->data_sg_cnt &&
+ !cqe->status) {
/* push the data over before sending rsp */
nvmet_fc_transfer_fcp_data(tgtport, fod,
NVMET_FCOP_READDATA);
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 2/4] nvmet: zns: reject a NUMD whose byte count wraps
2026-10-22 13:29 [PATCH v2 0/4] nvmet: fix read data, status and length checks in fc, zns and pci-epf Andrea Parri
2026-10-22 13:29 ` [PATCH v2 1/4] nvmet-fc: only send read data for successful commands Andrea Parri
@ 2026-10-22 13:29 ` Andrea Parri
2026-10-22 13:29 ` [PATCH v2 3/4] nvmet: pci-epf: report transport errors in command completions Andrea Parri
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Andrea Parri @ 2026-10-22 13:29 UTC (permalink / raw)
To: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni
Cc: Andrea Parri, Damien Le Moal, Keith Busch, Niklas Cassel,
linux-nvme, linux-kernel
Zone Management Receive computes its buffer size as (numd + 1) << 2 in
u32, in nvmet_bdev_validate_zone_mgmt_recv(),
nvmet_bdev_zone_zmgmt_recv_work() and
nvmet_bdev_execute_zone_mgmt_recv(). NUMD is a 32-bit dword count, so
any NUMD from 0x3fffffff up wraps.
The data length check in nvmet_bdev_execute_zone_mgmt_recv() sees the
same wrapped value, so such a command can pass it. A data length that
does not match the wrapped size is already rejected by that check, with
Data SGL Length Invalid.
Every site agrees on the wrapped size, so the target zeroes and fills
only that many bytes and nothing leaks, but it answers a command whose
NUMD asks for 4 GiB or more.
Reject a NUMD from U32_MAX / 4 up with Invalid Field at the start of
nvmet_bdev_execute_zone_mgmt_recv(), before this handler or its work
item computes the byte count. A size_t computation would still wrap on
32-bit kernels, where nvmet_check_transfer_len() and req->transfer_len
are 32 bits wide.
nvmet_io_cmd_transfer_len() in core.c, which the pci-epf transport uses
to size its buffer, computes the same expression before this handler
runs. Return 0 there for such a NUMD, so pci-epf allocates no buffer and
the handler then rejects the command.
Tested over the loop, tcp, rdma and fc transports with a zoned
null_blk namespace: a wrapping NUMD, sent with a data length that
matches the wrapped size, was accepted before this patch and now fails
with Invalid Field, while ordinary NUMD values still succeed. The core.c
change only affects pci-epf and is build-tested only.
Fixes: aaf2e048af27 ("nvmet: add ZBD over ZNS backend support")
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
v2: check NUMD at the start of nvmet_bdev_execute_zone_mgmt_recv() and in
nvmet_io_cmd_transfer_len(), before any (numd + 1) << 2 is computed,
instead of in nvmet_bdev_validate_zone_mgmt_recv(); this also drops the
over-long comment line (Christoph).
drivers/nvme/target/core.c | 3 +++
drivers/nvme/target/zns.c | 12 ++++++++++--
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
index 291d782ce5442..2e4e066ef6bac 100644
--- a/drivers/nvme/target/core.c
+++ b/drivers/nvme/target/core.c
@@ -1093,6 +1093,9 @@ static u32 nvmet_io_cmd_transfer_len(struct nvmet_req *req)
case nvme_cmd_dsm:
return nvmet_dsm_len(req);
case nvme_cmd_zone_mgmt_recv:
+ /* The handler rejects a NUMD whose byte count would wrap. */
+ if (le32_to_cpu(req->cmd->zmr.numd) >= U32_MAX / 4)
+ return 0;
return (le32_to_cpu(req->cmd->zmr.numd) + 1) << 2;
default:
return 0;
diff --git a/drivers/nvme/target/zns.c b/drivers/nvme/target/zns.c
index 78d82b6ff1214..92f5c1435582e 100644
--- a/drivers/nvme/target/zns.c
+++ b/drivers/nvme/target/zns.c
@@ -322,9 +322,17 @@ static void nvmet_bdev_zone_zmgmt_recv_work(struct work_struct *w)
void nvmet_bdev_execute_zone_mgmt_recv(struct nvmet_req *req)
{
- u32 out_bufsize = (le32_to_cpu(req->cmd->zmr.numd) + 1) << 2;
+ u32 numd = le32_to_cpu(req->cmd->zmr.numd);
+
+ /* From U32_MAX / 4 up, (numd + 1) << 2 wraps here and in the work. */
+ if (numd >= U32_MAX / 4) {
+ req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd, numd);
+ nvmet_req_complete(req,
+ NVME_SC_INVALID_FIELD | NVME_STATUS_DNR);
+ return;
+ }
- if (!nvmet_check_transfer_len(req, out_bufsize))
+ if (!nvmet_check_transfer_len(req, (numd + 1) << 2))
return;
INIT_WORK(&req->z.zmgmt_work, nvmet_bdev_zone_zmgmt_recv_work);
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 3/4] nvmet: pci-epf: report transport errors in command completions
2026-10-22 13:29 [PATCH v2 0/4] nvmet: fix read data, status and length checks in fc, zns and pci-epf Andrea Parri
2026-10-22 13:29 ` [PATCH v2 1/4] nvmet-fc: only send read data for successful commands Andrea Parri
2026-10-22 13:29 ` [PATCH v2 2/4] nvmet: zns: reject a NUMD whose byte count wraps Andrea Parri
@ 2026-10-22 13:29 ` Andrea Parri
2026-10-22 13:29 ` [PATCH v2 4/4] nvmet: pci-epf: require SGLs to match the command data length Andrea Parri
2026-10-22 15:16 ` [PATCH v2 0/4] nvmet: fix read data, status and length checks in fc, zns and pci-epf Keith Busch
4 siblings, 0 replies; 6+ messages in thread
From: Andrea Parri @ 2026-10-22 13:29 UTC (permalink / raw)
To: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni
Cc: Andrea Parri, Damien Le Moal, Keith Busch, Manivannan Sadhasivam,
Krzysztof Wilczyński, Niklas Cassel, linux-nvme,
linux-kernel, stable
When the PCI endpoint target fails a command itself, the host receives a
successful completion. This happens for a data length above MDTS,
malformed PRPs or SGLs, a buffer allocation failure, an SQ that is not
live, and a failed DMA of the command data. A write whose data cannot be
fetched from the host is thus reported as written, though nothing was
executed.
Each of these paths sets iod->status and calls
nvmet_pci_epf_complete_iod(), which then reloads iod->status from
iod->cqe.status. The nvmet core only fills that field when it completes
the request, which these paths never do, so it is still zero and
nvmet_pci_epf_cq_work() posts a success status to the host.
Drop the reload. Every path already sets iod->status before completion:
nvmet_pci_epf_queue_response() copies it from the request's CQE, and
the transport error paths set it themselves.
Found by code inspection.
Fixes: 0faa0fe6f90e ("nvmet: New NVMe PCI endpoint function target driver")
Cc: stable@vger.kernel.org
Tested-by: Niklas Cassel <cassel@kernel.org>
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
drivers/nvme/target/pci-epf.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/drivers/nvme/target/pci-epf.c b/drivers/nvme/target/pci-epf.c
index 9c28367435a59..099e8766f6d40 100644
--- a/drivers/nvme/target/pci-epf.c
+++ b/drivers/nvme/target/pci-epf.c
@@ -1149,7 +1149,6 @@ static void nvmet_pci_epf_complete_iod(struct nvmet_pci_epf_iod *iod)
unsigned long flags;
/* Print an error message for failed commands, except AENs. */
- iod->status = le16_to_cpu(iod->cqe.status) >> 1;
if (iod->status && iod->cmd.common.opcode != nvme_admin_async_event)
dev_err(iod->ctrl->dev,
"CQ[%d]: Command %s (0x%x) status 0x%0x\n",
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 4/4] nvmet: pci-epf: require SGLs to match the command data length
2026-10-22 13:29 [PATCH v2 0/4] nvmet: fix read data, status and length checks in fc, zns and pci-epf Andrea Parri
` (2 preceding siblings ...)
2026-10-22 13:29 ` [PATCH v2 3/4] nvmet: pci-epf: report transport errors in command completions Andrea Parri
@ 2026-10-22 13:29 ` Andrea Parri
2026-10-22 15:16 ` [PATCH v2 0/4] nvmet: fix read data, status and length checks in fc, zns and pci-epf Keith Busch
4 siblings, 0 replies; 6+ messages in thread
From: Andrea Parri @ 2026-10-22 13:29 UTC (permalink / raw)
To: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni
Cc: Andrea Parri, Damien Le Moal, Keith Busch, Manivannan Sadhasivam,
Krzysztof Wilczyński, Niklas Cassel, linux-nvme,
linux-kernel, stable
The PCI endpoint target sends the host every byte a read command's SGL
describes, even when the SGL describes more than the command transfers.
The excess is kmalloc() memory the command never wrote, so the host
receives stale target memory. Any successful read can do this,
including Identify.
nvmet_pci_epf_alloc_iod_data_buf() allocates each segment at the length
of its SGL data descriptor, but sets req->transfer_len from
iod->data_len, the length the command implies. Once the command
completes, nvmet_pci_epf_transfer_iod_data() sends every segment in
full. Nothing compares the descriptor lengths with iod->data_len, and
the MDTS check only bounds iod->data_len.
Fail the command with Data SGL Length Invalid when the data descriptors
do not add up to iod->data_len, before any segment is allocated. The
tcp and rdma transports take the data length from the SGL itself, so
there most commands already reject a mismatch in
nvmet_check_transfer_len(). PRP lists are built from iod->data_len and
are unaffected.
Found by code inspection.
Fixes: 0faa0fe6f90e ("nvmet: New NVMe PCI endpoint function target driver")
Cc: stable@vger.kernel.org
Tested-by: Niklas Cassel <cassel@kernel.org>
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
For stable, this depends on patch 3/4: without it, the new Data SGL
Length Invalid error is still reported to the host as a success.
drivers/nvme/target/pci-epf.c | 29 +++++++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/drivers/nvme/target/pci-epf.c b/drivers/nvme/target/pci-epf.c
index 099e8766f6d40..0cc198f87dec3 100644
--- a/drivers/nvme/target/pci-epf.c
+++ b/drivers/nvme/target/pci-epf.c
@@ -1070,10 +1070,22 @@ static int nvmet_pci_epf_iod_parse_sgl_segments(struct nvmet_pci_epf_ctrl *ctrl,
return 0;
}
+static u64 nvmet_pci_epf_iod_data_segs_len(struct nvmet_pci_epf_iod *iod)
+{
+ u64 len = 0;
+ int i;
+
+ for (i = 0; i < iod->nr_data_segs; i++)
+ len += iod->data_segs[i].length;
+
+ return len;
+}
+
static int nvmet_pci_epf_iod_parse_sgls(struct nvmet_pci_epf_iod *iod)
{
struct nvmet_pci_epf_ctrl *ctrl = iod->ctrl;
struct nvme_sgl_desc *sgl = &iod->cmd.common.dptr.sgl;
+ int ret;
if (sgl->type == (NVME_SGL_FMT_DATA_DESC << 4)) {
/* Single data descriptor case. */
@@ -1081,10 +1093,23 @@ static int nvmet_pci_epf_iod_parse_sgls(struct nvmet_pci_epf_iod *iod)
iod->data_segs = &iod->data_seg;
iod->data_seg.pci_addr = le64_to_cpu(sgl->addr);
iod->data_seg.length = le32_to_cpu(sgl->length);
- return 0;
+ } else {
+ ret = nvmet_pci_epf_iod_parse_sgl_segments(ctrl, iod);
+ if (ret)
+ return ret;
}
- return nvmet_pci_epf_iod_parse_sgl_segments(ctrl, iod);
+ /*
+ * The command executes on data_len bytes, but each segment is
+ * allocated and transferred in full, so they must describe exactly
+ * data_len bytes.
+ */
+ if (nvmet_pci_epf_iod_data_segs_len(iod) != iod->data_len) {
+ iod->status = NVME_SC_SGL_INVALID_DATA | NVME_STATUS_DNR;
+ return -EINVAL;
+ }
+
+ return 0;
}
static int nvmet_pci_epf_alloc_iod_data_buf(struct nvmet_pci_epf_iod *iod)
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2 0/4] nvmet: fix read data, status and length checks in fc, zns and pci-epf
2026-10-22 13:29 [PATCH v2 0/4] nvmet: fix read data, status and length checks in fc, zns and pci-epf Andrea Parri
` (3 preceding siblings ...)
2026-10-22 13:29 ` [PATCH v2 4/4] nvmet: pci-epf: require SGLs to match the command data length Andrea Parri
@ 2026-10-22 15:16 ` Keith Busch
4 siblings, 0 replies; 6+ messages in thread
From: Keith Busch @ 2026-10-22 15:16 UTC (permalink / raw)
To: Andrea Parri
Cc: Justin Tee, Naresh Gottumukkala, Paul Ely, Christoph Hellwig,
Sagi Grimberg, Chaitanya Kulkarni, James Smart,
Johannes Thumshirn, Damien Le Moal, Manivannan Sadhasivam,
Krzysztof Wilczy´nski, Niklas Cassel, linux-nvme,
linux-kernel
On Thu, Oct 22, 2026 at 03:29:29PM +0200, Andrea Parri wrote:
> A connected host can currently receive stale target memory through two
> nvmet transports, and ZNS answers a Zone Management Receive whose NUMD
> byte count wraps.
>
> The FC transport sends a read buffer even when the command completes with
> an error. Some errors occur before the command handler initializes that
> buffer, so the host receives data left in the allocation.
>
> The PCI endpoint transport allocates each read segment at the length of
> the host's SGL data descriptor and sends every segment in full, even when
> the descriptors describe more than the command transfers. The excess
> reaches the host uninitialized, on any successful read.
>
> Patch 1 makes the FC transport send read data only for successful
> commands, matching the TCP and RDMA transports. Patch 2 rejects NUMD
> values whose byte count wraps the u32 length calculation instead of
> accepting the wrapped request.
>
> Patch 3 fixes a related PCI endpoint bug that patch 4 depends on. The
> transport reports every error it detects itself as a successful
> completion, because nvmet_pci_epf_complete_iod() reloads the status from
> a CQE that only the nvmet core fills in, so even a write whose data
> could not be fetched is reported as written. Patch 4 then makes the
> transport reject SGLs whose data descriptors do not add up to the
> command's data length.
>
> Patches 1 and 2 were tested on top of nvme-7.4 over the loop, tcp, rdma
> (rdma_rxe) and fc (fcloop) transports, with a zoned null_blk namespace
> for patch 2. Without them, a failed Identify over fc returns data and a
> wrapping NUMD is accepted; with them, neither occurs on any of the four
> transports.
Looks good to me. Applied to nvme-7.4.
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-22 15:16 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-22 13:29 [PATCH v2 0/4] nvmet: fix read data, status and length checks in fc, zns and pci-epf Andrea Parri
2026-10-22 13:29 ` [PATCH v2 1/4] nvmet-fc: only send read data for successful commands Andrea Parri
2026-10-22 13:29 ` [PATCH v2 2/4] nvmet: zns: reject a NUMD whose byte count wraps Andrea Parri
2026-10-22 13:29 ` [PATCH v2 3/4] nvmet: pci-epf: report transport errors in command completions Andrea Parri
2026-10-22 13:29 ` [PATCH v2 4/4] nvmet: pci-epf: require SGLs to match the command data length Andrea Parri
2026-10-22 15:16 ` [PATCH v2 0/4] nvmet: fix read data, status and length checks in fc, zns and pci-epf Keith Busch
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®