[PATCH 5/6] nvmet-fc: reject duplicate queue IDs on Create Connection

From: shao.mingyin

Date: Tue Nov 10 2026 - 10:18:07 EST


From: Shao Mingyin <shao.mingyin@xxxxxxxxxx>

nvmet_fc_alloc_target_queue() stores the new queue with

WARN_ON(assoc->queues[qid]);
assoc->queues[qid] = queue;

The queue ID comes straight from the Create Connection LS and is only
checked against NVMET_NR_QUEUES. A remote initiator can send a second
Create Connection for a queue ID that already exists, or send queue ID 0
which was already created as the admin queue by Create Association. The
WARN fires and the old pointer is overwritten: the old queue's base
reference is never put, so its workqueue, nvmet_sq and nvmet_cq are
never released. For the admin queue this also leaves the controller
reference unreachable from nvmet_fc_delete_target_assoc(), so the
controller and its subsystem reference leak permanently.

Publish the queue with cmpxchg() and destroy the newly created queue if
the slot is already taken, returning the normal queue allocation error
to the initiator. This keeps the existing error path and makes both
duplicate and admin queue IDs fail cleanly.

Fixes: c53432030d86 ("nvme-fabrics: Add target support for FC transport")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Shao Mingyin <shao.mingyin@xxxxxxxxxx>
---
Tested on 7.3.0-next-20261030 (QEMU/TCG guest) with nvmet-fc over the
fcloop loopback transport; no FC HBA or fabric is needed.

A misbehaving FC initiator sends a duplicate Create Connection for a
queue ID that already exists, or for queue ID 0 which was already
created as the admin queue by Create Association. A normal host never
does this, so the initiator is emulated by driving raw LS requests
through fcloop. The LS response and the target debugfs controller
count after controller teardown are checked:

unpatched patched
duplicate conn -> ACC + WARN duplicate conn -> RJT (0x03)
ctrl1 dir leaked after delete ctrl1 dir gone after delete

diff --git a/drivers/nvme/target/fc.c b/drivers/nvme/target/fc.c
index 2b7dc3f03912..181f8028a841 100644
--- a/drivers/nvme/target/fc.c
+++ b/drivers/nvme/target/fc.c
@@ -818,8 +818,17 @@ nvmet_fc_alloc_target_queue(struct nvmet_fc_tgt_assoc *assoc,
if (ret)
goto out_fail_iodlist;

- WARN_ON(assoc->queues[qid]);
- assoc->queues[qid] = queue;
+ /*
+ * Publish the queue only if the slot is still free. A Create
+ * Connection may arrive twice for the same queue ID, or with the
+ * admin queue ID that was created by Create Association; replacing
+ * the old pointer would leak that queue and its controller
+ * reference forever.
+ */
+ if (cmpxchg(&assoc->queues[qid], NULL, queue)) {
+ nvmet_sq_destroy(&queue->nvme_sq);
+ goto out_fail_iodlist;
+ }

return queue;

--
2.27.0