* [PATCH 0/5] nvmet: fix stale data exposure and ZNS validation
@ 2026-10-12 14:12 Andrea Parri
2026-10-12 14:12 ` [PATCH 1/5] nvmet-fc: only send read data for successful commands Andrea Parri
` (4 more replies)
0 siblings, 5 replies; 12+ messages in thread
From: Andrea Parri @ 2026-10-12 14:12 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,
linux-nvme, linux-kernel
A connected host can currently receive stale target memory through three
nvmet paths.
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.
Zone Management Receive writes only the Number of Zones field and the
descriptors returned by the backend, while the transports send the entire
requested buffer. Reserved header bytes and unused descriptor slots can
therefore retain stale data. The handler also does not verify that the
transport data length agrees with NUMD, allowing a host to increase the
amount returned beyond the size described by the command.
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 validates the
Zone Management Receive transfer length and zeroes the report before
filling it. Patch 3 rejects NUMD values whose byte count wraps the u32
length calculation instead of accepting the wrapped request.
Patch 4 fixes a related PCI endpoint bug that patch 5 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 5 then makes the
transport reject SGLs whose data descriptors do not add up to the
command's data length.
Patches 1-3 were tested over the loop, tcp, rdma (rdma_rxe) and fc
(fcloop) transports, with a zoned null_blk namespace for the Zone
Management Receive fixes. On the unpatched kernel, a failed Identify over
fc returns data, zone reports carry stale bytes and accept a transfer
length that disagrees with NUMD, and a wrapping NUMD is accepted. With
patches 1-3 applied, none of these occur on any of the four transports.
Patches 4 and 5 are build-tested only: no PCI endpoint hardware was
available.
A tested reproducer for patches 1-3 is available privately on request.
Andrea Parri (5):
nvmet-fc: only send read data for successful commands
nvmet: zns: don't leak stale memory in zone management receive
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/fc.c | 8 ++++----
drivers/nvme/target/pci-epf.c | 30 +++++++++++++++++++++++++++---
drivers/nvme/target/zns.c | 17 +++++++++++++++--
3 files changed, 46 insertions(+), 9 deletions(-)
base-commit: 24a19b953e2a3d0ea5a8c4951a0ab8f32720c907
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 1/5] nvmet-fc: only send read data for successful commands
2026-10-12 14:12 [PATCH 0/5] nvmet: fix stale data exposure and ZNS validation Andrea Parri
@ 2026-10-12 14:12 ` Andrea Parri
2026-10-12 14:12 ` [PATCH 2/5] nvmet: zns: don't leak stale memory in zone management receive Andrea Parri
` (3 subsequent siblings)
4 siblings, 0 replies; 12+ messages in thread
From: Andrea Parri @ 2026-10-12 14:12 UTC (permalink / raw)
To: Justin Tee, Naresh Gottumukkala, Paul Ely, Christoph Hellwig,
Sagi Grimberg, Chaitanya Kulkarni
Cc: Andrea Parri, Johannes Thumshirn, James Smart, 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
Assisted-by: LLM
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 1b557775e033a..44bbc7d26f07d 100644
--- a/drivers/nvme/target/fc.c
+++ b/drivers/nvme/target/fc.c
@@ -2484,11 +2484,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] 12+ messages in thread
* [PATCH 2/5] nvmet: zns: don't leak stale memory in zone management receive
2026-10-12 14:12 [PATCH 0/5] nvmet: fix stale data exposure and ZNS validation Andrea Parri
2026-10-12 14:12 ` [PATCH 1/5] nvmet-fc: only send read data for successful commands Andrea Parri
@ 2026-10-12 14:12 ` Andrea Parri
2026-10-13 14:25 ` Christoph Hellwig
2026-10-13 15:47 ` Keith Busch
2026-10-12 14:12 ` [PATCH 3/5] nvmet: zns: reject a NUMD whose byte count wraps Andrea Parri
` (2 subsequent siblings)
4 siblings, 2 replies; 12+ messages in thread
From: Andrea Parri @ 2026-10-12 14:12 UTC (permalink / raw)
To: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni
Cc: Andrea Parri, Damien Le Moal, linux-nvme, linux-kernel, stable
A Zone Management Receive report returns the full buffer the host asked
for, but nvmet_bdev_zone_zmgmt_recv_work() writes only the 8-byte
Number of Zones field and one 64-byte descriptor for each matching zone
that fits. The 56 reserved header bytes and every descriptor slot past
the last match keep whatever the buffer held before. A 4096-byte report
covering a single zone leaves 4024 bytes unwritten.
The tcp, rdma and fc transports allocate that buffer with sgl_alloc()
and pci-epf with kmalloc(), none with __GFP_ZERO, and on success each
sends the whole buffer to the host (nvmet_setup_c2h_data_pdu() for
tcp), so the host receives old kernel memory from the target.
nvmet_bdev_execute_zone_mgmt_recv() also never checks the transport
data length against NUMD, so on transports where the host sets that
length, the buffer sent back can be much larger than the report NUMD
describes. pci-epf derives its data length from NUMD, so the length
check below does not apply there, but the zeroing still covers the
report.
Reject a data length that does not match NUMD with
nvmet_check_transfer_len(), as nvmet_bdev_execute_zone_append() does,
and zero the report with nvmet_zero_sgl() before filling it. Zeroing
NUMD bytes is only enough because of the length check, so the two go
together.
Allocating with __GFP_ZERO in the transports would also close the leak,
but it adds a memset to every read command to fix one handler, so the
transports are left alone.
Tested over the loop, tcp, rdma and fc transports with a zoned
null_blk namespace: before this patch, zone reports carried stale bytes
and a data length larger than NUMD was accepted; with it, reports are
fully initialized and such a length fails with Data SGL Length Invalid.
Fixes: aaf2e048af27 ("nvmet: add ZBD over ZNS backend support")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
pci-epf has a separate, pre-existing problem with SGLs that describe more
than the command's data length. Patch 5/5 fixes it.
drivers/nvme/target/zns.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/nvme/target/zns.c b/drivers/nvme/target/zns.c
index 23a17c02abee2..78d82b6ff1214 100644
--- a/drivers/nvme/target/zns.c
+++ b/drivers/nvme/target/zns.c
@@ -282,6 +282,11 @@ static void nvmet_bdev_zone_zmgmt_recv_work(struct work_struct *w)
if (status)
goto out;
+ /* Only the zone count and matching descriptors are written below. */
+ status = nvmet_zero_sgl(req, 0, out_bufsize);
+ if (status)
+ goto out;
+
if (!req_slba_nr_zones) {
status = NVME_SC_SUCCESS;
goto out;
@@ -317,6 +322,11 @@ 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;
+
+ if (!nvmet_check_transfer_len(req, out_bufsize))
+ return;
+
INIT_WORK(&req->z.zmgmt_work, nvmet_bdev_zone_zmgmt_recv_work);
queue_work(zbd_wq, &req->z.zmgmt_work);
}
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 3/5] nvmet: zns: reject a NUMD whose byte count wraps
2026-10-12 14:12 [PATCH 0/5] nvmet: fix stale data exposure and ZNS validation Andrea Parri
2026-10-12 14:12 ` [PATCH 1/5] nvmet-fc: only send read data for successful commands Andrea Parri
2026-10-12 14:12 ` [PATCH 2/5] nvmet: zns: don't leak stale memory in zone management receive Andrea Parri
@ 2026-10-12 14:12 ` Andrea Parri
2026-10-13 14:26 ` Christoph Hellwig
2026-10-12 14:12 ` [PATCH 4/5] nvmet: pci-epf: report transport errors in command completions Andrea Parri
2026-10-12 14:12 ` [PATCH 5/5] nvmet: pci-epf: require SGLs to match the command data length Andrea Parri
4 siblings, 1 reply; 12+ messages in thread
From: Andrea Parri @ 2026-10-12 14:12 UTC (permalink / raw)
To: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni
Cc: Andrea Parri, Damien Le Moal, 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, next to the
existing check for a NUMD smaller than the report header. Computing the
size in size_t instead 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 uses the same expression and is
left alone here.
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.
Fixes: aaf2e048af27 ("nvmet: add ZBD over ZNS backend support")
Assisted-by: LLM
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
drivers/nvme/target/zns.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/nvme/target/zns.c b/drivers/nvme/target/zns.c
index 78d82b6ff1214..14689ca0c38af 100644
--- a/drivers/nvme/target/zns.c
+++ b/drivers/nvme/target/zns.c
@@ -150,14 +150,17 @@ void nvmet_execute_identify_ns_zns(struct nvmet_req *req)
static u16 nvmet_bdev_validate_zone_mgmt_recv(struct nvmet_req *req)
{
sector_t sect = nvmet_lba_to_sect(req->ns, req->cmd->zmr.slba);
- u32 out_bufsize = (le32_to_cpu(req->cmd->zmr.numd) + 1) << 2;
+ u32 numd = le32_to_cpu(req->cmd->zmr.numd);
+ u32 out_bufsize = (numd + 1) << 2;
if (sect >= get_capacity(req->ns->bdev->bd_disk)) {
req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd, slba);
return NVME_SC_LBA_RANGE | NVME_STATUS_DNR;
}
- if (out_bufsize < sizeof(struct nvme_zone_report)) {
+ /* From U32_MAX / 4 up, the byte count wraps and no longer matches NUMD. */
+ if (numd >= U32_MAX / 4 ||
+ out_bufsize < sizeof(struct nvme_zone_report)) {
req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd, numd);
return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
}
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 4/5] nvmet: pci-epf: report transport errors in command completions
2026-10-12 14:12 [PATCH 0/5] nvmet: fix stale data exposure and ZNS validation Andrea Parri
` (2 preceding siblings ...)
2026-10-12 14:12 ` [PATCH 3/5] nvmet: zns: reject a NUMD whose byte count wraps Andrea Parri
@ 2026-10-12 14:12 ` Andrea Parri
2026-10-13 14:27 ` Christoph Hellwig
2026-10-12 14:12 ` [PATCH 5/5] nvmet: pci-epf: require SGLs to match the command data length Andrea Parri
4 siblings, 1 reply; 12+ messages in thread
From: Andrea Parri @ 2026-10-12 14:12 UTC (permalink / raw)
To: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni
Cc: Andrea Parri, Damien Le Moal, Keith Busch, Manivannan Sadhasivam,
Krzysztof Wilczyński, 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. Build-tested only (x86_64, W=1): no PCI
endpoint hardware was available to run it.
Fixes: 0faa0fe6f90e ("nvmet: New NVMe PCI endpoint function target driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
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 c683450df4227..4c5fff232fb99 100644
--- a/drivers/nvme/target/pci-epf.c
+++ b/drivers/nvme/target/pci-epf.c
@@ -1152,7 +1152,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] 12+ messages in thread
* [PATCH 5/5] nvmet: pci-epf: require SGLs to match the command data length
2026-10-12 14:12 [PATCH 0/5] nvmet: fix stale data exposure and ZNS validation Andrea Parri
` (3 preceding siblings ...)
2026-10-12 14:12 ` [PATCH 4/5] nvmet: pci-epf: report transport errors in command completions Andrea Parri
@ 2026-10-12 14:12 ` Andrea Parri
2026-10-13 14:28 ` Christoph Hellwig
4 siblings, 1 reply; 12+ messages in thread
From: Andrea Parri @ 2026-10-12 14:12 UTC (permalink / raw)
To: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni
Cc: Andrea Parri, Damien Le Moal, Keith Busch, Manivannan Sadhasivam,
Krzysztof Wilczyński, 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. Build-tested only (x86_64, W=1): no PCI
endpoint hardware was available to run it.
Fixes: 0faa0fe6f90e ("nvmet: New NVMe PCI endpoint function target driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
For stable, this depends on patch 4/5: 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 4c5fff232fb99..a8df176c1a574 100644
--- a/drivers/nvme/target/pci-epf.c
+++ b/drivers/nvme/target/pci-epf.c
@@ -1073,10 +1073,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. */
@@ -1084,10 +1096,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] 12+ messages in thread
* Re: [PATCH 2/5] nvmet: zns: don't leak stale memory in zone management receive
2026-10-12 14:12 ` [PATCH 2/5] nvmet: zns: don't leak stale memory in zone management receive Andrea Parri
@ 2026-10-13 14:25 ` Christoph Hellwig
2026-10-13 15:47 ` Keith Busch
1 sibling, 0 replies; 12+ messages in thread
From: Christoph Hellwig @ 2026-10-13 14:25 UTC (permalink / raw)
To: Andrea Parri
Cc: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni,
Damien Le Moal, linux-nvme, linux-kernel, stable
On Mon, Oct 12, 2026 at 04:12:29PM +0200, Andrea Parri wrote:
> Tested over the loop, tcp, rdma and fc transports with a zoned
> null_blk namespace: before this patch, zone reports carried stale bytes
> and a data length larger than NUMD was accepted; with it, reports are
> fully initialized and such a length fails with Data SGL Length Invalid.
Can you wire up the reproduce in blktests?
The change itself looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 3/5] nvmet: zns: reject a NUMD whose byte count wraps
2026-10-12 14:12 ` [PATCH 3/5] nvmet: zns: reject a NUMD whose byte count wraps Andrea Parri
@ 2026-10-13 14:26 ` Christoph Hellwig
0 siblings, 0 replies; 12+ messages in thread
From: Christoph Hellwig @ 2026-10-13 14:26 UTC (permalink / raw)
To: Andrea Parri
Cc: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni,
Damien Le Moal, linux-nvme, linux-kernel
On Mon, Oct 12, 2026 at 04:12:30PM +0200, Andrea Parri wrote:
> 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, next to the
> existing check for a NUMD smaller than the report header. Computing the
> size in size_t instead 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 uses the same expression and is
> left alone here.
>
> 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.
>
> Fixes: aaf2e048af27 ("nvmet: add ZBD over ZNS backend support")
> Assisted-by: LLM
> Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
> ---
> drivers/nvme/target/zns.c | 7 +++++--
> 1 file changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/nvme/target/zns.c b/drivers/nvme/target/zns.c
> index 78d82b6ff1214..14689ca0c38af 100644
> --- a/drivers/nvme/target/zns.c
> +++ b/drivers/nvme/target/zns.c
> @@ -150,14 +150,17 @@ void nvmet_execute_identify_ns_zns(struct nvmet_req *req)
> static u16 nvmet_bdev_validate_zone_mgmt_recv(struct nvmet_req *req)
> {
> sector_t sect = nvmet_lba_to_sect(req->ns, req->cmd->zmr.slba);
> - u32 out_bufsize = (le32_to_cpu(req->cmd->zmr.numd) + 1) << 2;
> + u32 numd = le32_to_cpu(req->cmd->zmr.numd);
> + u32 out_bufsize = (numd + 1) << 2;
>
> if (sect >= get_capacity(req->ns->bdev->bd_disk)) {
> req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd, slba);
> return NVME_SC_LBA_RANGE | NVME_STATUS_DNR;
> }
>
> - if (out_bufsize < sizeof(struct nvme_zone_report)) {
> + /* From U32_MAX / 4 up, the byte count wraps and no longer matches NUMD. */
Overly long line here.
> + if (numd >= U32_MAX / 4 ||
> + out_bufsize < sizeof(struct nvme_zone_report)) {
I guess to make various sanitizers happy we probably should only
calculate out_bufsize after bounds checking numd?
Otherwise this looks good.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 4/5] nvmet: pci-epf: report transport errors in command completions
2026-10-12 14:12 ` [PATCH 4/5] nvmet: pci-epf: report transport errors in command completions Andrea Parri
@ 2026-10-13 14:27 ` Christoph Hellwig
2026-10-13 15:12 ` Damien Le Moal
0 siblings, 1 reply; 12+ messages in thread
From: Christoph Hellwig @ 2026-10-13 14:27 UTC (permalink / raw)
To: Andrea Parri
Cc: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni,
Damien Le Moal, Keith Busch, Manivannan Sadhasivam,
Krzysztof Wilczyński, Niklas Cassel, linux-nvme,
linux-kernel, stable
I think this looks good. Damien/Niklas, can you test this on hardware?
On Mon, Oct 12, 2026 at 04:12:31PM +0200, Andrea Parri wrote:
> 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. Build-tested only (x86_64, W=1): no PCI
> endpoint hardware was available to run it.
>
> Fixes: 0faa0fe6f90e ("nvmet: New NVMe PCI endpoint function target driver")
> Cc: stable@vger.kernel.org
> Assisted-by: LLM
> 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 c683450df4227..4c5fff232fb99 100644
> --- a/drivers/nvme/target/pci-epf.c
> +++ b/drivers/nvme/target/pci-epf.c
> @@ -1152,7 +1152,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
---end quoted text---
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 5/5] nvmet: pci-epf: require SGLs to match the command data length
2026-10-12 14:12 ` [PATCH 5/5] nvmet: pci-epf: require SGLs to match the command data length Andrea Parri
@ 2026-10-13 14:28 ` Christoph Hellwig
0 siblings, 0 replies; 12+ messages in thread
From: Christoph Hellwig @ 2026-10-13 14:28 UTC (permalink / raw)
To: Andrea Parri
Cc: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni,
Damien Le Moal, Niklas Cassel, Keith Busch,
Manivannan Sadhasivam, Krzysztof Wilczyński, linux-nvme,
linux-kernel, stable
On Mon, Oct 12, 2026 at 04:12:32PM +0200, Andrea Parri wrote:
> 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. Build-tested only (x86_64, W=1): no PCI
> endpoint hardware was available to run it.
Looks good, although again having this validated on hardware would
be really useful.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 4/5] nvmet: pci-epf: report transport errors in command completions
2026-10-13 14:27 ` Christoph Hellwig
@ 2026-10-13 15:12 ` Damien Le Moal
0 siblings, 0 replies; 12+ messages in thread
From: Damien Le Moal @ 2026-10-13 15:12 UTC (permalink / raw)
To: Christoph Hellwig, Andrea Parri
Cc: Sagi Grimberg, Chaitanya Kulkarni, Keith Busch,
Manivannan Sadhasivam, Krzysztof Wilczyński, Niklas Cassel,
linux-nvme, linux-kernel, stable
On 2026/10/13 16:27, Christoph Hellwig wrote:
> I think this looks good. Damien/Niklas, can you test this on hardware?
I do not have the hardware available right now. Niklas ? Can you test this?
--
Damien Le Moal
Western Digital Research
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/5] nvmet: zns: don't leak stale memory in zone management receive
2026-10-12 14:12 ` [PATCH 2/5] nvmet: zns: don't leak stale memory in zone management receive Andrea Parri
2026-10-13 14:25 ` Christoph Hellwig
@ 2026-10-13 15:47 ` Keith Busch
1 sibling, 0 replies; 12+ messages in thread
From: Keith Busch @ 2026-10-13 15:47 UTC (permalink / raw)
To: Andrea Parri
Cc: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni,
Damien Le Moal, linux-nvme, linux-kernel, stable
On Mon, Oct 12, 2026 at 04:12:29PM +0200, Andrea Parri wrote:
> A Zone Management Receive report returns the full buffer the host asked
> for, but nvmet_bdev_zone_zmgmt_recv_work() writes only the 8-byte
> Number of Zones field and one 64-byte descriptor for each matching zone
> that fits. The 56 reserved header bytes and every descriptor slot past
> the last match keep whatever the buffer held before. A 4096-byte report
> covering a single zone leaves 4024 bytes unwritten.
>
> The tcp, rdma and fc transports allocate that buffer with sgl_alloc()
> and pci-epf with kmalloc(), none with __GFP_ZERO, and on success each
> sends the whole buffer to the host (nvmet_setup_c2h_data_pdu() for
> tcp), so the host receives old kernel memory from the target.
Thanks, applied to nvme-7.4.
This one looks good on its own independent of the patch series, so I've
applied this one now and will wait for the comments on the remaining
patches to settle before considering the rest.
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-10-13 15:47 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-12 14:12 [PATCH 0/5] nvmet: fix stale data exposure and ZNS validation Andrea Parri
2026-10-12 14:12 ` [PATCH 1/5] nvmet-fc: only send read data for successful commands Andrea Parri
2026-10-12 14:12 ` [PATCH 2/5] nvmet: zns: don't leak stale memory in zone management receive Andrea Parri
2026-10-13 14:25 ` Christoph Hellwig
2026-10-13 15:47 ` Keith Busch
2026-10-12 14:12 ` [PATCH 3/5] nvmet: zns: reject a NUMD whose byte count wraps Andrea Parri
2026-10-13 14:26 ` Christoph Hellwig
2026-10-12 14:12 ` [PATCH 4/5] nvmet: pci-epf: report transport errors in command completions Andrea Parri
2026-10-13 14:27 ` Christoph Hellwig
2026-10-13 15:12 ` Damien Le Moal
2026-10-12 14:12 ` [PATCH 5/5] nvmet: pci-epf: require SGLs to match the command data length Andrea Parri
2026-10-13 14:28 ` Christoph Hellwig
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®