[PATCH 1/2] nvmet-pci: validate queue IDs against endpoint queues

Michael Bommarito <[email protected]>
Newsgroups org.infradead.lists.linux-nvme,org.kernel.vger.linux-kernel,org.kernel.vger.stable
Message-ID <[email protected]>
The NVMe PCI endpoint transport allocates SQ/CQ arrays using
ctrl->nr_queues, which is capped by endpoint interrupt capacity. Common
target admin validation only checks queue IDs against subsys->max_qid, so
a root-complex host can submit Create/Delete SQ/CQ commands with qids that
pass the common checks but index past the smaller endpoint transport
arrays.

Impact: A PCI root-complex host can crash an NVMe PCI endpoint target with
malformed queue IDs.

Reject queue IDs that are outside ctrl->nr_queues before indexing the
endpoint SQ/CQ arrays.

Fixes: 0faa0fe6f90e ("nvmet: New NVMe PCI endpoint function target driver")
Cc: [email protected]
Assisted-by: Codex:gpt-5-5-xhigh
Signed-off-by: Michael Bommarito <[email protected]>
---

I reproduced this with a same-translation-unit KUnit/KASAN test. The stock
Create CQ path faults in nvmet_pci_epf_create_cq() after
nvmet_check_io_cqid() accepts qid 2 with max_qid 8 and nr_queues 2. The
patched checks reject malformed Create/Delete SQ/CQ cases while the benign
control still passes.
 drivers/nvme/target/pci-epf.c | 31 ++++++++++++++++++++++++++-----
 1 file changed, 26 insertions(+), 5 deletions(-)

diff --git a/drivers/nvme/target/pci-epf.c b/drivers/nvme/target/pci-epf.c
index 4e9db96ebfecd..5bddda09c0538 100644
--- a/drivers/nvme/target/pci-epf.c
+++ b/drivers/nvme/target/pci-epf.c
@@ -1267,10 +1267,15 @@ static u16 nvmet_pci_epf_create_cq(struct nvmet_ctrl *tctrl,
 		u16 cqid, u16 flags, u16 qsize, u64 pci_addr, u16 vector)
 {
 	struct nvmet_pci_epf_ctrl *ctrl = tctrl->drvdata;
-	struct nvmet_pci_epf_queue *cq = &ctrl->cq[cqid];
+	struct nvmet_pci_epf_queue *cq;
 	u16 status;
 	int ret;
 
+	if (cqid >= ctrl->nr_queues)
+		return NVME_SC_QID_INVALID | NVME_STATUS_DNR;
+
+	cq = &ctrl->cq[cqid];
+
 	if (test_bit(NVMET_PCI_EPF_Q_LIVE, &cq->flags))
 		return NVME_SC_QID_INVALID | NVME_STATUS_DNR;
 
@@ -1348,7 +1353,12 @@ static u16 nvmet_pci_epf_create_cq(struct nvmet_ctrl *tctrl,
 static u16 nvmet_pci_epf_delete_cq(struct nvmet_ctrl *tctrl, u16 cqid)
 {
 	struct nvmet_pci_epf_ctrl *ctrl = tctrl->drvdata;
-	struct nvmet_pci_epf_queue *cq = &ctrl->cq[cqid];
+	struct nvmet_pci_epf_queue *cq;
+
+	if (cqid >= ctrl->nr_queues)
+		return NVME_SC_QID_INVALID | NVME_STATUS_DNR;
+
+	cq = &ctrl->cq[cqid];
 
 	if (!test_and_clear_bit(NVMET_PCI_EPF_Q_LIVE, &cq->flags))
 		return NVME_SC_QID_INVALID | NVME_STATUS_DNR;
@@ -1367,10 +1377,16 @@ static u16 nvmet_pci_epf_create_sq(struct nvmet_ctrl *tctrl,
 		u16 sqid, u16 cqid, u16 flags, u16 qsize, u64 pci_addr)
 {
 	struct nvmet_pci_epf_ctrl *ctrl = tctrl->drvdata;
-	struct nvmet_pci_epf_queue *sq = &ctrl->sq[sqid];
-	struct nvmet_pci_epf_queue *cq = &ctrl->cq[cqid];
+	struct nvmet_pci_epf_queue *sq;
+	struct nvmet_pci_epf_queue *cq;
 	u16 status;
 
+	if (sqid >= ctrl->nr_queues || cqid >= ctrl->nr_queues)
+		return NVME_SC_QID_INVALID | NVME_STATUS_DNR;
+
+	sq = &ctrl->sq[sqid];
+	cq = &ctrl->cq[cqid];
+
 	if (test_bit(NVMET_PCI_EPF_Q_LIVE, &sq->flags))
 		return NVME_SC_QID_INVALID | NVME_STATUS_DNR;
 
@@ -1419,7 +1435,12 @@ static u16 nvmet_pci_epf_create_sq(struct nvmet_ctrl *tctrl,
 static u16 nvmet_pci_epf_delete_sq(struct nvmet_ctrl *tctrl, u16 sqid)
 {
 	struct nvmet_pci_epf_ctrl *ctrl = tctrl->drvdata;
-	struct nvmet_pci_epf_queue *sq = &ctrl->sq[sqid];
+	struct nvmet_pci_epf_queue *sq;
+
+	if (sqid >= ctrl->nr_queues)
+		return NVME_SC_QID_INVALID | NVME_STATUS_DNR;
+
+	sq = &ctrl->sq[sqid];
 
 	if (!test_and_clear_bit(NVMET_PCI_EPF_Q_LIVE, &sq->flags))
 		return NVME_SC_QID_INVALID | NVME_STATUS_DNR;
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.