From 9fb4adbc39751697b9fcf71e45bd5d5119c3f07c Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Thu, 22 Oct 2026 15:29:33 +0200 Subject: [PATCH] nvmet: pci-epf: require SGLs to match the command data length 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 Signed-off-by: Andrea Parri Signed-off-by: Keith Busch --- 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 099e8766f6d4..0cc198f87dec 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)