[PATCH v2] nvmet-fc: bound and advertise the maximum FCP data transfer length

Yehyeong Lee yhlee at isslab.korea.ac.kr
Tue Oct 13 08:33:16 PDT 2026


nvmet_fc_handle_fcp_rqst() takes the data length straight from the FCP
command IU (cmdiu->data_len, a 32-bit value from the wire) into
fod->req.transfer_len and calls nvmet_fc_alloc_tgt_pgs(), which hands it
to sgl_alloc() with no upper bound.  nvmet_req_init() only parses the
command and clears transfer_len; nvmet_check_transfer_len(), the only
other place the length is checked, runs later from req->execute(), after
the allocation has already happened.

The fc transport also advertised no limit: with no ->get_mdts callback the
target reports MDTS as unlimited, so a host has no indication it should
keep transfers small.  A remote, effectively unauthenticated host can
therefore declare a huge transfer length and make the target allocate
that much kernel memory for a command that never delivers the data.
nvmet-tcp bounds the equivalent length and the rdma transport is limited
by its 24-bit SGL length field; the fc transport had neither.

Advertise a Maximum Data Transfer Size through a ->get_mdts callback and
reject a command whose data length exceeds it before allocating anything.
The ceiling is 16 MiB, the largest transfer any in-tree fc LLDD can
describe (lpfc maps up to LPFC_MAX_SG_SEG_CNT 4096 segments, qla2xxx
1024), so no in-tree configuration is affected: a compliant host now sees
the limit through MDTS, and a host that ignores it is failed with
SGL_INVALID_DATA before any allocation is attempted.

Move the fod->offset reset ahead of the length check so that the new
rejection path and the existing allocation-failure path both report a
zero transferred length in their response, rather than the stale value
left in the recycled command context.

Fixes: c53432030d86 ("nvme-fabrics: Add target support for FC transport")
Cc: stable at vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee at isslab.korea.ac.kr>
---
v2: advertise the ceiling through a ->get_mdts callback, per Christoph
    Hellwig's review, and move the fod->offset reset ahead of the length
    check so the rejection and allocation-failure paths both report a zero
    transferred length; keep the allocation-time bound as enforcement.
v1: https://lore.kernel.org/linux-nvme/20261013104038.3078601-1-yhlee@isslab.korea.ac.kr/

 drivers/nvme/target/fc.c | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/drivers/nvme/target/fc.c b/drivers/nvme/target/fc.c
index 1b557775e033a..55263d162cb7f 100644
--- a/drivers/nvme/target/fc.c
+++ b/drivers/nvme/target/fc.c
@@ -62,6 +62,9 @@ struct nvmet_fc_ls_req_op {		/* for an LS RQST XMT */
 /* desired maximum for a single sequence - if sg list allows it */
 #define NVMET_FC_MAX_SEQ_LENGTH		(256 * 1024)
 
+/* maximum data transfer length a host may declare in an FCP CMD IU */
+#define NVMET_FC_MAX_XFR_LENGTH		(16 * 1024 * 1024)
+
 enum nvmet_fcp_datadir {
 	NVMET_FCP_NODATA,
 	NVMET_FCP_WRITE,
@@ -2576,7 +2579,16 @@ nvmet_fc_handle_fcp_rqst(struct nvmet_fc_tgtport *tgtport,
 	/* keep a running counter of tail position */
 	atomic_inc(&fod->queue->sqtail);
 
+	fod->offset = 0;
+
 	if (fod->req.transfer_len) {
+		if (fod->req.transfer_len > NVMET_FC_MAX_XFR_LENGTH) {
+			nvmet_req_complete(&fod->req,
+					   NVME_SC_SGL_INVALID_DATA |
+					   NVME_STATUS_DNR);
+			return;
+		}
+
 		ret = nvmet_fc_alloc_tgt_pgs(fod);
 		if (ret) {
 			nvmet_req_complete(&fod->req, ret);
@@ -2585,7 +2597,6 @@ nvmet_fc_handle_fcp_rqst(struct nvmet_fc_tgtport *tgtport,
 	}
 	fod->req.sg = fod->data_sg;
 	fod->req.sg_cnt = fod->data_sg_cnt;
-	fod->offset = 0;
 
 	if (fod->io_dir == NVMET_FCP_WRITE) {
 		/* pull the data over before invoking nvmet layer */
@@ -2988,6 +2999,14 @@ nvmet_fc_host_traddr(struct nvmet_ctrl *ctrl,
 	return ret;
 }
 
+static u8
+nvmet_fc_get_mdts(const struct nvmet_ctrl *ctrl)
+{
+	int page_shift = NVME_CAP_MPSMIN(ctrl->cap) + 12;
+
+	return ilog2(NVMET_FC_MAX_XFR_LENGTH) - page_shift;
+}
+
 static const struct nvmet_fabrics_ops nvmet_fc_tgt_fcp_ops = {
 	.owner			= THIS_MODULE,
 	.type			= NVMF_TRTYPE_FC,
@@ -2998,6 +3017,7 @@ static const struct nvmet_fabrics_ops nvmet_fc_tgt_fcp_ops = {
 	.delete_ctrl		= nvmet_fc_delete_ctrl,
 	.discovery_chg		= nvmet_fc_discovery_chg,
 	.host_traddr		= nvmet_fc_host_traddr,
+	.get_mdts		= nvmet_fc_get_mdts,
 };
 
 static int __init nvmet_fc_init_module(void)

base-commit: 24a19b953e2a3d0ea5a8c4951a0ab8f32720c907
-- 
2.43.0




More information about the Linux-nvme mailing list