From 9f426f14051181e251590783e68ce589a63e2b1c Mon Sep 17 00:00:00 2001 From: Yu Zhang Date: Tue, 20 Oct 2026 12:50:47 +1000 Subject: [PATCH 01/44] nvmet: take subsys->lock while force-reconnecting in nvmet_subsys_attr_qid_max_store() nvmet_subsys_attr_qid_max_store() sets subsys->max_qid and then walks subsys->ctrls to force every controller to reconnect, but it holds only nvmet_config_sem (for write). subsys->ctrls is protected by subsys->lock: nvmet_alloc_ctrl() adds an entry under subsys->lock, and nvmet_ctrl_free() removes it under subsys->lock with list_del() + kfree() (not kfree_rcu). The free path takes no nvmet_config_sem, so the two locks are independent. A controller teardown (host disconnect, keep-alive timeout or fatal error -> nvmet_sq_destroy() -> nvmet_ctrl_put() -> nvmet_ctrl_free()) can therefore list_del() an entry while qid_max_store() is iterating the list, corrupting the walk. KASAN, concurrent "echo 128 > .../subsystems//attr_qid_max" racing nvme-loop controllers connecting and disconnecting, panics in seconds: BUG: KASAN: null-ptr-deref in nvmet_subsys_attr_qid_max_store+0x10d/0x1b0 Read of size 8 at addr 0000000000000090 Call Trace: nvmet_subsys_attr_qid_max_store+0x10d/0x1b0 configfs_write_iter+0x1a7/0x250 vfs_write+0x5df/0x940 ksys_write+0xfd/0x1e0 (also seen as a general-protection fault dereferencing a LIST_POISON pointer at the same instruction.) Take subsys->lock around the list_for_each_entry() loop, matching every other subsys->ctrls accessor. ->delete_ctrl() only queues asynchronous controller teardown, so it does not re-enter subsys->lock under the loop. Fixes: 3e980f5995e0 ("nvmet: expose max queues to configfs") Cc: stable@vger.kernel.org Reviewed-by: Maurizio Lombardi Signed-off-by: Yu Zhang Tested-by: Yu Zhang Signed-off-by: Keith Busch --- drivers/nvme/target/configfs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c index 0b12b75c6e07..2b085327320e 100644 --- a/drivers/nvme/target/configfs.c +++ b/drivers/nvme/target/configfs.c @@ -1780,8 +1780,10 @@ static ssize_t nvmet_subsys_attr_qid_max_store(struct config_item *item, subsys->max_qid = qid_max; /* Force reconnect */ + mutex_lock(&subsys->lock); list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) ctrl->ops->delete_ctrl(ctrl); + mutex_unlock(&subsys->lock); up_write(&nvmet_config_sem); return cnt; From a504cd79b1670488cf66d56a3c96b2b93b1fd900 Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Wed, 14 Oct 2026 09:46:39 +0200 Subject: [PATCH 02/44] nvme-tcp: initialize pf_cache_lock when allocating the controller Since commit 61b0ed3be81b ("nvme-tcp: parallelize I/O queue allocation and startup") the I/O tag set is allocated before the I/O queues. blk_mq_alloc_tag_set calls nvme_tcp_init_request for every request, which takes pf_cache_lock of the request's queue, but nvme_tcp_alloc_queue initializes that mutex only later. With CONFIG_DEBUG_MUTEXES the first connect triggers DEBUG_LOCKS_WARN_ON(lock->magic != lock) in __mutex_lock and lock debugging is disabled. Initialize pf_cache_lock for all queues when allocating the controller and keep it for the lifetime of the controller. Fixes: 61b0ed3be81b ("nvme-tcp: parallelize I/O queue allocation and startup") Reviewed-by: Hannes Reinecke Tested-by: Maurizio Lombardi Tested-by: Saravanan D Signed-off-by: Daniel Wagner Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index ec678ed07af8..f876375dffd2 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -1531,7 +1531,6 @@ static void nvme_tcp_free_queue(struct nvme_ctrl *nctrl, int qid) kfree(queue->pdu); mutex_destroy(&queue->send_mutex); mutex_destroy(&queue->queue_lock); - mutex_destroy(&queue->pf_cache_lock); #ifdef CONFIG_DEBUG_LOCK_ALLOC lockdep_unregister_key(&queue->nvme_tcp_sk_key); @@ -1861,7 +1860,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, INIT_LIST_HEAD(&queue->send_list); mutex_init(&queue->send_mutex); INIT_WORK(&queue->io_work, nvme_tcp_io_work); - mutex_init(&queue->pf_cache_lock); if (qid > 0) queue->cmnd_capsule_len = nctrl->ioccsz * 16; @@ -2002,7 +2000,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, err_destroy_mutex: mutex_destroy(&queue->send_mutex); mutex_destroy(&queue->queue_lock); - mutex_destroy(&queue->pf_cache_lock); return ret; } @@ -3028,7 +3025,7 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev, struct nvmf_ctrl_options *opts) { struct nvme_tcp_ctrl *ctrl; - int ret; + int ret, i; ctrl = kzalloc_obj(*ctrl); if (!ctrl) @@ -3098,6 +3095,14 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev, goto out_free_ctrl; } + /* + * The I/O tag set is allocated before the I/O queues, and initializing + * its requests already allocates the PDUs from the page frag caches of + * the queues, so initialize their locks here. + */ + for (i = 0; i < ctrl->ctrl.queue_count; i++) + mutex_init(&ctrl->queues[i].pf_cache_lock); + ctrl->net = get_net(current->nsproxy->net_ns); ret = nvme_init_ctrl(&ctrl->ctrl, dev, &nvme_tcp_ctrl_ops, 0); From 9588769af5fc8ac12290d48ce1014a78916681fc Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Wed, 14 Oct 2026 09:46:41 +0200 Subject: [PATCH 03/44] nvme-fabrics: reset io_queues before computing the queue mapping nvmf_set_io_queues only sets the io_queues entries of the queue types it hands out, but the array is reused on every reconnect. When the controller grants fewer queues on a reconnect, stale entries survive and nvmf_map_queues maps CPUs to hardware contexts that don't exist anymore, which crashes blk_mq_map_swqueue with a NULL pointer dereference. E.g. with nr_io_queues=8, nr_write_queues=8 and nr_poll_queues=8, 24 queues are mapped 8/8/8 (default/read/poll). If the controller grants only 16 queues on the reconnect, the poll map still starts at hardware context 16. Clear the array before computing the new mapping. Fixes: 6486199378a5 ("nvme-tcp: fix queue mapping when queue count is limited") Fixes: 5651cd3c4336 ("nvme-rdma: fix queue mapping when queue count is limited") Reviewed-by: Hannes Reinecke Tested-by: Maurizio Lombardi Signed-off-by: Daniel Wagner Signed-off-by: Keith Busch --- drivers/nvme/host/fabrics.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/nvme/host/fabrics.c b/drivers/nvme/host/fabrics.c index 59f823dfbbcc..571de549fbaf 100644 --- a/drivers/nvme/host/fabrics.c +++ b/drivers/nvme/host/fabrics.c @@ -1125,6 +1125,8 @@ static int nvmf_parse_options(struct nvmf_ctrl_options *opts, void nvmf_set_io_queues(struct nvmf_ctrl_options *opts, u32 nr_io_queues, u32 io_queues[HCTX_MAX_TYPES]) { + memset(io_queues, 0, sizeof(*io_queues) * HCTX_MAX_TYPES); + if (opts->nr_write_queues && opts->nr_io_queues < nr_io_queues) { /* * separate read/write queues From 99fd0cbae84988e0af34784aa18ddd51ae094ade Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Wed, 14 Oct 2026 09:46:42 +0200 Subject: [PATCH 04/44] nvme-fabrics: limit each queue type to the number of online CPUs nvmf_nr_io_queues limits each queue type to the number of online CPUs when asking for I/O queues, but nvmf_set_io_queues hands out the granted queues without these limits, and only nr_io_queues is clamped when parsing the options. E.g. with 2 CPUs, nr_io_queues=2, nr_write_queues=10 and nr_poll_queues=10, 2 + 2 + 2 = 6 queues are requested, but handed out as 4 default, 2 read and 0 poll queues. blk_mq_map_queues can't create more CPU masks than there are CPUs, so default queues 2 and 3 take over the CPUs of default queues 0 and 1, and connecting the I/O queue of default queue 1 fails with -EXDEV. Apply the same limits in nvmf_set_io_queues. A queue type with more queues than online CPUs always has a hardware context without an online CPU, so no working setup changes. The options are not modified, so the limits follow CPU hotplug on reconnect. Fixes: 6486199378a5 ("nvme-tcp: fix queue mapping when queue count is limited") Fixes: a249d3066de6 ("nvme-fabrics: add queue setup helpers") Reviewed-by: Hannes Reinecke Tested-by: Maurizio Lombardi Signed-off-by: Daniel Wagner Signed-off-by: Keith Busch --- drivers/nvme/host/fabrics.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/nvme/host/fabrics.c b/drivers/nvme/host/fabrics.c index 571de549fbaf..27a8ce51d2b3 100644 --- a/drivers/nvme/host/fabrics.c +++ b/drivers/nvme/host/fabrics.c @@ -1125,18 +1125,27 @@ static int nvmf_parse_options(struct nvmf_ctrl_options *opts, void nvmf_set_io_queues(struct nvmf_ctrl_options *opts, u32 nr_io_queues, u32 io_queues[HCTX_MAX_TYPES]) { + /* + * Each queue type is mapped to the CPUs separately, so a type with + * more queues than online CPUs has hardware contexts without an online + * CPU. Use the same limits as nvmf_nr_io_queues. + */ + u32 nr_io = min(opts->nr_io_queues, num_online_cpus()); + u32 nr_write = min(opts->nr_write_queues, num_online_cpus()); + u32 nr_poll = min(opts->nr_poll_queues, num_online_cpus()); + memset(io_queues, 0, sizeof(*io_queues) * HCTX_MAX_TYPES); - if (opts->nr_write_queues && opts->nr_io_queues < nr_io_queues) { + if (nr_write && nr_io < nr_io_queues) { /* * separate read/write queues * hand out dedicated default queues only after we have * sufficient read queues. */ - io_queues[HCTX_TYPE_READ] = opts->nr_io_queues; + io_queues[HCTX_TYPE_READ] = nr_io; nr_io_queues -= io_queues[HCTX_TYPE_READ]; io_queues[HCTX_TYPE_DEFAULT] = - min(opts->nr_write_queues, nr_io_queues); + min(nr_write, nr_io_queues); nr_io_queues -= io_queues[HCTX_TYPE_DEFAULT]; } else { /* @@ -1145,14 +1154,14 @@ void nvmf_set_io_queues(struct nvmf_ctrl_options *opts, u32 nr_io_queues, * sufficient queue count to have dedicated default queues. */ io_queues[HCTX_TYPE_DEFAULT] = - min(opts->nr_io_queues, nr_io_queues); + min(nr_io, nr_io_queues); nr_io_queues -= io_queues[HCTX_TYPE_DEFAULT]; } - if (opts->nr_poll_queues && nr_io_queues) { + if (nr_poll && nr_io_queues) { /* map dedicated poll queues only if we have queues left */ io_queues[HCTX_TYPE_POLL] = - min(opts->nr_poll_queues, nr_io_queues); + min(nr_poll, nr_io_queues); } } EXPORT_SYMBOL_GPL(nvmf_set_io_queues); From 36ede1aa85d5d74c39ed375a906f6eadb15cee5b Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Wed, 14 Oct 2026 09:46:43 +0200 Subject: [PATCH 05/44] nvme-fabrics: clear the poll map if no poll queues are mapped blk_mq_alloc_tag_set sets the nr_queues of every map to nr_hw_queues. When nr_poll_queues is set but no queues are left for polling, nvmf_map_queues skips the poll map and leaves it this way, with all CPUs mapped to hctx 0. The queue then claims poll support, polled I/O ends up on hctx 0, which isn't a poll queue, and hctx 0 is marked as a poll hctx, so even the Connect command of the first I/O queue is marked as polled. Clear the poll map when no poll queues are mapped, so polled I/O is rejected with -EOPNOTSUPP. Fixes: 1a9460cef571 ("nvme-tcp: support simple polling") Fixes: 5651cd3c4336 ("nvme-rdma: fix queue mapping when queue count is limited") Reviewed-by: Hannes Reinecke Tested-by: Maurizio Lombardi Signed-off-by: Daniel Wagner Signed-off-by: Keith Busch --- drivers/nvme/host/fabrics.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/nvme/host/fabrics.c b/drivers/nvme/host/fabrics.c index 27a8ce51d2b3..3d7ad86c1955 100644 --- a/drivers/nvme/host/fabrics.c +++ b/drivers/nvme/host/fabrics.c @@ -1199,6 +1199,13 @@ void nvmf_map_queues(struct blk_mq_tag_set *set, struct nvme_ctrl *ctrl, io_queues[HCTX_TYPE_DEFAULT] + io_queues[HCTX_TYPE_READ]; blk_mq_map_queues(&set->map[HCTX_TYPE_POLL]); + } else { + /* + * blk_mq_alloc_tag_set sets nr_queues of all maps to + * nr_hw_queues. Clear the poll map, otherwise the queue claims + * poll support and polled I/O ends up on hctx 0. + */ + set->map[HCTX_TYPE_POLL].nr_queues = 0; } dev_info(ctrl->device, From 7847eb57bb2783d77c05bf63b676be72c1eeb8fa Mon Sep 17 00:00:00 2001 From: Martin George Date: Wed, 21 Oct 2026 14:06:46 +0530 Subject: [PATCH 06/44] nvme-auth: update error log in nvme_auth_submit() nvme_auth_submit() issues both AUTH Send and AUTH Receive commands, but the failure messages are hardcoded to "auth_send failed", which can be misleading when an AUTH Receive fails. So print the correct direction instead. Reviewed-by: Hannes Reinecke Signed-off-by: Martin George Signed-off-by: Keith Busch --- drivers/nvme/host/auth.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/auth.c b/drivers/nvme/host/auth.c index cbb38935fa22..85f005a5efd0 100644 --- a/drivers/nvme/host/auth.c +++ b/drivers/nvme/host/auth.c @@ -85,10 +85,12 @@ static int nvme_auth_submit(struct nvme_ctrl *ctrl, int qid, qid == 0 ? NVME_QID_ANY : qid, flags); if (ret > 0) dev_warn(ctrl->device, - "qid %d auth_send failed with status %d\n", qid, ret); + "qid %d auth_%s failed with status %d\n", + qid, auth_send ? "send" : "receive", ret); else if (ret < 0) dev_err(ctrl->device, - "qid %d auth_send failed with error %d\n", qid, ret); + "qid %d auth_%s failed with error %d\n", + qid, auth_send ? "send" : "receive", ret); return ret; } From 635e760169a87abf03874cd4636cd91b580cb427 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Fri, 16 Oct 2026 10:23:38 -0700 Subject: [PATCH 07/44] nvmet-rdma: reject short connection requests nvmet_rdma_parse_cm_connect_req() treats RDMA-CM private data as a struct nvme_rdma_cm_req but accepts any nonzero private_data_len. The iWARP path allocates exactly the peer-provided length, so a six-byte MPA request reaches the parser and the hsqsize load reads two bytes past the allocation. Require the complete request structure before reading any of its fields. The decoded KASAN report is: BUG: KASAN: slab-out-of-bounds in nvmet_rdma_cm_handler (drivers/nvme/target/rdma.c:1427 drivers/nvme/target/rdma.c:1475 drivers/nvme/target/rdma.c:1614 drivers/nvme/target/rdma.c:1814) Read of size 2 at addr ff1100010459d366 by task kworker/u16:1/41 Workqueue: iw_cm_wq cm_work_handler Call Trace: dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120) print_report (mm/kasan/report.c:378 mm/kasan/report.c:482) kasan_report (mm/kasan/report.c:595) nvmet_rdma_cm_handler (drivers/nvme/target/rdma.c:1427 drivers/nvme/target/rdma.c:1475 drivers/nvme/target/rdma.c:1614 drivers/nvme/target/rdma.c:1814) cma_cm_event_handler (drivers/infiniband/core/cma.c:2181) iw_conn_req_handler (drivers/infiniband/core/cma.c:2640) cm_work_handler (drivers/infiniband/core/cm.c:2502 drivers/infiniband/core/cm.c:3858) process_one_work (kernel/workqueue.c:3396) worker_thread (kernel/workqueue.c:3479 kernel/workqueue.c:3560) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) Allocated by task 12: __kasan_kmalloc (mm/kasan/common.c:398 mm/kasan/common.c:415) __kmalloc_node_track_caller_noprof (mm/slub.c:5414 mm/slub.c:5545) kmemdup_noprof (mm/util.c:138) cm_event_handler (drivers/infiniband/core/iwcm.c:194 drivers/infiniband/core/iwcm.c:1076) siw_cm_upcall (drivers/infiniband/sw/siw/siw_cm.c:408) siw_proc_mpareq (drivers/infiniband/sw/siw/siw_cm.c:750) siw_cm_work_handler (drivers/infiniband/sw/siw/siw_cm.c:1086) The buggy address is located 0 bytes to the right of allocated 6-byte region [ff1100010459d360, ff1100010459d366) Cc: stable@vger.kernel.org Fixes: 8f000cac6e7a ("nvmet-rdma: add a NVMe over Fabrics RDMA target driver") Reported-by: Assisted-by: LLM Reviewed-by: Christoph Hellwig Signed-off-by: Weiming Shi Signed-off-by: Keith Busch --- drivers/nvme/target/rdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c index ffa1e172c259..075ab58d8c88 100644 --- a/drivers/nvme/target/rdma.c +++ b/drivers/nvme/target/rdma.c @@ -1412,7 +1412,7 @@ nvmet_rdma_parse_cm_connect_req(struct rdma_conn_param *conn, struct nvme_rdma_cm_req *req; req = (struct nvme_rdma_cm_req *)conn->private_data; - if (!req || conn->private_data_len == 0) + if (!req || conn->private_data_len < sizeof(*req)) return NVME_RDMA_CM_INVALID_LEN; if (le16_to_cpu(req->recfmt) != NVME_RDMA_CM_FMT_1_0) From 2c29f8c9b9e598c09fa50aa02200136a107f6b3f Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Sun, 18 Oct 2026 00:38:05 +0900 Subject: [PATCH 08/44] nvmet-fc: fix use-after-free in nvmet_fc_ls_create_connection() nvmet_fc_ls_create_connection() looks up the association with nvmet_fc_find_target_assoc(), which returns it with a reference held, and drops that reference as soon as the target queue has been allocated. iod->assoc is then dereferenced again by nvmet_fc_makeconnid() while building the connection id. The queue only borrows the pointer and takes no reference of its own. Once the lookup reference is dropped the association is kept alive solely by the reference taken when it was created, so a disconnect LS or a remote port unregister running concurrently can free it before nvmet_fc_makeconnid() reads it: BUG: KASAN: slab-use-after-free in nvmet_fc_handle_ls_rqst_work (drivers/nvme/target/fc.c:193 drivers/nvme/target/fc.c:1841 drivers/nvme/target/fc.c:1995 drivers/nvme/target/fc.c:2021) Read of size 8 at addr ffff888179f1b000 by task kworker/u9:2/1167 Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: nvmet-wq nvmet_fc_handle_ls_rqst_work Call Trace: dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120) print_report (mm/kasan/report.c:378 mm/kasan/report.c:482) kasan_report (mm/kasan/report.c:595) nvmet_fc_handle_ls_rqst_work (drivers/nvme/target/fc.c:193 drivers/nvme/target/fc.c:1841 drivers/nvme/target/fc.c:1995 drivers/nvme/target/fc.c:2021) process_scheduled_works (kernel/workqueue.c:3322 kernel/workqueue.c:3405) worker_thread (kernel/workqueue.c:3486) kthread (kernel/kthread.c:436) ret_from_fork (archkernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) Freed by task 1167 on cpu 0 at 405.998150s: kasan_save_track (mm/kasan/common.c:57 mm/kasan/common.c:78) kasan_save_free_info (mm/kasan/generic.c:584) __kasan_slab_free (mm/kasan/common.c:253 mm/kasan/common.c:285) kfree (include/linux/kasan.h:235 mm/slub.c:2705 mm/slub.c:6405 mm/slub.c:6720) nvmet_fc_handle_ls_rqst_work (drivers/nvme/target/fc.c:1808 drivers/nvme/target/fc.c:1995 drivers/nvme/target/fc.c:2021) process_scheduled_works (kernel/workqueue.c:3322 kernel/workqueue.c:3405) worker_thread (kernel/workqueue.c:3486) kthread (kernel/kthread.c:436) ret_from_fork (archkernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) The buggy address belongs to the object at ffff888179f1b000 which belongs to the cache kmalloc-2k of size 2048 The buggy address is located 0 bytes inside of freed 2048-byte region [ffff888179f1b000, ffff888179f1b800) Hold the lookup reference until the response has been built, and drop it on the error path as well. Reported-by: Shuangpeng Bai Closes: https://lore.kernel.org/linux-nvme/20260731204504.2396434-1-shuangpeng.kernel@gmail.com Fixes: c53432030d86 ("nvme-fabrics: Add target support for FC transport") Cc: stable@vger.kernel.org Signed-off-by: Yehyeong Lee Signed-off-by: Keith Busch --- drivers/nvme/target/fc.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/target/fc.c b/drivers/nvme/target/fc.c index eecdffefa24f..b1504557768f 100644 --- a/drivers/nvme/target/fc.c +++ b/drivers/nvme/target/fc.c @@ -1799,9 +1799,6 @@ nvmet_fc_ls_create_connection(struct nvmet_fc_tgtport *tgtport, be16_to_cpu(rqst->connect_cmd.sqsize)); if (!queue) ret = VERR_QUEUE_ALLOC_FAIL; - - /* release get taken in nvmet_fc_find_target_assoc */ - nvmet_fc_tgt_a_put(iod->assoc); } } @@ -1815,6 +1812,8 @@ nvmet_fc_ls_create_connection(struct nvmet_fc_tgtport *tgtport, FCNVME_RJT_RC_INV_ASSOC : FCNVME_RJT_RC_LOGIC, FCNVME_RJT_EXP_NONE, 0); + if (iod->assoc) + nvmet_fc_tgt_a_put(iod->assoc); return; } @@ -1836,6 +1835,9 @@ nvmet_fc_ls_create_connection(struct nvmet_fc_tgtport *tgtport, acc->connectid.connection_id = cpu_to_be64(nvmet_fc_makeconnid(iod->assoc, be16_to_cpu(rqst->connect_cmd.qid))); + + /* release get taken in nvmet_fc_find_target_assoc */ + nvmet_fc_tgt_a_put(iod->assoc); } /* From d1512773a0d539669440bd9f6d050b8e12923b5f Mon Sep 17 00:00:00 2001 From: Andrew Stellman Date: Wed, 21 Oct 2026 01:07:48 -0400 Subject: [PATCH 09/44] nvmet-rdma: fix out-of-bounds write in nvmet_rdma_map_sgl_inline() nvmet_rdma_map_sgl_inline() bounds the host-supplied in-capsule SGL offset with "off + len > inline_data_size", evaluated in 64-bit arithmetic. off is taken straight from the host SGL descriptor (le64_to_cpu(sgl->addr)) and len is promoted to 64-bit, so the sum wraps: an off within len of 2^64 makes it small and the check passes. off is then used as page_idx = off / PAGE_SIZE to index the fixed-size rsp->cmd->inline_sg[] array (NVMET_RDMA_MAX_INLINE_SGE entries), so a crafted offset yields an out-of-bounds scatterlist index that is written through in nvmet_rdma_use_inline_sg(). The mapping runs while handling a received capsule, before the command executes, so a remote RDMA peer can reach it (the Connect capsule is a write and takes this path on a queue that has no controller yet). The host controls only the low bits of the resulting displacement, so the expected impact is an out-of-bounds write that faults the target (remote denial of service). This wrap was reported, with a KASAN reproduction, on linux-nvme in May, and the check below was suggested in that thread. The fix that was applied, 48c0162f647b, reworked how the offset is mapped but kept the original check, and its page index turns a wrapped offset into an out-of-bounds index into inline_sg[]. Reject the offset before it can wrap. Found independently by static analysis with AI assistance, not reproduced at runtime. Fixes: 0d5ee2b2ab4f ("nvmet-rdma: support max(16KB, PAGE_SIZE) inline data") Fixes: 48c0162f647b ("nvmet-rdma: handle inline data with a nonzero offset") Reported-by: Bryam Vargas Closes: https://lore.kernel.org/all/LM21QIR-1-qJb7PViyJKCnGBnUzizeiNJVWQ3wb7ZwGezodjgKg3f-iobqOyequ-sT1jFCKJImfqNO_BKU3KO80xFITnaI5GTV_GxLUNDDc=@proton.me/ Suggested-by: Keith Busch Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Andrew Stellman Signed-off-by: Keith Busch --- drivers/nvme/target/rdma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c index 075ab58d8c88..ebf4a7bde524 100644 --- a/drivers/nvme/target/rdma.c +++ b/drivers/nvme/target/rdma.c @@ -864,7 +864,8 @@ static u16 nvmet_rdma_map_sgl_inline(struct nvmet_rdma_rsp *rsp) return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; } - if (off + len > rsp->queue->dev->inline_data_size) { + if (off > (u64)rsp->queue->dev->inline_data_size || + len > (u64)rsp->queue->dev->inline_data_size - off) { pr_err("invalid inline data offset!\n"); return NVME_SC_SGL_INVALID_OFFSET | NVME_STATUS_DNR; } From 94f93c61b1a5dd08d011be58172ce4392b849008 Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Thu, 22 Oct 2026 15:29:30 +0200 Subject: [PATCH 10/44] nvmet-fc: only send read data for successful commands 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 Reviewed-by: Christoph Hellwig Signed-off-by: Andrea Parri Signed-off-by: Keith Busch --- 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 b1504557768f..a63ffb436a92 100644 --- a/drivers/nvme/target/fc.c +++ b/drivers/nvme/target/fc.c @@ -2489,11 +2489,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); From 12740bec646dafe009a7d9a4d22d3495ef1ea5cd Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Thu, 22 Oct 2026 15:29:31 +0200 Subject: [PATCH 11/44] nvmet: zns: reject a NUMD whose byte count wraps 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 at the start of nvmet_bdev_execute_zone_mgmt_recv(), before this handler or its work item computes the byte count. A size_t computation 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, which the pci-epf transport uses to size its buffer, computes the same expression before this handler runs. Return 0 there for such a NUMD, so pci-epf allocates no buffer and the handler then rejects the command. 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. The core.c change only affects pci-epf and is build-tested only. Fixes: aaf2e048af27 ("nvmet: add ZBD over ZNS backend support") Signed-off-by: Andrea Parri Signed-off-by: Keith Busch --- drivers/nvme/target/core.c | 3 +++ drivers/nvme/target/zns.c | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 291d782ce544..2e4e066ef6ba 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -1093,6 +1093,9 @@ static u32 nvmet_io_cmd_transfer_len(struct nvmet_req *req) case nvme_cmd_dsm: return nvmet_dsm_len(req); case nvme_cmd_zone_mgmt_recv: + /* The handler rejects a NUMD whose byte count would wrap. */ + if (le32_to_cpu(req->cmd->zmr.numd) >= U32_MAX / 4) + return 0; return (le32_to_cpu(req->cmd->zmr.numd) + 1) << 2; default: return 0; diff --git a/drivers/nvme/target/zns.c b/drivers/nvme/target/zns.c index 78d82b6ff121..92f5c1435582 100644 --- a/drivers/nvme/target/zns.c +++ b/drivers/nvme/target/zns.c @@ -322,9 +322,17 @@ 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; + u32 numd = le32_to_cpu(req->cmd->zmr.numd); - if (!nvmet_check_transfer_len(req, out_bufsize)) + /* From U32_MAX / 4 up, (numd + 1) << 2 wraps here and in the work. */ + if (numd >= U32_MAX / 4) { + req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd, numd); + nvmet_req_complete(req, + NVME_SC_INVALID_FIELD | NVME_STATUS_DNR); + return; + } + + if (!nvmet_check_transfer_len(req, (numd + 1) << 2)) return; INIT_WORK(&req->z.zmgmt_work, nvmet_bdev_zone_zmgmt_recv_work); From 46ed7310a60029826eedb01081a4556643ed5f3b Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Thu, 22 Oct 2026 15:29:32 +0200 Subject: [PATCH 12/44] nvmet: pci-epf: report transport errors in command completions 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. 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 | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/nvme/target/pci-epf.c b/drivers/nvme/target/pci-epf.c index 9c28367435a5..099e8766f6d4 100644 --- a/drivers/nvme/target/pci-epf.c +++ b/drivers/nvme/target/pci-epf.c @@ -1149,7 +1149,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", From 9fb4adbc39751697b9fcf71e45bd5d5119c3f07c Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Thu, 22 Oct 2026 15:29:33 +0200 Subject: [PATCH 13/44] 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) From 2f61b9d1d54edf75a352c43eebe3804144b5d44e Mon Sep 17 00:00:00 2001 From: Avi Weiss Date: Thu, 22 Oct 2026 09:29:23 +0300 Subject: [PATCH 14/44] nvmet: prevent double-free after metadata SGL allocation failure If metadata SGL allocation fails, nvmet_req_alloc_sgls() frees req->sg without clearing it. The RDMA error completion path later frees the same scatterlist again. Use nvmet_req_free_sgls() instead so that the scatterlist pointers and counts are cleared before response cleanup. Fixes: c6e3f13398123 ("nvmet: add metadata support for block devices") Signed-off-by: Avi Weiss Signed-off-by: Keith Busch --- drivers/nvme/target/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 2e4e066ef6ba..4955872dc1fc 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -1357,7 +1357,7 @@ int nvmet_req_alloc_sgls(struct nvmet_req *req) return 0; out_free: - sgl_free(req->sg); + nvmet_req_free_sgls(req); out: return -ENOMEM; } From 9c15ab904e07a752a4c45f46a74c642900411b90 Mon Sep 17 00:00:00 2001 From: Martin George Date: Thu, 22 Oct 2026 17:33:23 +0530 Subject: [PATCH 15/44] nvme-multipath: avoid size_t underflow in ANA log bounds checks The ANA log bounds checks subtract the required size from ana_log_size in unsigned arithmetic, which can wrap when a rogue or buggy target reports an oversized nnsids. Compare as "ana_log_size - offset < required" instead, so the subtraction can't underflow and bypass the check. Signed-off-by: Martin George Signed-off-by: Keith Busch --- drivers/nvme/host/multipath.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 87b934b46b85..701015022199 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -878,7 +878,7 @@ static int nvme_parse_ana_log(struct nvme_ctrl *ctrl, void *data, u32 nr_nsids; size_t nsid_buf_size; - if (WARN_ON_ONCE(offset > ctrl->ana_log_size - sizeof(*desc))) + if (WARN_ON_ONCE(ctrl->ana_log_size - offset < sizeof(*desc))) return -EINVAL; nr_nsids = le32_to_cpu(desc->nnsids); @@ -894,7 +894,7 @@ static int nvme_parse_ana_log(struct nvme_ctrl *ctrl, void *data, return -EINVAL; offset += sizeof(*desc); - if (WARN_ON_ONCE(offset > ctrl->ana_log_size - nsid_buf_size)) + if (WARN_ON_ONCE(ctrl->ana_log_size - offset < nsid_buf_size)) return -EINVAL; error = cb(ctrl, desc, data); From ed75528c095bc7760a3cf34a3f3e2ec7a59eb285 Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Fri, 23 Oct 2026 11:48:05 +0200 Subject: [PATCH 16/44] nvme-pci: fix early boot hang with nvme.quirks parameter Passing nvme.quirks on the kernel command line causes a silent boot hang when the NVMe driver is compiled as a built-in module. This happens because the parameter is currently registered using module_param_cb(). For built-in modules, this macro flags the parameter to be evaluated at initcall level -1. Consequently, quirks_param_set() is called before the memory allocators are initialized. Fix this by replacing module_param_cb() with device_param_cb(). This defers the parameter parsing to initcall level 6, guaranteeing that the system memory allocators are fully functional before the quirk string is parsed and allocated. Fixes: 7bb8c40f5ad8 ("nvme: add support for dynamic quirk configuration via module parameter") Reviewed-by: Laurence Oberman Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/host/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 82e308fe4f4c..1ea98647062e 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -243,7 +243,7 @@ static struct kparam_string quirks_param_string = { .string = quirks_param, }; -module_param_cb(quirks, &quirks_param_ops, &quirks_param_string, 0444); +device_param_cb(quirks, &quirks_param_ops, &quirks_param_string, 0444); MODULE_PARM_DESC(quirks, "Enable/disable NVMe quirks by specifying " "quirks=VID:DID:quirk_names"); From a47e3077fbfb4b0dd5350a79e178c22723c17bc5 Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Fri, 23 Oct 2026 11:48:06 +0200 Subject: [PATCH 17/44] nvme-pci: increase quirks_param buffer size to 256 bytes The current 128 byte buffer for the nvme.quirks command line parameter is a bit too small. If a user needs to specify multiple quirks or use multiple VID:DID combinations, the configuration string can easily exceed this limit. Increase the size of the quirks_param array from 128 to 256 bytes to comfortably accommodate longer and more complex quirk configurations. Reviewed-by: Laurence Oberman Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/host/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 1ea98647062e..149a93a3c888 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -232,7 +232,7 @@ static int quirks_param_set(const char *value, const struct kernel_param *kp) return err; } -static char quirks_param[128]; +static char quirks_param[256]; static const struct kernel_param_ops quirks_param_ops = { .set = quirks_param_set, .get = param_get_string, From a563f13a54efb5662627743ef56b3d5f87ef6045 Mon Sep 17 00:00:00 2001 From: Martin George Date: Fri, 23 Oct 2026 19:30:57 +0530 Subject: [PATCH 18/44] nvme-fc: fix reference leak in nvme_fc_nvme_discovery_store() nvme_fc_nvme_discovery_store() takes an lport and rport reference before checking whether the rport is already queued on the local discovery list. On the retry path (goto restart), rports that were queued in an earlier pass are skipped by the list_empty() check, but the references taken for them in the current pass are never dropped, leaking one lport and one rport reference per retry. Check list_empty() first and only take references for rports that are actually added to the local discovery list. Reviewed-by: Laurence Oberman Signed-off-by: Martin George Signed-off-by: Keith Busch --- drivers/nvme/host/fc.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index 48454cb7a0fc..03f6fffc9574 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -3778,6 +3778,9 @@ static ssize_t nvme_fc_nvme_discovery_store(struct device *dev, restart: list_for_each_entry(lport, &nvme_fc_lport_list, port_list) { list_for_each_entry(rport, &lport->endp_list, endp_list) { + /* skip rports already queued for discovery */ + if (!list_empty(&rport->disc_list)) + continue; if (!nvme_fc_lport_get(lport)) continue; if (!nvme_fc_rport_get(rport)) { @@ -3799,9 +3802,7 @@ static ssize_t nvme_fc_nvme_discovery_store(struct device *dev, "failures\n"); goto process_local_list; } - if (list_empty(&rport->disc_list)) - list_add_tail(&rport->disc_list, - &local_disc_list); + list_add_tail(&rport->disc_list, &local_disc_list); } } From cead6beb89256ae7145b65fbe8e8a5fedba4a47c Mon Sep 17 00:00:00 2001 From: Qing Zhang Date: Fri, 23 Oct 2026 19:29:28 +0800 Subject: [PATCH 19/44] nvmet: protect nvmet_ports_list with nvmet_config_sem Readers of nvmet_ports_list (e.g. nvmet_subsys_disc_changed()) hold nvmet_config_sem and assert it via lockdep. The writers -- nvmet_ports_make() and nvmet_port_release() -- do not take the lock; they rely on the parent directory's VFS inode lock for serialization, which is an implementation detail of configfs and not a guaranteed contract. Align the writers with the lock the readers already require. Also move list_add() in nvmet_ports_make() to after the port is fully initialized: the old position published the port on the global list before INIT_LIST_HEAD(&port->subsystems), so a concurrent reader could traverse uninitialized list heads. flush_workqueue() in nvmet_port_release() stays outside the semaphore to avoid a lock dependency on nvmet_wq. Fixes: b662a078576e ("nvmet: enable Discovery Controller AENs") Reviewed-by: Maurizio Lombardi Signed-off-by: Qing Zhang Signed-off-by: Keith Busch --- drivers/nvme/target/configfs.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c index 2b085327320e..7cd88d6bfd61 100644 --- a/drivers/nvme/target/configfs.c +++ b/drivers/nvme/target/configfs.c @@ -2103,7 +2103,9 @@ static void nvmet_port_release(struct config_item *item) /* Let inflight controllers teardown complete */ flush_workqueue(nvmet_wq); + down_write(&nvmet_config_sem); list_del(&port->global_entry); + up_write(&nvmet_config_sem); key_put(port->keyring); kfree(port); @@ -2164,8 +2166,6 @@ static struct config_group *nvmet_ports_make(struct config_group *group, port->ana_state[i] = NVME_ANA_INACCESSIBLE; } - list_add(&port->global_entry, &nvmet_ports_list); - INIT_LIST_HEAD(&port->entry); INIT_LIST_HEAD(&port->subsystems); INIT_LIST_HEAD(&port->referrals); @@ -2199,6 +2199,10 @@ static struct config_group *nvmet_ports_make(struct config_group *group, configfs_add_default_group(&port->ana_default_group.group, &port->ana_groups_group); + down_write(&nvmet_config_sem); + list_add(&port->global_entry, &nvmet_ports_list); + up_write(&nvmet_config_sem); + return &port->group; } From 60385038754ed9fb05dab127b35f88c702252505 Mon Sep 17 00:00:00 2001 From: Rongzheng Wang Date: Sat, 24 Oct 2026 03:18:51 +0800 Subject: [PATCH 20/44] nvmet: use the enabled transport when disabling a port nvmet_disable_port() looks up the transport from the current disc_addr.trtype, while nvmet_enable_port() records the transport in port->tr_ops. If trtype is changed while a port is being enabled, the port can be removed through a different transport, passing the private data of the original transport to the wrong ->remove_port(). The same mismatch is reachable with loop and RDMA: loop add_port() leaves port->priv NULL, and RDMA remove_port() dereferences it. Use port->tr_ops for removal and serialize trtype changes with nvmet_config_sem in nvmet_addr_trtype_store(), so the transport that added the port is also the one that removes it. Fixes: 8f000cac6e7a ("nvmet-rdma: add a NVMe over Fabrics RDMA target driver") Reported-by: syzbot+c98b21427617af7f547a@syzkaller.appspotmail.com Link: https://syzkaller.appspot.com/bug?extid=c98b21427617af7f547a Link: https://lore.kernel.org/all/6acbc835.a150a118.27e199.003c.GAE@google.com/T/ Signed-off-by: Rongzheng Wang Signed-off-by: Keith Busch --- drivers/nvme/target/configfs.c | 16 ++++++++++++---- drivers/nvme/target/core.c | 8 +++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c index 7cd88d6bfd61..1d46a2bb40f8 100644 --- a/drivers/nvme/target/configfs.c +++ b/drivers/nvme/target/configfs.c @@ -387,10 +387,15 @@ static ssize_t nvmet_addr_trtype_store(struct config_item *item, const char *page, size_t count) { struct nvmet_port *port = to_nvmet_port(item); + ssize_t ret = count; int i; - if (nvmet_is_port_enabled(port, __func__)) - return -EACCES; + down_write(&nvmet_config_sem); + + if (nvmet_is_port_enabled(port, __func__)) { + ret = -EACCES; + goto out; + } for (i = 0; i < ARRAY_SIZE(nvmet_transport); i++) { if (sysfs_streq(page, nvmet_transport[i].name)) @@ -398,7 +403,8 @@ static ssize_t nvmet_addr_trtype_store(struct config_item *item, } pr_err("Invalid value '%s' for trtype\n", page); - return -EINVAL; + ret = -EINVAL; + goto out; found: memset(&port->disc_addr.tsas, 0, NVMF_TSAS_SIZE); @@ -407,7 +413,9 @@ static ssize_t nvmet_addr_trtype_store(struct config_item *item, nvmet_port_init_tsas_rdma(port); else if (port->disc_addr.trtype == NVMF_TRTYPE_TCP) nvmet_port_init_tsas_tcp(port, NVMF_TCP_SECTYPE_NONE); - return count; +out: + up_write(&nvmet_config_sem); + return ret; } CONFIGFS_ATTR(nvmet_, addr_trtype); diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 4955872dc1fc..62f56ba6ad29 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -393,11 +393,13 @@ void nvmet_disable_port(struct nvmet_port *port) lockdep_assert_held(&nvmet_config_sem); - port->enabled = false; - port->tr_ops = NULL; + if (!port->enabled) + return; - ops = nvmet_transports[port->disc_addr.trtype]; + ops = port->tr_ops; + port->enabled = false; ops->remove_port(port); + port->tr_ops = NULL; module_put(ops->owner); } From 557114b52c1d73f45c603337ac3465bba6667e34 Mon Sep 17 00:00:00 2001 From: Christian Aurich Zanettini Martins Date: Sat, 24 Oct 2026 18:37:37 -0300 Subject: [PATCH 21/44] nvme-rdma: fix NULL metadata_sgl dereference with T10-PI Since commit 2a8513091d2f ("nvme-rdma: parallelize I/O queue allocation and startup") the I/O tag set is allocated before the I/O queues. nvme_rdma_init_request() only sets req->metadata_sgl if queue->pi_support is set, but pi_support is only set later by nvme_rdma_alloc_queue(), so every I/O request is left with a NULL metadata_sgl. With an RDMA device that supports integrity and a namespace formatted with protection information, nvme_rdma_dma_map_req() then dereferences the NULL pointer on the first integrity I/O, already during the namespace scan when there is more than one I/O queue. Queues that a later reconnect adds for the first time have the same problem. Check queue_idx and the controller's max_integrity_segments instead, which is what nvme_rdma_alloc_queue() derives pi_support from. Fixes: 2a8513091d2f ("nvme-rdma: parallelize I/O queue allocation and startup") Signed-off-by: Christian Aurich Zanettini Martins Signed-off-by: Keith Busch --- drivers/nvme/host/rdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c index 5b9b540693b5..07216890f75c 100644 --- a/drivers/nvme/host/rdma.c +++ b/drivers/nvme/host/rdma.c @@ -313,7 +313,7 @@ static int nvme_rdma_init_request(struct blk_mq_tag_set *set, return -ENOMEM; /* metadata nvme_rdma_sgl struct is located after command's data SGL */ - if (queue->pi_support) + if (queue_idx && ctrl->ctrl.max_integrity_segments) req->metadata_sgl = (void *)nvme_req(rq) + sizeof(struct nvme_rdma_request) + NVME_RDMA_DATA_SGL_SIZE; From 96da408bb4a24bf87b366b13f41c2687d3c05671 Mon Sep 17 00:00:00 2001 From: Shao Mingyin Date: Mon, 26 Oct 2026 16:45:26 +0800 Subject: [PATCH 22/44] nvmet-auth: fix error handling on CHALLENGE failure In nvmet_execute_auth_receive(), when nvmet_auth_challenge() fails (for example when the initiator-supplied allocation length is shorter than the DH-HMAC-CHAP challenge response, or the challenge nonce allocation fails), the error path sets status = NVME_SC_INTERNAL and breaks out of the switch. The unconditional status = nvmet_copy_to_sgl(req, 0, d, al); after the switch then overwrites the error status (nvmet_copy_to_sgl() returns 0 on success), so the AUTH_RECEIVE command incorrectly completes with SUCCESS and the initiator proceeds with a truncated/empty challenge. Fix this by freeing the buffer and jumping to the common done: label on CHALLENGE failure, skipping the copy_to_sgl() entirely and preserving the NVME_SC_INTERNAL status. Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication") Cc: stable@vger.kernel.org Signed-off-by: Shao Mingyin Signed-off-by: Keith Busch --- drivers/nvme/target/fabrics-cmd-auth.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c index ff6b73cfe0f9..bc49b48cdd63 100644 --- a/drivers/nvme/target/fabrics-cmd-auth.c +++ b/drivers/nvme/target/fabrics-cmd-auth.c @@ -605,10 +605,11 @@ void nvmet_execute_auth_receive(struct nvmet_req *req) switch (req->sq->dhchap_step) { case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE: if (nvmet_auth_challenge(req, d, al) < 0) { - pr_warn("ctrl %d qid %d: challenge error (%d)\n", - ctrl->cntlid, req->sq->qid, status); + pr_warn("ctrl %d qid %d: challenge error\n", + ctrl->cntlid, req->sq->qid); status = NVME_SC_INTERNAL; - break; + kfree(d); + goto done; } req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_REPLY; break; From 11db8ab55e1a7533e9cfe57687ab142ba67d3c84 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Mon, 26 Oct 2026 18:44:56 +0800 Subject: [PATCH 23/44] nvmet-fc: Tear down the association when the admin queue allocation fails When the initial admin queue allocation fails after a new association was created, nvmet_fc_ls_create_association() only drops the association reference, which frees the association. By then the association is already on the target port's assoc_list, though, and removal from that list only happens in nvmet_fc_delete_target_assoc(), which this error path never calls. The freed association stays on the list, where later lookups and the port teardown can access it, and the target port reference taken at allocation time leaks. A remote host can trigger this reliably with a Create Association LS requesting a queue too large to allocate. Schedule the regular association teardown instead of dropping the reference directly. The teardown removes the association from the list and releases the target port reference before the final reference drop frees it. Fixes: 0cab4404874f ("nvme-fc: fix a missing queue put in nvmet_fc_ls_create_association") Cc: stable@vger.kernel.org Reviewed-by: Daniel Wagner Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/fc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/target/fc.c b/drivers/nvme/target/fc.c index a63ffb436a92..2b7dc3f03912 100644 --- a/drivers/nvme/target/fc.c +++ b/drivers/nvme/target/fc.c @@ -1708,7 +1708,7 @@ nvmet_fc_ls_create_association(struct nvmet_fc_tgtport *tgtport, be16_to_cpu(rqst->assoc_cmd.sqsize)); if (!queue) { ret = VERR_QUEUE_ALLOC_FAIL; - nvmet_fc_tgt_a_put(iod->assoc); + nvmet_fc_schedule_delete_assoc(iod->assoc); } } } From 288cfa187208cc9d0809eb8e1508658151a444dd Mon Sep 17 00:00:00 2001 From: Martin Belanger Date: Tue, 27 Oct 2026 06:38:52 -0400 Subject: [PATCH 24/44] nvme-tcp: start error recovery when read_sock fails nvme_tcp_recv_skb() disables the queue and starts error recovery for errors it detects itself. An error returned by the transport reaches nvme_tcp_try_recv() without either action. nvme_tcp_io_work() returns on a negative result and does not requeue itself, so the queue stops receiving and remains stalled until a command times out. Report the error and start error recovery when the queue is still enabled, which indicates that the error was not handled by the actor. Do this under the socket lock, as nvme_tcp_recv_skb() does. Fixes: be8e82caa685 ("nvme-tcp: enable TLS handshake upcall") Cc: stable@vger.kernel.org Assisted-by: LLM Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Martin Belanger Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index f876375dffd2..c401dc0206c5 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -1430,6 +1430,19 @@ static int nvme_tcp_try_recv(struct nvme_tcp_queue *queue) lock_sock(sk); queue->nr_cqe = 0; consumed = sock->ops->read_sock(sk, &rd_desc, nvme_tcp_recv_skb); + /* + * nvme_tcp_recv_skb() handles its own errors, so a queue that is + * still enabled failed inside read_sock(). Handle it before + * release_sock() processes the backlog. + */ + if (unlikely(consumed < 0 && consumed != -EAGAIN && + queue->rd_enabled)) { + dev_err(queue->ctrl->ctrl.device, + "queue %d: receive failed: %d\n", + nvme_tcp_queue_id(queue), consumed); + queue->rd_enabled = false; + nvme_tcp_error_recovery(&queue->ctrl->ctrl); + } release_sock(sk); return consumed == -EAGAIN ? 0 : consumed; } From 12c20e2903563acfb949342967adb900310a24d4 Mon Sep 17 00:00:00 2001 From: Martin Belanger Date: Tue, 27 Oct 2026 06:38:53 -0400 Subject: [PATCH 25/44] nvme-tcp: skip TLS control records while waiting for the ICResp A peer may send a NewSessionTicket as soon as the handshake completes, ahead of the ICResp. nvme_tcp_init_connection() fails the connection on any record that is not application data, so such a target cannot be reached at all. An OpenSSL-based target does this by default. RFC 9846 section 4.7.1 requires a client that does not support session resumption to ignore a NewSessionTicket. Discard such a record and read again, up to a fixed number of records. Report an alert, and fail the connection for any other record type. The record type is now checked before the length. A control record shorter than the ICResp was reported as -ECONNRESET, which hid the real cause. Fixes: 2837966ab2a8 ("nvme-tcp: control message handling for recvmsg()") Assisted-by: LLM Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Martin Belanger Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 162 ++++++++++++++++++++++++++++++++++------ 1 file changed, 140 insertions(+), 22 deletions(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index c401dc0206c5..dbefe36b0575 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -55,6 +55,12 @@ MODULE_PARM_DESC(tls_handshake_timeout, "nvme TLS handshake timeout in seconds (default 10)"); #endif +/* read a TLS control record in chunks of this size */ +#define NVME_TCP_TLS_REC_CHUNK 256 + +/* TLS records read while waiting for the ICResp, the ICResp included */ +#define NVME_TCP_ICRESP_MAX_RECORDS 8 + static int nvme_tcp_cpu_queues[NR_CPUS]; static DEFINE_SPINLOCK(nvme_tcp_cpu_queues_lock); @@ -1551,12 +1557,143 @@ static void nvme_tcp_free_queue(struct nvme_ctrl *nctrl, int qid) #endif } +static int nvme_tcp_recv_tls_alert(struct nvme_tcp_queue *queue, + struct msghdr *msg, int len) +{ + struct sock *sk = queue->sock->sk; + u8 level, description; + + if (len < 2) { + dev_err(queue->ctrl->ctrl.device, + "queue %d: truncated TLS alert\n", + nvme_tcp_queue_id(queue)); + return -ENOTCONN; + } + + /* tls_alert_recv() reads through the iterator */ + iov_iter_revert(&msg->msg_iter, len); + tls_alert_recv(sk, msg, &level, &description); + + if (level == TLS_ALERT_LEVEL_FATAL) { + dev_err(queue->ctrl->ctrl.device, + "queue %d: fatal TLS alert %u\n", + nvme_tcp_queue_id(queue), description); + return -ENOTCONN; + } + + /* a peer shutting down normally is not worth a warning */ + if (description == TLS_ALERT_DESC_CLOSE_NOTIFY) + dev_dbg(queue->ctrl->ctrl.device, + "queue %d: TLS close_notify\n", + nvme_tcp_queue_id(queue)); + else + dev_warn(queue->ctrl->ctrl.device, + "queue %d: TLS alert %u\n", + nvme_tcp_queue_id(queue), description); + + return 0; +} + +/* + * Discard a TLS record that arrived in place of the ICResp. A peer may send + * a NewSessionTicket as soon as the handshake completes. + * + * Returns 0 when the record was discarded and the ICResp can be read, or a + * negative errno if the connection cannot continue. + */ +static int nvme_tcp_skip_tls_record(struct nvme_tcp_queue *queue, + struct msghdr *msg, u8 ctype, int len) +{ + u8 buf[NVME_TCP_TLS_REC_CHUNK]; + int ret; + + switch (ctype) { + case TLS_RECORD_TYPE_HANDSHAKE: + break; + case TLS_RECORD_TYPE_ALERT: + ret = nvme_tcp_recv_tls_alert(queue, msg, len); + return ret ? : -ENOTCONN; + default: + pr_err("queue %d: unhandled TLS record %u\n", + nvme_tcp_queue_id(queue), ctype); + return -ENOTCONN; + } + + /* the ICResp is in a later record, drain this one first */ + while (!(msg->msg_flags & MSG_EOR)) { + struct kvec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + char cbuf[CMSG_LEN(sizeof(char))] = {}; + /* kTLS fails a control record read without a cmsg buffer */ + struct msghdr drain = { + .msg_control = cbuf, + .msg_controllen = sizeof(cbuf), + }; + + ret = kernel_recvmsg(queue->sock, &drain, &iov, 1, sizeof(buf), + 0); + if (ret <= 0) { + pr_warn("queue %d: failed to drain TLS record: %d\n", + nvme_tcp_queue_id(queue), ret); + return ret ? : -ECONNRESET; + } + msg->msg_flags = drain.msg_flags; + } + + return 0; +} + +/* + * Receive the ICResp. Over TLS, skip the records that a peer may send + * ahead of it. + * + * Returns the length received, or a negative errno. + */ +static int nvme_tcp_recv_icresp(struct nvme_tcp_queue *queue, + struct nvme_tcp_icresp_pdu *icresp) +{ + char cbuf[CMSG_LEN(sizeof(char))]; + struct msghdr msg; + struct kvec iov; + u8 ctype; + int i, ret; + + for (i = 0; i < NVME_TCP_ICRESP_MAX_RECORDS; i++) { + memset(&msg, 0, sizeof(msg)); + memset(cbuf, 0, sizeof(cbuf)); + iov.iov_base = icresp; + iov.iov_len = sizeof(*icresp); + if (nvme_tcp_queue_tls(queue)) { + msg.msg_control = cbuf; + msg.msg_controllen = sizeof(cbuf); + } + msg.msg_flags = MSG_WAITALL; + ret = kernel_recvmsg(queue->sock, &msg, &iov, 1, + iov.iov_len, msg.msg_flags); + if (ret <= 0 || !nvme_tcp_queue_tls(queue)) + return ret; + + ctype = tls_get_record_type(queue->sock->sk, + (struct cmsghdr *)cbuf); + if (ctype == TLS_RECORD_TYPE_DATA) + return ret; + + ret = nvme_tcp_skip_tls_record(queue, &msg, ctype, ret); + if (ret < 0) + return ret; + } + + pr_err("queue %d: too many TLS records before the ICResp\n", + nvme_tcp_queue_id(queue)); + return -EPROTO; +} + static int nvme_tcp_init_connection(struct nvme_tcp_queue *queue) { struct nvme_tcp_icreq_pdu *icreq; struct nvme_tcp_icresp_pdu *icresp; - char cbuf[CMSG_LEN(sizeof(char))] = {}; - u8 ctype; struct msghdr msg = {}; struct kvec iov; bool ctrl_hdgst, ctrl_ddgst; @@ -1594,16 +1731,7 @@ static int nvme_tcp_init_connection(struct nvme_tcp_queue *queue) goto free_icresp; } - memset(&msg, 0, sizeof(msg)); - iov.iov_base = icresp; - iov.iov_len = sizeof(*icresp); - if (nvme_tcp_queue_tls(queue)) { - msg.msg_control = cbuf; - msg.msg_controllen = sizeof(cbuf); - } - msg.msg_flags = MSG_WAITALL; - ret = kernel_recvmsg(queue->sock, &msg, &iov, 1, - iov.iov_len, msg.msg_flags); + ret = nvme_tcp_recv_icresp(queue, icresp); if (ret >= 0 && ret < sizeof(*icresp)) ret = -ECONNRESET; if (ret < 0) { @@ -1611,16 +1739,6 @@ static int nvme_tcp_init_connection(struct nvme_tcp_queue *queue) nvme_tcp_queue_id(queue), ret); goto free_icresp; } - ret = -ENOTCONN; - if (nvme_tcp_queue_tls(queue)) { - ctype = tls_get_record_type(queue->sock->sk, - (struct cmsghdr *)cbuf); - if (ctype != TLS_RECORD_TYPE_DATA) { - pr_err("queue %d: unhandled TLS record %d\n", - nvme_tcp_queue_id(queue), ctype); - goto free_icresp; - } - } ret = -EINVAL; if (icresp->hdr.type != nvme_tcp_icresp) { pr_err("queue %d: bad type returned %d\n", From cf54a0dff38435bb3b7652872717cd02fdbac0ca Mon Sep 17 00:00:00 2001 From: Christian Aurich Zanettini Martins Date: Tue, 27 Oct 2026 16:47:10 -0300 Subject: [PATCH 26/44] nvme-tcp: fix request PDU size when header digest is enabled Since commit 61b0ed3be81b ("nvme-tcp: parallelize I/O queue allocation and startup") the I/O tag set is allocated before the I/O queues. nvme_tcp_init_request() sizes the PDU with nvme_tcp_hdgst_len(queue), but queue->hdr_digest is only set later by nvme_tcp_alloc_queue(), so every I/O PDU is allocated without room for the header digest. With hdr_digest enabled, the digest is then written 4 bytes past the PDU, over the header of the next PDU in the page_frag cache, and the target drops the connection on the first I/O: nvmet_tcp: unexpected pdu type 159 nvmet: ctrl 1 fatal error occurred! Queues that a later reconnect adds for the first time have the same problem. Set queue->hdr_digest when allocating the controller, together with pf_cache_lock, so it is valid before any request is initialized. Fixes: 61b0ed3be81b ("nvme-tcp: parallelize I/O queue allocation and startup") Signed-off-by: Christian Aurich Zanettini Martins Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index dbefe36b0575..191e5811d25e 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -2079,7 +2079,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, } } - queue->hdr_digest = nctrl->opts->hdr_digest; queue->data_digest = nctrl->opts->data_digest; rcv_pdu_size = sizeof(struct nvme_tcp_rsp_pdu) + @@ -3229,10 +3228,13 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev, /* * The I/O tag set is allocated before the I/O queues, and initializing * its requests already allocates the PDUs from the page frag caches of - * the queues, so initialize their locks here. + * the queues, sized by their header digest setting, so set up the + * locks and hdr_digest here. */ - for (i = 0; i < ctrl->ctrl.queue_count; i++) + for (i = 0; i < ctrl->ctrl.queue_count; i++) { mutex_init(&ctrl->queues[i].pf_cache_lock); + ctrl->queues[i].hdr_digest = opts->hdr_digest; + } ctrl->net = get_net(current->nsproxy->net_ns); From 5db358f3a7f9c33717622c1597d15c4c91997b19 Mon Sep 17 00:00:00 2001 From: Seiji Nishikawa Date: Wed, 14 Oct 2026 15:45:24 +0900 Subject: [PATCH 27/44] nvme-tcp: fix io_work priority inversion when send_mutex is held Commit f42d4796ee10 ("nvme-tcp: fix I/O stalls on congested sockets") added a check in nvme_tcp_io_work() that sets pending to true when the queue has pending requests and the socket is writeable. The check is placed after the mutex_trylock() block, so it also runs when mutex_trylock(&queue->send_mutex) failed and io_work did not send anything. When .queue_rq sends a request directly from its own context, it holds send_mutex and can give up the CPU in lock_sock_nested() when another task needs the CPU, for example the io_work kworker of the same queue, which runs on the same CPU. Then io_work cannot take send_mutex, but the requests are still on req_list and the socket is writeable, so pending is set to true. io_work loops until the deadline and queues itself again on io_cpu, without sleeping. This is a priority inversion. io_work runs on the high priority nvme_tcp_wq and cannot make progress until send_mutex is released, but it keeps running. The task holding send_mutex runs at normal priority and needs the CPU to release it. The scheduler keeps giving the CPU to io_work, so the task holding send_mutex may not run again, and the requests on the queue are not sent and time out. This is the same problem that commit 70f437fb4395 ("nvme-tcp: fix io_work priority inversion") fixed. That commit removed "pending = !llist_empty(&queue->req_list)" from the path where mutex_trylock() fails, and let the task holding send_mutex queue io_work again in nvme_tcp_queue_request() after it releases the mutex. The new check brings back the same behavior in a different form. Two things make this easier to hit. nvme_tcp_queue_request() sends directly only when it runs on io_cpu, so the task that takes send_mutex is on the same CPU as the io_work kworker of that queue when it takes the mutex. Also, when the tasks are in a cgroup with the cpu controller enabled, the whole group competes with the nice -20 kworker as one entity, so each task in the group gets even less CPU time. We hit this on a 6 CPU VM running a database on nvme-tcp storage. In the vmcore, 2 tasks held send_mutex of 2 queues and were runnable in lock_sock_nested() for 103 seconds, while the kworkers for those queues kept running io_work on their CPUs. All database tasks were in one cgroup, which got only about 0.3% of the CPU against the nice -20 kworker. Requests timed out, and blk_mq_timeout_work() waited in synchronize_srcu() for the tasks still in .queue_rq. Only do the pending check if io_work could take send_mutex. If it could not, the task holding send_mutex sends what it can in nvme_tcp_send_all(), and queues io_work again after it releases the mutex if requests are still pending, so no request is left behind. Fixes: f42d4796ee10 ("nvme-tcp: fix I/O stalls on congested sockets") Cc: stable@vger.kernel.org # v6.16+ Signed-off-by: Seiji Nishikawa Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 191e5811d25e..1f7a2de95419 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -1461,9 +1461,11 @@ static void nvme_tcp_io_work(struct work_struct *w) do { bool pending = false; + bool got_send_mutex = false; int result; if (mutex_trylock(&queue->send_mutex)) { + got_send_mutex = true; result = nvme_tcp_try_send(queue); mutex_unlock(&queue->send_mutex); if (result > 0) @@ -1478,8 +1480,13 @@ static void nvme_tcp_io_work(struct work_struct *w) else if (unlikely(result < 0)) return; - /* did we get some space after spending time in recv? */ - if (nvme_tcp_queue_has_pending(queue) && + /* + * did we get some space after spending time in recv? + * Only check this if we could take send_mutex. Otherwise + * the task holding it sends the requests and queues io_work + * again after it releases the mutex. + */ + if (got_send_mutex && nvme_tcp_queue_has_pending(queue) && sk_stream_is_writeable(queue->sock->sk)) pending = true; From 2eeeaa9c1ff0c429096acc2947c296db6a56f96d Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Wed, 28 Oct 2026 10:09:04 +0800 Subject: [PATCH 28/44] nvme-tcp: Validate the queue state under the queue lock in the address attribute Reading the "address" sysfs attribute calls kernel_getsockname() on the admin queue socket, which can race with the queue teardown during connection error recovery: a reader can observe NVME_TCP_Q_LIVE set, then block on the queue lock while nvme_tcp_free_queue() releases the socket and clears queue->sock without holding the lock. Once the reader acquires the lock it dereferences the NULL socket pointer. Commit 76d54bf20cdc ("nvme-tcp: don't access released socket during error recovery") had the queue liveness check inside the lock. Commit 782373ba2766 ("nvme: tcp: avoid race between queue_lock lock and destroy") moved it back out because the lock itself is destroyed in nvme_tcp_free_queue(), so a reader arriving late trips mutex_destroy(). Check the queue liveness inside the queue lock, and clear queue->sock under the same lock. The teardown clears NVME_TCP_Q_LIVE under the lock well before the socket is released, so a reader either sees the bit cleared or a socket that cannot be released while it holds the lock. To also close the lock lifetime race, the queue locks are now owned by the controller: they are initialized with the queue array and only destroyed when the controller is freed, which cannot happen while a sysfs attribute read is still in progress. The queue count is computed by the NVME_TCP_OPT_NR_QUEUES() macro from the original connect options for both initialization and destruction, as reconnects may shrink ctrl->queue_count. Fixes: 782373ba2766 ("nvme: tcp: avoid race between queue_lock lock and destroy") Cc: stable@vger.kernel.org Signed-off-by: Guixin Liu Reviewed-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 47 ++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 1f7a2de95419..39cb63687293 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -103,6 +103,9 @@ enum nvme_tcp_queue_flags { NVME_TCP_Q_IO_CPU_SET = 3, }; +#define NVME_TCP_OPT_NR_QUEUES(opts) ((opts)->nr_io_queues + \ + (opts)->nr_write_queues + (opts)->nr_poll_queues + 1) + enum nvme_tcp_recv_state { NVME_TCP_RECV_PDU = 0, NVME_TCP_RECV_DATA, @@ -1549,14 +1552,21 @@ static void nvme_tcp_free_queue(struct nvme_ctrl *nctrl, int qid) * nvme_reset_ctrl_work(), queue teardown is immediately followed by * re-allocation. fput() defers socket cleanup to delayed_fput_work * in workqueue context, which can race with new queue setup. + * + * Clear queue->sock under the queue lock so that a sysfs reader in + * nvme_tcp_get_address() either observes the NVME_TCP_Q_LIVE bit + * cleared or a still valid socket. The queue lock itself is owned + * by the controller and only destroyed when the controller is + * freed, so readers blocked on it cannot race its teardown. */ + mutex_lock(&queue->queue_lock); __fput_sync(queue->sock->file); queue->sock = NULL; + mutex_unlock(&queue->queue_lock); memalloc_noio_restore(noio_flag); kfree(queue->pdu); mutex_destroy(&queue->send_mutex); - mutex_destroy(&queue->queue_lock); #ifdef CONFIG_DEBUG_LOCK_ALLOC lockdep_unregister_key(&queue->nvme_tcp_sk_key); @@ -1992,7 +2002,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, int ret, rcv_pdu_size; struct file *sock_file; - mutex_init(&queue->queue_lock); queue->ctrl = ctrl; init_llist_head(&queue->req_list); INIT_LIST_HEAD(&queue->send_list); @@ -2136,7 +2145,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, #endif err_destroy_mutex: mutex_destroy(&queue->send_mutex); - mutex_destroy(&queue->queue_lock); return ret; } @@ -2823,6 +2831,9 @@ static void nvme_tcp_stop_ctrl(struct nvme_ctrl *ctrl) static void nvme_tcp_free_ctrl(struct nvme_ctrl *nctrl) { struct nvme_tcp_ctrl *ctrl = to_tcp_ctrl(nctrl); + /* the same count used to size and initialize the queue array */ + unsigned int nr_queues = NVME_TCP_OPT_NR_QUEUES(nctrl->opts); + unsigned int i; if (list_empty_careful(&ctrl->list)) goto free_ctrl; @@ -2834,6 +2845,8 @@ static void nvme_tcp_free_ctrl(struct nvme_ctrl *nctrl) nvmf_free_options(nctrl->opts); free_ctrl: put_net(ctrl->net); + for (i = 0; i < nr_queues; i++) + mutex_destroy(&ctrl->queues[i].queue_lock); kfree(ctrl->queues); kfree(ctrl); } @@ -3086,17 +3099,18 @@ static int nvme_tcp_get_address(struct nvme_ctrl *ctrl, char *buf, int size) len = nvmf_get_address(ctrl, buf, size); - if (!test_bit(NVME_TCP_Q_LIVE, &queue->flags)) - return len; - mutex_lock(&queue->queue_lock); - ret = kernel_getsockname(queue->sock, (struct sockaddr *)&src_addr); - if (ret > 0) { - if (len > 0) - len--; /* strip trailing newline */ - len += scnprintf(buf + len, size - len, "%ssrc_addr=%pISc\n", - (len) ? "," : "", &src_addr); + if (test_bit(NVME_TCP_Q_LIVE, &queue->flags)) { + ret = kernel_getsockname(queue->sock, + (struct sockaddr *)&src_addr); + if (ret > 0) { + if (len > 0) + len--; /* strip trailing newline */ + len += scnprintf(buf + len, size - len, + "%ssrc_addr=%pISc\n", + (len) ? "," : "", &src_addr); + } } mutex_unlock(&queue->queue_lock); @@ -3173,8 +3187,7 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev, */ context_unsafe(INIT_LIST_HEAD(&ctrl->list)); ctrl->ctrl.opts = opts; - ctrl->ctrl.queue_count = opts->nr_io_queues + opts->nr_write_queues + - opts->nr_poll_queues + 1; + ctrl->ctrl.queue_count = NVME_TCP_OPT_NR_QUEUES(opts); ctrl->ctrl.sqsize = opts->queue_size - 1; ctrl->ctrl.kato = opts->kato; @@ -3237,14 +3250,18 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev, * its requests already allocates the PDUs from the page frag caches of * the queues, sized by their header digest setting, so set up the * locks and hdr_digest here. + * + * The queue locks are owned by the controller, not the queues: + * they outlive the per-queue connect/disconnect cycles so that + * sysfs readers can never race their destruction. */ for (i = 0; i < ctrl->ctrl.queue_count; i++) { mutex_init(&ctrl->queues[i].pf_cache_lock); ctrl->queues[i].hdr_digest = opts->hdr_digest; + mutex_init(&ctrl->queues[i].queue_lock); } ctrl->net = get_net(current->nsproxy->net_ns); - ret = nvme_init_ctrl(&ctrl->ctrl, dev, &nvme_tcp_ctrl_ops, 0); if (ret) goto out_kfree_queues; From e35557e41ab1c254061eb5c5f57a2aaee1fe69c4 Mon Sep 17 00:00:00 2001 From: Christian Aurich Zanettini Martins Date: Wed, 28 Oct 2026 11:33:43 -0300 Subject: [PATCH 29/44] nvme-tcp: fix NULL queue->ctrl dereference on reconnect Since commit 61b0ed3be81b ("nvme-tcp: parallelize I/O queue allocation and startup"), a reconnect that gets more I/O queues than before grows the tag set before the new queues are allocated. queue->ctrl is only set by nvme_tcp_alloc_queue(), so I/O submitted in between reaches nvme_tcp_queue_rq() on a queue that was never allocated and dereferences a NULL queue->ctrl: BUG: kernel NULL pointer dereference, address: 00000000000002ac RIP: 0010:nvme_tcp_queue_rq+0x33/0x3b0 Add nvme_tcp_init_queue() for the queue fields that do not depend on the connection and call it for every queue when allocating the controller. Such requests are then requeued like on any other queue that is not live. send_mutex and io_work move there as well, and nvme_tcp_exit_queue() destroys the helper's mutexes when the controller is freed. Fixes: 61b0ed3be81b ("nvme-tcp: parallelize I/O queue allocation and startup") Suggested-by: Keith Busch Reviewed-by: Nilay Shroff Reviewed-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Signed-off-by: Christian Aurich Zanettini Martins [kbusch: dropped the comment above nvme_tcp_init_queue()] Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 57 ++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 39cb63687293..e69e6535e1a1 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -1566,7 +1566,6 @@ static void nvme_tcp_free_queue(struct nvme_ctrl *nctrl, int qid) memalloc_noio_restore(noio_flag); kfree(queue->pdu); - mutex_destroy(&queue->send_mutex); #ifdef CONFIG_DEBUG_LOCK_ALLOC lockdep_unregister_key(&queue->nvme_tcp_sk_key); @@ -1994,6 +1993,28 @@ static int nvme_tcp_start_tls(struct nvme_ctrl *nctrl, return ret; } +static void nvme_tcp_init_queue(struct nvme_tcp_ctrl *ctrl, int qid) +{ + struct nvme_tcp_queue *queue = &ctrl->queues[qid]; + + queue->ctrl = ctrl; + mutex_init(&queue->queue_lock); + mutex_init(&queue->send_mutex); + mutex_init(&queue->pf_cache_lock); + INIT_WORK(&queue->io_work, nvme_tcp_io_work); + queue->hdr_digest = ctrl->ctrl.opts->hdr_digest; + queue->data_digest = ctrl->ctrl.opts->data_digest; +} + +static void nvme_tcp_exit_queue(struct nvme_tcp_ctrl *ctrl, int qid) +{ + struct nvme_tcp_queue *queue = &ctrl->queues[qid]; + + mutex_destroy(&queue->queue_lock); + mutex_destroy(&queue->send_mutex); + mutex_destroy(&queue->pf_cache_lock); +} + static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, key_serial_t pskid) { @@ -2002,11 +2023,8 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, int ret, rcv_pdu_size; struct file *sock_file; - queue->ctrl = ctrl; init_llist_head(&queue->req_list); INIT_LIST_HEAD(&queue->send_list); - mutex_init(&queue->send_mutex); - INIT_WORK(&queue->io_work, nvme_tcp_io_work); if (qid > 0) queue->cmnd_capsule_len = nctrl->ioccsz * 16; @@ -2020,14 +2038,12 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, if (ret) { dev_err(nctrl->device, "failed to create socket: %d\n", ret); - goto err_destroy_mutex; + return ret; } sock_file = sock_alloc_file(queue->sock, O_CLOEXEC, NULL); - if (IS_ERR(sock_file)) { - ret = PTR_ERR(sock_file); - goto err_destroy_mutex; - } + if (IS_ERR(sock_file)) + return PTR_ERR(sock_file); sk_net_refcnt_upgrade(queue->sock->sk); @@ -2095,8 +2111,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, } } - queue->data_digest = nctrl->opts->data_digest; - rcv_pdu_size = sizeof(struct nvme_tcp_rsp_pdu) + nvme_tcp_hdgst_len(queue); queue->pdu = kmalloc(rcv_pdu_size, GFP_KERNEL); @@ -2143,8 +2157,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, lockdep_unregister_key(&queue->nvme_tcp_sk_key); lockdep_unregister_key(&queue->nvme_tcp_slock_key); #endif -err_destroy_mutex: - mutex_destroy(&queue->send_mutex); return ret; } @@ -2846,7 +2858,7 @@ static void nvme_tcp_free_ctrl(struct nvme_ctrl *nctrl) free_ctrl: put_net(ctrl->net); for (i = 0; i < nr_queues; i++) - mutex_destroy(&ctrl->queues[i].queue_lock); + nvme_tcp_exit_queue(ctrl, i); kfree(ctrl->queues); kfree(ctrl); } @@ -3245,21 +3257,8 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev, goto out_free_ctrl; } - /* - * The I/O tag set is allocated before the I/O queues, and initializing - * its requests already allocates the PDUs from the page frag caches of - * the queues, sized by their header digest setting, so set up the - * locks and hdr_digest here. - * - * The queue locks are owned by the controller, not the queues: - * they outlive the per-queue connect/disconnect cycles so that - * sysfs readers can never race their destruction. - */ - for (i = 0; i < ctrl->ctrl.queue_count; i++) { - mutex_init(&ctrl->queues[i].pf_cache_lock); - ctrl->queues[i].hdr_digest = opts->hdr_digest; - mutex_init(&ctrl->queues[i].queue_lock); - } + for (i = 0; i < ctrl->ctrl.queue_count; i++) + nvme_tcp_init_queue(ctrl, i); ctrl->net = get_net(current->nsproxy->net_ns); ret = nvme_init_ctrl(&ctrl->ctrl, dev, &nvme_tcp_ctrl_ops, 0); From 18fc5f6f1ca945630dae5404265de55d65194e22 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Wed, 28 Oct 2026 14:44:36 +0800 Subject: [PATCH 30/44] nvmet: Finish the blk plug when integrity allocation fails nvmet_bdev_execute_rw() starts a plug before building the bios, but the two error exits taken when nvmet_bdev_alloc_bip() fails return without calling blk_finish_plug(). current->plug is left pointing at the stack frame of the returned function, so the next blk_flush_plug() walks a stale list. Route both failures through a common exit that finishes the plug. Fixes: c6e3f13398123 ("nvmet: add metadata support for block devices") Cc: stable@vger.kernel.org Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Reviewed-by: Nilay Shroff Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/io-cmd-bdev.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/target/io-cmd-bdev.c b/drivers/nvme/target/io-cmd-bdev.c index f2d9e8901df4..4b7483bc9cc3 100644 --- a/drivers/nvme/target/io-cmd-bdev.c +++ b/drivers/nvme/target/io-cmd-bdev.c @@ -316,7 +316,7 @@ static void nvmet_bdev_execute_rw(struct nvmet_req *req) &prot_miter); if (unlikely(rc)) { bio_io_error(bio); - return; + goto out_plug; } } @@ -336,11 +336,12 @@ static void nvmet_bdev_execute_rw(struct nvmet_req *req) rc = nvmet_bdev_alloc_bip(req, bio, &prot_miter); if (unlikely(rc)) { bio_io_error(bio); - return; + goto out_plug; } } submit_bio(bio); +out_plug: blk_finish_plug(&plug); } From 5c279fcabb0ea7ba61881c13027b15d8054b8abd Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Wed, 28 Oct 2026 16:55:51 +0800 Subject: [PATCH 31/44] nvmet: Free the old negotiation state when restarting authentication Restarting a DH-HMAC-CHAP negotiation with SEND(NEGOTIATE) resets sq->dhchap_step but does not release the previous c1/c2/skey, which are only freed in nvmet_auth_sq_free() at teardown. The next CHALLENGE/REPLY exchange then overwrites those pointers with fresh allocations. An unauthenticated host can repeat the negotiate message endlessly, leaking a few dozen bytes per round and slowly exhausting target memory. Clear the old negotiation state with nvmet_auth_sq_free() before setting up the new one. Also reset dhchap_skey_len to zero: if a DH session key was already established, a following round with dhvlen=0 would otherwise pass a NULL skey with a stale non-zero length to the HMAC code and dereference it. Fixes: db1312dd95488 ("nvmet: implement basic In-Band Authentication") Cc: stable@vger.kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/auth.c | 1 + drivers/nvme/target/fabrics-cmd-auth.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/nvme/target/auth.c b/drivers/nvme/target/auth.c index 1680e5e6839c..574025730834 100644 --- a/drivers/nvme/target/auth.c +++ b/drivers/nvme/target/auth.c @@ -237,6 +237,7 @@ void nvmet_auth_sq_free(struct nvmet_sq *sq) sq->dhchap_c2 = NULL; kfree(sq->dhchap_skey); sq->dhchap_skey = NULL; + sq->dhchap_skey_len = 0; } void nvmet_auth_sq_destroy(struct nvmet_sq *sq) diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c index bc49b48cdd63..3d9840d37a8b 100644 --- a/drivers/nvme/target/fabrics-cmd-auth.c +++ b/drivers/nvme/target/fabrics-cmd-auth.c @@ -330,6 +330,7 @@ void nvmet_execute_auth_send(struct nvmet_req *req) /* Restart negotiation */ pr_debug("%s: ctrl %d qid %d reset negotiation\n", __func__, ctrl->cntlid, req->sq->qid); + nvmet_auth_sq_free(req->sq); if (!req->sq->qid) { dhchap_status = nvmet_setup_auth(ctrl, req->sq, true); From e3e7a85730a4b17efa1e2ff4088443c4753be683 Mon Sep 17 00:00:00 2001 From: Yingjie Wang <1075151112@qq.com> Date: Wed, 28 Oct 2026 19:57:19 +0800 Subject: [PATCH 32/44] nvmet-tcp: validate the TLS handshake timeout tls_handshake_timeout is writable but stored as a signed int. Multiplying the value by MSEC_PER_SEC and HZ therefore uses signed arithmetic. With HZ=1000, setting it to 2147484 overflows both multiplications. If the handshake request is accepted and remains pending, the timer wheel treats the jiffies delay as already expired and queues the timeout work on its next advance, while the handshake agent was given about 24.9 days. Negative values such as -1 cause the same immediate kernel-side timeout. Store the parameter as an unsigned int and reject values that cannot be represented both by tls_handshake_args::ta_timeout_ms and by a safe jiffies delay. Take one snapshot so a concurrent parameter update cannot give the handshake agent and delayed work different timeouts. Document the accepted range for built-in use. Negative and oversized values now fail with -EINVAL. Keep zero valid, preserving the existing immediate-timeout behavior. Fixes: 675b453e0241 ("nvmet-tcp: enable TLS handshake upcall") Cc: stable@vger.kernel.org Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Yingjie Wang <1075151112@qq.com> Signed-off-by: Keith Busch --- .../admin-guide/kernel-parameters.txt | 5 ++++ drivers/nvme/target/tcp.c | 26 ++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 68647ff4bdd2..5bd8a24be6b6 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4801,6 +4801,11 @@ Kernel parameters Example: nvme.quirks=7710:2267:bogus_nid,^identify_cns-9900:7711:broken_msi + nvmet_tcp.tls_handshake_timeout= + [NVME] NVMe/TCP target TLS handshake timeout in seconds. + Valid values range from 0 to the smaller of + UINT_MAX / 1000 and MAX_JIFFY_OFFSET / HZ. Default: 10 + ohci1394_dma=early [HW,EARLY] enable debugging via the ohci1394 driver. See Documentation/core-api/debugging-via-ohci1394.rst for more info. diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index e59810175262..7937377436d3 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -6,6 +6,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include +#include #include #include #include @@ -76,8 +77,24 @@ MODULE_PARM_DESC(idle_poll_period_usecs, /* * TLS handshake timeout */ -static int tls_handshake_timeout = 10; -module_param(tls_handshake_timeout, int, 0644); +static unsigned int tls_handshake_timeout = 10; + +static int tls_handshake_timeout_set(const char *val, + const struct kernel_param *kp) +{ + return param_set_uint_minmax(val, kp, 0, + min_t(unsigned long, UINT_MAX / MSEC_PER_SEC, + MAX_JIFFY_OFFSET / HZ)); +} + +static const struct kernel_param_ops tls_handshake_timeout_ops = { + .set = tls_handshake_timeout_set, + .get = param_get_uint, +}; + +module_param_cb(tls_handshake_timeout, &tls_handshake_timeout_ops, + &tls_handshake_timeout, 0644); +__MODULE_PARM_TYPE(tls_handshake_timeout, "uint"); MODULE_PARM_DESC(tls_handshake_timeout, "nvme TLS handshake timeout in seconds (default 10)"); #endif @@ -1904,6 +1921,7 @@ static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue) { int ret = -EOPNOTSUPP; struct tls_handshake_args args; + unsigned int timeout = READ_ONCE(tls_handshake_timeout); if (queue->state != NVMET_TCP_Q_TLS_HANDSHAKE) { pr_warn("cannot start TLS in state %d\n", queue->state); @@ -1917,7 +1935,7 @@ static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue) args.ta_done = nvmet_tcp_tls_handshake_done; args.ta_data = queue; args.ta_keyring = key_serial(queue->port->nport->keyring); - args.ta_timeout_ms = tls_handshake_timeout * 1000; + args.ta_timeout_ms = timeout * MSEC_PER_SEC; ret = tls_server_hello_psk(&args, GFP_KERNEL); if (ret) { @@ -1925,7 +1943,7 @@ static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue) pr_err("failed to start TLS, err=%d\n", ret); } else { queue_delayed_work(nvmet_wq, &queue->tls_handshake_tmo_work, - tls_handshake_timeout * HZ); + (unsigned long)timeout * HZ); } return ret; } From 799203afd298b1f8e9ee49815eb38f775f211299 Mon Sep 17 00:00:00 2001 From: Yingjie Wang <1075151112@qq.com> Date: Wed, 28 Oct 2026 19:57:20 +0800 Subject: [PATCH 33/44] nvme-tcp: validate the TLS handshake timeout tls_handshake_timeout is writable but stored as a signed int. Multiplying the value by MSEC_PER_SEC and HZ therefore uses signed arithmetic. With HZ=1000, setting it to 2147484 overflows both multiplications. If the handshake request is accepted and its completion remains pending, schedule_timeout() reports a wrong timeout value with a stack dump and returns zero. The handshake then fails immediately with -ETIMEDOUT, while the handshake agent was given about 24.9 days. Negative values such as -1 have the same kernel-side effect. Store the parameter as an unsigned int and reject values that cannot be represented both by tls_handshake_args::ta_timeout_ms and by a safe jiffies delay. Take one snapshot so a concurrent parameter update cannot give the handshake agent and completion wait different timeouts. Document the accepted range for built-in use. wait_for_completion_interruptible_timeout() returns the remaining jiffies as a long after a successful completion. Keep that result in a long because an accepted timeout can exceed INT_MAX jiffies on 64-bit systems. Negative and oversized values now fail with -EINVAL. Keep zero valid, preserving the existing immediate-timeout behavior. Fixes: be8e82caa685 ("nvme-tcp: enable TLS handshake upcall") Cc: stable@vger.kernel.org Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Yingjie Wang <1075151112@qq.com> Signed-off-by: Keith Busch --- .../admin-guide/kernel-parameters.txt | 5 +++ drivers/nvme/host/tcp.c | 34 ++++++++++++++----- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 5bd8a24be6b6..187917a4f196 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4801,6 +4801,11 @@ Kernel parameters Example: nvme.quirks=7710:2267:bogus_nid,^identify_cns-9900:7711:broken_msi + nvme_tcp.tls_handshake_timeout= + [NVME] NVMe/TCP host TLS handshake timeout in seconds. + Valid values range from 0 to the smaller of + UINT_MAX / 1000 and MAX_JIFFY_OFFSET / HZ. Default: 10 + nvmet_tcp.tls_handshake_timeout= [NVME] NVMe/TCP target TLS handshake timeout in seconds. Valid values range from 0 to the smaller of diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index e69e6535e1a1..a8e94e4866db 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -6,6 +6,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include +#include #include #include #include @@ -48,9 +49,24 @@ MODULE_PARM_DESC(wq_unbound, "Use unbound workqueue for nvme-tcp IO context (def /* * TLS handshake timeout */ -static int tls_handshake_timeout = 10; +static unsigned int tls_handshake_timeout = 10; #ifdef CONFIG_NVME_TCP_TLS -module_param(tls_handshake_timeout, int, 0644); +static int tls_handshake_timeout_set(const char *val, + const struct kernel_param *kp) +{ + return param_set_uint_minmax(val, kp, 0, + min_t(unsigned long, UINT_MAX / MSEC_PER_SEC, + MAX_JIFFY_OFFSET / HZ)); +} + +static const struct kernel_param_ops tls_handshake_timeout_ops = { + .set = tls_handshake_timeout_set, + .get = param_get_uint, +}; + +module_param_cb(tls_handshake_timeout, &tls_handshake_timeout_ops, + &tls_handshake_timeout, 0644); +__MODULE_PARM_TYPE(tls_handshake_timeout, "uint"); MODULE_PARM_DESC(tls_handshake_timeout, "nvme TLS handshake timeout in seconds (default 10)"); #endif @@ -1946,8 +1962,10 @@ static int nvme_tcp_start_tls(struct nvme_ctrl *nctrl, { int qid = nvme_tcp_queue_id(queue); int ret; + long wait_ret; struct tls_handshake_args args; - unsigned long tmo = tls_handshake_timeout * HZ; + unsigned int timeout = READ_ONCE(tls_handshake_timeout); + unsigned long tmo = (unsigned long)timeout * HZ; key_serial_t keyring = nvme_keyring_id(); dev_dbg(nctrl->device, "queue %d: start TLS with key %x\n", @@ -1961,7 +1979,7 @@ static int nvme_tcp_start_tls(struct nvme_ctrl *nctrl, if (nctrl->opts->keyring) keyring = key_serial(nctrl->opts->keyring); args.ta_keyring = keyring; - args.ta_timeout_ms = tls_handshake_timeout * 1000; + args.ta_timeout_ms = timeout * MSEC_PER_SEC; queue->tls_err = -EOPNOTSUPP; init_completion(&queue->tls_complete); ret = tls_client_hello_psk(&args, GFP_KERNEL); @@ -1970,10 +1988,10 @@ static int nvme_tcp_start_tls(struct nvme_ctrl *nctrl, qid, ret); return ret; } - ret = wait_for_completion_interruptible_timeout(&queue->tls_complete, tmo); - if (ret <= 0) { - if (ret == 0) - ret = -ETIMEDOUT; + wait_ret = wait_for_completion_interruptible_timeout(&queue->tls_complete, + tmo); + if (wait_ret <= 0) { + ret = wait_ret < 0 ? -ERESTARTSYS : -ETIMEDOUT; dev_err(nctrl->device, "queue %d: TLS handshake failed, error %d\n", From 424bd0e98ca4f227d424c3543166a9982ca45819 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 28 Oct 2026 10:41:48 -0600 Subject: [PATCH 34/44] nvmet: don't truncate the length of big discards nvmet_bdev_execute_discard() shifts the 32-bit nlb of a DSM range into sectors before widening it to sector_t, so on a 4k namespace the top bits are lost. A 3 TiB range discards 1 TiB, a 2 TiB range discards nothing, and both complete with success. The target announces no DMRSL, and hosts other than Linux (and nvme dsm passthrough) do send ranges that big. Widen before the shift. Fixes: a07b4970f464 ("nvmet: add a generic NVMe target") Cc: Christoph Hellwig Cc: Sagi Grimberg Cc: Chaitanya Kulkarni Signed-off-by: Josef Bacik Signed-off-by: Jens Axboe Signed-off-by: Keith Busch --- drivers/nvme/target/io-cmd-bdev.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/io-cmd-bdev.c b/drivers/nvme/target/io-cmd-bdev.c index 4b7483bc9cc3..72618dced191 100644 --- a/drivers/nvme/target/io-cmd-bdev.c +++ b/drivers/nvme/target/io-cmd-bdev.c @@ -390,7 +390,8 @@ static void nvmet_bdev_execute_discard(struct nvmet_req *req) if (status) break; - nr_sects = le32_to_cpu(range.nlb) << (ns->blksize_shift - 9); + nr_sects = (sector_t)le32_to_cpu(range.nlb) << + (ns->blksize_shift - 9); __blkdev_issue_discard(ns->bdev, nvmet_lba_to_sect(ns, range.slba), nr_sects, GFP_KERNEL, &bio); From 7dd1d31e2e3124f331056d68af9a48c501b37e02 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 28 Oct 2026 10:42:08 -0600 Subject: [PATCH 35/44] nvmet-auth: take only AUTH_Negotiate for a negotiation nvmet_execute_auth_send() hands a common-type message to nvmet_auth_negotiate() if its ID is AUTH_Negotiate or matches the step the queue is in. The second case is wrong: the only other common-type message a host sends is AUTH_Failure2, and no queue waits in that step. A host can name the step on purpose though, and its message is then treated as a new AUTH_Negotiate without the work that goes with a new transaction, like reloading the keys on the admin queue. Fail every other common-type message, as AUTH_Failure2 already is. Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication") Cc: Hannes Reinecke Cc: Christoph Hellwig Cc: Sagi Grimberg Cc: Chaitanya Kulkarni Signed-off-by: Josef Bacik Signed-off-by: Jens Axboe Signed-off-by: Keith Busch --- drivers/nvme/target/fabrics-cmd-auth.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c index 3d9840d37a8b..e9dd2b0cd3a7 100644 --- a/drivers/nvme/target/fabrics-cmd-auth.c +++ b/drivers/nvme/target/fabrics-cmd-auth.c @@ -345,8 +345,9 @@ void nvmet_execute_auth_send(struct nvmet_req *req) } req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE; - } else if (data->auth_id != req->sq->dhchap_step) + } else { goto done_failure1; + } /* Validate negotiation parameters */ dhchap_status = nvmet_auth_negotiate(req, d, tl); if (dhchap_status == 0) From 4a5ace11b5a367270bc86097d31c35cb61890a95 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 28 Oct 2026 10:42:15 -0600 Subject: [PATCH 36/44] nvmet: fix NULL dereference of PCI endpoint targets nvmet_pci_epf_create_ctrl() calls nvmet_alloc_ctrl() without a queue, the admin queue comes later from the host. nvmet_alloc_ctrl() passes args->sq to nvmet_queue_tls_keyid(), which with CONFIG_NVME_TARGET_TCP_TLS dereferences it, so every PCI endpoint controller creation crashes on such a kernel. The debugfs tls_key file passes ctrl->sqs[0] to the same function, which is NULL until the admin queue exists and again once a fabrics host has closed it while keeping an I/O queue. A queue that isn't there has no TLS key. Fixes: fa2e0f8bbc68 ("nvmet-tcp: support secure channel concatenation") Cc: Christoph Hellwig Cc: Sagi Grimberg Cc: Chaitanya Kulkarni Signed-off-by: Josef Bacik Signed-off-by: Jens Axboe Signed-off-by: Keith Busch --- drivers/nvme/target/nvmet.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h index e315f1c24e73..bd13adc72f67 100644 --- a/drivers/nvme/target/nvmet.h +++ b/drivers/nvme/target/nvmet.h @@ -957,7 +957,8 @@ static inline void nvmet_req_bio_put(struct nvmet_req *req, struct bio *bio) #ifdef CONFIG_NVME_TARGET_TCP_TLS static inline key_serial_t nvmet_queue_tls_keyid(struct nvmet_sq *sq) { - return sq->tls_key ? key_serial(sq->tls_key) : 0; + /* A PCI endpoint controller is created without a queue */ + return sq && sq->tls_key ? key_serial(sq->tls_key) : 0; } static inline void nvmet_sq_put_tls_key(struct nvmet_sq *sq) { From ece0f4e67f08792c8572080fb0fcc231554f08ca Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 28 Oct 2026 10:42:16 -0600 Subject: [PATCH 37/44] nvmet-tcp: stop listening when a port is removed nvmet_tcp_accept_work() accepts until there's nothing left. A connection that is already dead when accepted is torn down right there, which takes a few ms, and more can arrive meanwhile. nvmet_tcp_remove_port() waits for the work with cancel_work_sync(), so someone opening and closing connections in a loop keeps the removal from finishing. It holds nvmet_config_sem for writing, so nothing can be configured and no port answers a Connect until the flood stops. No secret needed, just an admin removing the port at the wrong time. Shut the listening socket down first. Pending connections are dropped, as they were on release, and the next kernel_accept() fails and ends the work. Fixes: 872d26a391da ("nvmet-tcp: add NVMe over TCP target driver") Cc: Christoph Hellwig Cc: Sagi Grimberg Cc: Chaitanya Kulkarni Signed-off-by: Josef Bacik Signed-off-by: Jens Axboe Signed-off-by: Keith Busch --- drivers/nvme/target/tcp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index 7937377436d3..84af7d1bc588 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -2191,6 +2191,8 @@ static void nvmet_tcp_remove_port(struct nvmet_port *nport) port->sock->sk->sk_data_ready = port->data_ready; port->sock->sk->sk_user_data = NULL; write_unlock_bh(&port->sock->sk->sk_callback_lock); + /* The accept work goes on for as long as there is something to accept */ + kernel_sock_shutdown(port->sock, SHUT_RDWR); cancel_work_sync(&port->accept_work); /* * Destroy the remaining queues, which are not belong to any From 81ce4d9bbe8cf47d4b023e932ee29b22367545e6 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 28 Oct 2026 10:42:19 -0600 Subject: [PATCH 38/44] nvmet: fix NULL dereference in debugfs host_traddr The host_traddr callbacks of tcp, rdma and fc take the host address from the transport queue around ctrl->sqs[0], which nvmet_sq_destroy() clears when the admin queue goes away while the controller lives on with its I/O queues. A host that closes the admin connection and keeps an I/O queue leaves such a controller behind, and reading its host_traddr in debugfs dereferences a near-NULL pointer: BUG: kernel NULL pointer dereference, address: 00000000000002e0 RIP: 0010:nvmet_tcp_host_port_addr+0x19/0x50 [nvmet_tcp] Don't ask the transport if there's no admin queue. Nothing holds the queue for debugfs, so a reader that's past the check when the queue goes away is still on its own. Fixes: 7e5c3de3f2f2 ("nvmet: add 'host_traddr' callback for debugfs") Cc: Christoph Hellwig Cc: Sagi Grimberg Cc: Chaitanya Kulkarni Signed-off-by: Josef Bacik Signed-off-by: Jens Axboe Signed-off-by: Keith Busch --- drivers/nvme/target/core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 62f56ba6ad29..956f9a552e70 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -1821,6 +1821,9 @@ ssize_t nvmet_ctrl_host_traddr(struct nvmet_ctrl *ctrl, { if (!ctrl->ops->host_traddr) return -EOPNOTSUPP; + /* They all take the address from the admin queue */ + if (!ctrl->sqs[0]) + return -ENOTCONN; return ctrl->ops->host_traddr(ctrl, traddr, traddr_len); } From 3f98ce65a87979e881343dcf082c9595ec90ddc7 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 28 Oct 2026 10:42:21 -0600 Subject: [PATCH 39/44] nvmet-tcp: don't read a removed port at Connect time nvmet_tcp_alloc_cmd() takes the nvmet port of a command from queue->port, and the commands of a queue are allocated from the io work when the Connect arrives. nvmet_tcp_remove_port() shuts down the queues that have no controller yet and frees the port without waiting for them, they're released later from a work. An io work handling a Connect at that moment reads the freed port for every command of the queue and hands the result to the core. Note the nvmet port in the queue at allocation. It outlives the transport port. Fixes: 872d26a391da ("nvmet-tcp: add NVMe over TCP target driver") Cc: Christoph Hellwig Cc: Sagi Grimberg Cc: Chaitanya Kulkarni Signed-off-by: Josef Bacik Signed-off-by: Jens Axboe Signed-off-by: Keith Busch --- drivers/nvme/target/tcp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index 84af7d1bc588..85a10679d0a9 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -166,6 +166,7 @@ enum nvmet_tcp_queue_state { struct nvmet_tcp_queue { struct socket *sock; struct nvmet_tcp_port *port; + struct nvmet_port *nport; struct work_struct io_work; struct nvmet_cq nvme_cq; struct nvmet_sq nvme_sq; @@ -1512,7 +1513,7 @@ static int nvmet_tcp_alloc_cmd(struct nvmet_tcp_queue *queue, u8 hdgst = nvmet_tcp_hdgst_len(queue); c->queue = queue; - c->req.port = queue->port->nport; + c->req.port = queue->nport; c->cmd_pdu = page_frag_alloc(&queue->pf_cache, sizeof(*c->cmd_pdu) + hdgst, GFP_KERNEL | __GFP_ZERO); @@ -1969,6 +1970,8 @@ static void nvmet_tcp_alloc_queue(struct nvmet_tcp_port *port, kref_init(&queue->kref); queue->sock = newsock; queue->port = port; + /* The port may be gone when the commands are allocated */ + queue->nport = port->nport; queue->nr_cmds = 0; spin_lock_init(&queue->state_lock); if (queue->port->nport->disc_addr.tsas.tcp.sectype == From 64e21d782020e267ca57765a30216c38b34f296d Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 28 Oct 2026 10:42:23 -0600 Subject: [PATCH 40/44] nvme-loop: don't BUG() if a scan runs beside a reset A reset sets queue_count to 1 while it destroys the I/O queues and counts it back up when the new ones are made, and it doesn't stop the scan work. A new namespace found in that window trips the check in nvme_loop_init_hctx() for every hctx but the first: kernel BUG at drivers/nvme/target/loop.c:224! nvme_loop_init_hctx+0x7d/0x80 __blk_mq_realloc_hw_ctxs+0x3af/0xa80 nvme_alloc_ns+0x1a2/0x22f0 The check protects nothing. ctrl->queues has nr_io_queues + 1 entries for the life of the controller, the tag set never has more hardware queues than that, and nvme_loop_queue_rq() rejects requests until the controller is live again. Remove it. Fixes: 3a85a5de29ea ("nvme-loop: add a NVMe loopback host driver") Cc: Christoph Hellwig Cc: Sagi Grimberg Cc: Chaitanya Kulkarni Signed-off-by: Josef Bacik Signed-off-by: Jens Axboe Signed-off-by: Keith Busch --- drivers/nvme/target/loop.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/nvme/target/loop.c b/drivers/nvme/target/loop.c index fcb1f8186fdd..e579a307798b 100644 --- a/drivers/nvme/target/loop.c +++ b/drivers/nvme/target/loop.c @@ -221,8 +221,6 @@ static int nvme_loop_init_hctx(struct blk_mq_hw_ctx *hctx, void *data, struct nvme_loop_ctrl *ctrl = to_loop_ctrl(data); struct nvme_loop_queue *queue = &ctrl->queues[hctx_idx + 1]; - BUG_ON(hctx_idx >= ctrl->ctrl.queue_count); - /* * flush_end_io() can be called recursively for us, so use our own * lock class key for avoiding lockdep possible recursive locking, From 2b646788ee24d895b8862514e12a95ce5d72d6cc Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Wed, 28 Oct 2026 10:42:31 -0600 Subject: [PATCH 41/44] nvmet-rdma: flush nvmet-wq on module exit nvmet_rdma_exit() relies on ib_unregister_client() to flush nvmet-wq through nvmet_rdma_remove_one(), but since commit a3dd7d0022c3 that returns at once for a device no longer on device_list. The release work of the last queue takes the device off the list in nvmet_rdma_free_dev() and still has to drop the mutex, free the struct and return through nvmet_rdma_release_queue_work(), so an rmmod in that window frees the module code it's running. Flush nvmet-wq after ib_unregister_client(). No release work can be queued after that point. Fixes: a3dd7d0022c3 ("nvmet-rdma: Don't flush system_wq by default during remove_one") Cc: Christoph Hellwig Cc: Sagi Grimberg Cc: Chaitanya Kulkarni Signed-off-by: Chris Mason Signed-off-by: Jens Axboe Signed-off-by: Keith Busch --- drivers/nvme/target/rdma.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c index ebf4a7bde524..ed11263ea93f 100644 --- a/drivers/nvme/target/rdma.c +++ b/drivers/nvme/target/rdma.c @@ -2151,6 +2151,12 @@ static void __exit nvmet_rdma_exit(void) { nvmet_unregister_transport(&nvmet_rdma_ops); ib_unregister_client(&nvmet_rdma_ib_client); + /* + * nvmet_rdma_remove_one() does not flush for a device that has left + * device_list, and the release work that took it off still has to + * return from nvmet_rdma_free_dev(). + */ + flush_workqueue(nvmet_wq); WARN_ON_ONCE(!list_empty(&nvmet_rdma_queue_list)); ida_destroy(&nvmet_rdma_queue_ida); } From 14e4e09443ac2b5cd90c1e313ee3c0bdd4928e0d Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Wed, 28 Oct 2026 10:42:33 -0600 Subject: [PATCH 42/44] nvmet-rdma: disable interrupts under rsp_wr_wait_lock rsp_wr_wait_lock protects the list of commands waiting for room in the send queue, and it's taken with plain spin_lock(). But nvmet_rdma_queue_response() calls nvmet_rdma_release_rsp() directly if ib_post_send() fails, and that processes the wait list. The call chain starts in nvmet_req_complete(), which for a block device namespace runs from the bio end_io callback, which can be in hard interrupt context. An interrupt that completes a command on a CPU holding the lock for the same queue spins forever. Lockdep over siw with a null_blk namespace: inconsistent {HARDIRQ-ON-W} -> {IN-HARDIRQ-W} usage Use spin_lock_irqsave() for both takers. The processing loop still drops the lock, and restores the caller's interrupt state, around nvmet_rdma_execute_command(). Fixes: 8f000cac6e7a ("nvmet-rdma: add a NVMe over Fabrics RDMA target driver") Cc: Christoph Hellwig Cc: Sagi Grimberg Cc: Chaitanya Kulkarni Signed-off-by: Chris Mason Signed-off-by: Jens Axboe Signed-off-by: Keith Busch --- drivers/nvme/target/rdma.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c index ed11263ea93f..279d0d097b55 100644 --- a/drivers/nvme/target/rdma.c +++ b/drivers/nvme/target/rdma.c @@ -513,7 +513,9 @@ static int nvmet_rdma_post_recv(struct nvmet_rdma_device *ndev, static void nvmet_rdma_process_wr_wait_list(struct nvmet_rdma_queue *queue) { - spin_lock(&queue->rsp_wr_wait_lock); + unsigned long flags; + + spin_lock_irqsave(&queue->rsp_wr_wait_lock, flags); while (!list_empty(&queue->rsp_wr_wait_list)) { struct nvmet_rdma_rsp *rsp; bool ret; @@ -522,16 +524,16 @@ static void nvmet_rdma_process_wr_wait_list(struct nvmet_rdma_queue *queue) struct nvmet_rdma_rsp, wait_list); list_del(&rsp->wait_list); - spin_unlock(&queue->rsp_wr_wait_lock); + spin_unlock_irqrestore(&queue->rsp_wr_wait_lock, flags); ret = nvmet_rdma_execute_command(rsp); - spin_lock(&queue->rsp_wr_wait_lock); + spin_lock_irqsave(&queue->rsp_wr_wait_lock, flags); if (!ret) { list_add(&rsp->wait_list, &queue->rsp_wr_wait_list); break; } } - spin_unlock(&queue->rsp_wr_wait_lock); + spin_unlock_irqrestore(&queue->rsp_wr_wait_lock, flags); } static u16 nvmet_rdma_check_pi_status(struct ib_mr *sig_mr) @@ -977,6 +979,7 @@ static bool nvmet_rdma_execute_command(struct nvmet_rdma_rsp *rsp) static void nvmet_rdma_handle_command(struct nvmet_rdma_queue *queue, struct nvmet_rdma_rsp *cmd) { + unsigned long flags; u16 status; ib_dma_sync_single_for_cpu(queue->dev->device, @@ -994,9 +997,9 @@ static void nvmet_rdma_handle_command(struct nvmet_rdma_queue *queue, goto out_err; if (unlikely(!nvmet_rdma_execute_command(cmd))) { - spin_lock(&queue->rsp_wr_wait_lock); + spin_lock_irqsave(&queue->rsp_wr_wait_lock, flags); list_add_tail(&cmd->wait_list, &queue->rsp_wr_wait_list); - spin_unlock(&queue->rsp_wr_wait_lock); + spin_unlock_irqrestore(&queue->rsp_wr_wait_lock, flags); } return; From f1dac2a2e75260d1466d9f03ccf0944d6d0449a4 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Wed, 28 Oct 2026 10:42:38 -0600 Subject: [PATCH 43/44] nvmet-rdma: fix double puts after a failed RDMA WRITE For a read with T10-PI the RDMA WRITE is posted alone and nvmet_rdma_write_data_done() posts the SEND once the metadata check result is known. By then the request has been completed and its references put: __nvmet_req_complete() put ns->ref and the pc_ref, and nvmet_req_complete() sq->ref, right after queuing the response. The error branch of nvmet_rdma_write_data_done() copies the one from nvmet_rdma_read_data_done() and calls nvmet_req_uninit(), which puts all three again. A WRITE fails whenever the connection ends with reads in flight, which is up to the host. Depending on timing the sq->ref count goes to zero early and the queue is freed under the remaining completions, or never reaches zero and nvmet_sq_destroy() sleeps forever, or percpu_ref_put() dereferences NULL after percpu_ref_exit(). ns->ref stays too low for good, so disabling the namespace hangs too. Reproduced with KASAN over rxe with a test patch forcing the PI path. Drop the nvmet_req_uninit(). The branch still releases the RDMA context and the response, which is all the request owns at that point. Fixes: b09160c3996c ("nvmet-rdma: add metadata/T10-PI support") Cc: Christoph Hellwig Cc: Sagi Grimberg Cc: Chaitanya Kulkarni Signed-off-by: Chris Mason Signed-off-by: Jens Axboe Signed-off-by: Keith Busch --- drivers/nvme/target/rdma.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c index 279d0d097b55..b9e39d1657d2 100644 --- a/drivers/nvme/target/rdma.c +++ b/drivers/nvme/target/rdma.c @@ -803,7 +803,10 @@ static void nvmet_rdma_write_data_done(struct ib_cq *cq, struct ib_wc *wc) if (unlikely(wc->status != IB_WC_SUCCESS)) { nvmet_rdma_rw_ctx_destroy(rsp); - nvmet_req_uninit(&rsp->req); + /* + * Unlike in nvmet_rdma_read_data_done(), the request has been + * completed and its references are put: no nvmet_req_uninit(). + */ nvmet_rdma_release_rsp(rsp); if (wc->status != IB_WC_WR_FLUSH_ERR) { pr_info("RDMA WRITE for CQE failed with status %s (%d).\n", From 89480d137bf0d943a82c64f86ae2407a15276b5e Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Wed, 28 Oct 2026 10:42:41 -0600 Subject: [PATCH 44/44] nvmet: don't clear sq->ctrl for a Connect with sqsize 0 nvmet_install_queue() checks sqsize first and jumps to err on zero, where sq->ctrl is set to NULL. Since commit b71df12605ca the cmpxchg() that sets sq->ctrl comes after the checks, so err now undoes a bind that this call never made. Over RDMA a host can send two Connects together and the second is parsed before the first executes, so a second Connect with sqsize 0 clears the sq->ctrl the first one set. The reference it held is never dropped and the controller leaks, with its cntlid, until reboot. The discovery subsystem lets any host do that, 20 connections leak around 10 controllers. A pending AER on such a queue hangs nvmet_sq_destroy() forever, and a queue can keep its "authenticated" mark across a rebind. Return the status directly, as the two checks after it do, so err is only reached after this call's cmpxchg() succeeded. Fixes: b71df12605ca ("nvmet: avoid duplicate qid in connect cmd") Cc: Christoph Hellwig Cc: Sagi Grimberg Cc: Chaitanya Kulkarni Signed-off-by: Chris Mason Signed-off-by: Jens Axboe Signed-off-by: Keith Busch --- drivers/nvme/target/fabrics-cmd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/nvme/target/fabrics-cmd.c b/drivers/nvme/target/fabrics-cmd.c index b451029ab8f4..fc626c2fd2c9 100644 --- a/drivers/nvme/target/fabrics-cmd.c +++ b/drivers/nvme/target/fabrics-cmd.c @@ -186,8 +186,7 @@ static u16 nvmet_install_queue(struct nvmet_ctrl *ctrl, struct nvmet_req *req) pr_warn("queue size zero!\n"); req->error_loc = offsetof(struct nvmf_connect_command, sqsize); req->cqe->result.u32 = IPO_IATTR_CONNECT_SQE(sqsize); - ret = NVME_SC_CONNECT_INVALID_PARAM | NVME_STATUS_DNR; - goto err; + return NVME_SC_CONNECT_INVALID_PARAM | NVME_STATUS_DNR; } if (ctrl->sqs[qid] != NULL) {