From 6fe0687245e8406bf26143bd45eb16441bbe5280 Mon Sep 17 00:00:00 2001 From: Nick Chan Date: Sun, 7 Jun 2026 14:10:58 +0800 Subject: [PATCH 01/11] nvme-apple: Prevent shared tags across queues on Apple A11 On Apple A11, tags of pending commands must be unique across the admin and IO queues, else the firmware crashes with "duplicate tag error for tag N", with N being the tag. Apply the existing workaround for M1 of reserving two tags for the admin queue to A11. Cc: stable@vger.kernel.org Fixes: 04d8ecf37b5e ("nvme: apple: Add Apple A11 support") Reviewed-by: Sven Peter Signed-off-by: Nick Chan Signed-off-by: Keith Busch --- drivers/nvme/host/apple.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index de0d5126458f..79e1fe2a23f9 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -225,7 +225,7 @@ static unsigned int apple_nvme_queue_depth(struct apple_nvme_queue *q) { struct apple_nvme *anv = queue_to_apple_nvme(q); - if (q->is_adminq && anv->hw->has_lsq_nvmmu) + if (q->is_adminq) return APPLE_NVME_AQ_DEPTH; return anv->hw->max_queue_depth; @@ -303,7 +303,7 @@ static void apple_nvme_submit_cmd_t8015(struct apple_nvme_queue *q, memcpy((void *)q->sqes + (q->sq_tail << APPLE_NVME_IOSQES), cmd, sizeof(*cmd)); - if (++q->sq_tail == anv->hw->max_queue_depth) + if (++q->sq_tail == apple_nvme_queue_depth(q)) q->sq_tail = 0; writel(q->sq_tail, q->sq_db); @@ -1138,10 +1138,7 @@ static void apple_nvme_reset_work(struct work_struct *work) } /* Setup the admin queue */ - if (anv->hw->has_lsq_nvmmu) - aqa = APPLE_NVME_AQ_DEPTH - 1; - else - aqa = anv->hw->max_queue_depth - 1; + aqa = APPLE_NVME_AQ_DEPTH - 1; aqa |= aqa << 16; writel(aqa, anv->mmio_nvme + NVME_REG_AQA); writeq(anv->adminq.sq_dma_addr, anv->mmio_nvme + NVME_REG_ASQ); @@ -1324,8 +1321,7 @@ static int apple_nvme_alloc_tagsets(struct apple_nvme *anv) * both queues. The admin queue gets the first APPLE_NVME_AQ_DEPTH which * must be marked as reserved in the IO queue. */ - if (anv->hw->has_lsq_nvmmu) - anv->tagset.reserved_tags = APPLE_NVME_AQ_DEPTH; + anv->tagset.reserved_tags = APPLE_NVME_AQ_DEPTH; anv->tagset.queue_depth = anv->hw->max_queue_depth - 1; anv->tagset.timeout = NVME_IO_TIMEOUT; anv->tagset.numa_node = NUMA_NO_NODE; From 92f58587a04c94985fd4a9e3575720b054c432bf Mon Sep 17 00:00:00 2001 From: John Garry Date: Thu, 4 Jun 2026 14:58:40 +0000 Subject: [PATCH 02/11] nvme: quieten sparse warning in valid LBA size check Currently building with C=1 generates the following warning: CC drivers/nvme/host/core.o CHECK drivers/nvme/host/core.c drivers/nvme/host/core.c:2426:13: warning: unsigned value that used to be signed checked against zero? drivers/nvme/host/core.c:2426:13: signed value source This issue was introduced when using check_shl_overflow() to check for invalid LBA size. Sparse is having trouble dealing with __bitwise __le64 conversion when passing to check_shl_overflow(). Resolve the issue by moving the check_shl_overflow() call to a separate function, where types are not converted. The id->lbaf[lbaf].ds < SECTOR_SHIFT check is dropped as check_shl_overflow() is able to detect negative shifts. Reviewed-by: Christoph Hellwig Signed-off-by: John Garry Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index efaddab8296e..d37fc70fe48a 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2379,6 +2379,11 @@ static int nvme_query_fdp_info(struct nvme_ns *ns, struct nvme_ns_info *info) return ret; } +static bool nvme_invalid_lba_sz(u64 nsze, signed int shift, sector_t *capacity) +{ + return check_shl_overflow(nsze, shift, capacity); +} + static int nvme_update_ns_info_block(struct nvme_ns *ns, struct nvme_ns_info *info) { @@ -2422,10 +2427,8 @@ static int nvme_update_ns_info_block(struct nvme_ns *ns, goto out; } - if (id->lbaf[lbaf].ds < SECTOR_SHIFT || - check_shl_overflow(le64_to_cpu(id->nsze), - id->lbaf[lbaf].ds - SECTOR_SHIFT, - &capacity)) { + if (nvme_invalid_lba_sz(le64_to_cpu(id->nsze), + id->lbaf[lbaf].ds - SECTOR_SHIFT, &capacity)) { dev_warn_once(ns->ctrl->device, "invalid LBA data size %u, skipping namespace\n", id->lbaf[lbaf].ds); From 34b9a83c50660148bde01cde16451dbe78369749 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Tue, 9 Jun 2026 09:55:05 +0000 Subject: [PATCH 03/11] nvmet: fix refcount leak in nvmet_sq_create() In nvmet_sq_create(), a reference on the ctrl is taken via kref_get_unless_zero() before calling nvmet_check_sqid(). If nvmet_check_sqid() fails, the function returns the error directly without releasing the reference, leading to a leak. Fix this by jumping to the "ctrl_put" label, which already performs the necessary nvmet_ctrl_put(ctrl). This ensures the reference is properly released on this error path. Cc: stable@vger.kernel.org Fixes: 1eb380caf527 ("nvmet: Introduce nvmet_sq_create() and nvmet_cq_create()") Signed-off-by: Wentao Liang 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 62dd59b9aa4f..4477c4d6b1ee 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -944,7 +944,7 @@ u16 nvmet_sq_create(struct nvmet_ctrl *ctrl, struct nvmet_sq *sq, status = nvmet_check_sqid(ctrl, sqid, true); if (status != NVME_SC_SUCCESS) - return status; + goto ctrl_put; ret = nvmet_sq_init(sq, cq); if (ret) { From 26acdaa357cded33a37f575cd5f6bae1033b3a5d Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Mon, 8 Jun 2026 17:53:57 +0200 Subject: [PATCH 04/11] nvme: fix crash and memory leak during invalid cdev teardown In the NVMe multipath code, if nvme_add_ns_head_cdev() fails during nvme_mpath_set_live(), the error is ignored. However, during teardown, nvme_remove_head() unconditionally calls nvme_cdev_del(). This teardown asymmetry leads to a kernel panic if the character device was never successfully initialized. BUG: kernel NULL pointer dereference, address: 00000000000000d0 device_del+0x39/0x3c0 cdev_device_del+0x15/0x50 nvme_cdev_del+0xe/0x20 [nvme_core] nvme_mpath_shutdown_disk+0x38/0x60 [nvme_core] nvme_ns_remove+0x177/0x1f0 [nvme_core] nvme_remove_namespaces+0xdc/0x130 [nvme_core] nvme_do_delete_ctrl+0x71/0xd0 [nvme_core] Additionally, a memory leak exists in the nvme_cdev_add() failure path. Previously, dev_set_name() was called before ida_alloc(). If ida_alloc() subsequently failed, device_initialize() was never called, meaning put_device() could not be used to clean up the kobject, leaking the memory allocated by dev_set_name(). * Introduces the NVME_NSHEAD_CDEV_LIVE and NVME_NS_CDEV_LIVE bits to track the successful creation of the character devices. Teardown routines now check these bits before attempting deletion. * Refactor nvme_cdev_add() to accept the formatted device name as a parameter, moving dev_set_name() after the IDA allocation and immediately before device_initialize(). This ensures any internally allocated strings are safely cleaned up by put_device() upon failure. Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 33 ++++++++++++++++++++++++--------- drivers/nvme/host/multipath.c | 19 +++++++++++++------ drivers/nvme/host/nvme.h | 5 ++++- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index d37fc70fe48a..c6930e43cfea 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -3894,7 +3894,8 @@ void nvme_cdev_del(struct cdev *cdev, struct device *cdev_device) put_device(cdev_device); } -int nvme_cdev_add(struct cdev *cdev, struct device *cdev_device, +int nvme_cdev_add(const char *name, struct cdev *cdev, + struct device *cdev_device, const struct file_operations *fops, struct module *owner) { int minor, ret; @@ -3902,6 +3903,12 @@ int nvme_cdev_add(struct cdev *cdev, struct device *cdev_device, minor = ida_alloc(&nvme_ns_chr_minor_ida, GFP_KERNEL); if (minor < 0) return minor; + + ret = dev_set_name(cdev_device, name); + if (ret) { + ida_free(&nvme_ns_chr_minor_ida, minor); + return ret; + } cdev_device->devt = MKDEV(MAJOR(nvme_ns_chr_devt), minor); cdev_device->class = &nvme_ns_chr_class; cdev_device->release = nvme_cdev_rel; @@ -3939,15 +3946,21 @@ static const struct file_operations nvme_ns_chr_fops = { static int nvme_add_ns_cdev(struct nvme_ns *ns) { int ret; + char name[32]; ns->cdev_device.parent = ns->ctrl->device; - ret = dev_set_name(&ns->cdev_device, "ng%dn%d", - ns->ctrl->instance, ns->head->instance); - if (ret) - return ret; + snprintf(name, sizeof(name), "ng%dn%d", ns->ctrl->instance, + ns->head->instance); - return nvme_cdev_add(&ns->cdev, &ns->cdev_device, &nvme_ns_chr_fops, - ns->ctrl->ops->module); + ret = nvme_cdev_add(name, &ns->cdev, &ns->cdev_device, + &nvme_ns_chr_fops, ns->ctrl->ops->module); + if (ret) { + dev_err(ns->ctrl->device, "Unable to create the %s device\n", + name); + } else { + set_bit(NVME_NS_CDEV_LIVE, &ns->flags); + } + return ret; } static struct nvme_ns_head *nvme_alloc_ns_head(struct nvme_ctrl *ctrl, @@ -4323,8 +4336,10 @@ static void nvme_ns_remove(struct nvme_ns *ns) /* guarantee not available in head->list */ synchronize_srcu(&ns->head->srcu); - if (!nvme_ns_head_multipath(ns->head)) - nvme_cdev_del(&ns->cdev, &ns->cdev_device); + if (!nvme_ns_head_multipath(ns->head)) { + if (test_and_clear_bit(NVME_NS_CDEV_LIVE, &ns->flags)) + nvme_cdev_del(&ns->cdev, &ns->cdev_device); + } nvme_mpath_remove_sysfs_link(ns); diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index e033ede953cc..9cd49e2f760d 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -642,14 +642,20 @@ static const struct file_operations nvme_ns_head_chr_fops = { static int nvme_add_ns_head_cdev(struct nvme_ns_head *head) { int ret; + char name[32]; head->cdev_device.parent = &head->subsys->dev; - ret = dev_set_name(&head->cdev_device, "ng%dn%d", - head->subsys->instance, head->instance); - if (ret) - return ret; - ret = nvme_cdev_add(&head->cdev, &head->cdev_device, + snprintf(name, sizeof(name), "ng%dn%d", head->subsys->instance, + head->instance); + + ret = nvme_cdev_add(name, &head->cdev, &head->cdev_device, &nvme_ns_head_chr_fops, THIS_MODULE); + if (ret) { + dev_err(disk_to_dev(head->disk), + "Unable to create the %s device\n", name); + } else { + set_bit(NVME_NSHEAD_CDEV_LIVE, &head->flags); + } return ret; } @@ -694,7 +700,8 @@ static void nvme_remove_head(struct nvme_ns_head *head) */ kblockd_schedule_work(&head->requeue_work); - nvme_cdev_del(&head->cdev, &head->cdev_device); + if (test_and_clear_bit(NVME_NSHEAD_CDEV_LIVE, &head->flags)) + nvme_cdev_del(&head->cdev, &head->cdev_device); synchronize_srcu(&head->srcu); del_gendisk(head->disk); } diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index b367c67dcb37..824651cc898d 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -573,6 +573,7 @@ struct nvme_ns_head { atomic_long_t io_fail_no_available_path_count; #define NVME_NSHEAD_DISK_LIVE 0 #define NVME_NSHEAD_QUEUE_IF_NO_PATH 1 +#define NVME_NSHEAD_CDEV_LIVE 2 struct nvme_ns __rcu *current_path[]; #endif }; @@ -611,6 +612,7 @@ struct nvme_ns { #define NVME_NS_FORCE_RO 3 #define NVME_NS_READY 4 #define NVME_NS_SYSFS_ATTR_LINK 5 +#define NVME_NS_CDEV_LIVE 6 struct cdev cdev; struct device cdev_device; @@ -995,7 +997,8 @@ int nvme_get_log(struct nvme_ctrl *ctrl, u32 nsid, u8 log_page, u8 lsp, u8 csi, void *log, size_t size, u64 offset); bool nvme_tryget_ns_head(struct nvme_ns_head *head); void nvme_put_ns_head(struct nvme_ns_head *head); -int nvme_cdev_add(struct cdev *cdev, struct device *cdev_device, +int nvme_cdev_add(const char *name, struct cdev *cdev, + struct device *cdev_device, const struct file_operations *fops, struct module *owner); void nvme_cdev_del(struct cdev *cdev, struct device *cdev_device); int nvme_ioctl(struct block_device *bdev, blk_mode_t mode, From 4fd1f5f6a659886a4ef3a380b2a07207c94a7a24 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 7 Jun 2026 22:12:23 -0700 Subject: [PATCH 05/11] nvme: target: allocate ana_state with port Use a flexible array member to remove one allocation. Simplifies code slightly. Signed-off-by: Rosen Penev Signed-off-by: Keith Busch --- drivers/nvme/target/configfs.c | 9 +-------- drivers/nvme/target/nvmet.h | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c index b88f897f06e2..2b69ffcfc8df 100644 --- a/drivers/nvme/target/configfs.c +++ b/drivers/nvme/target/configfs.c @@ -2007,7 +2007,6 @@ static void nvmet_port_release(struct config_item *item) list_del(&port->global_entry); key_put(port->keyring); - kfree(port->ana_state); kfree(port); } @@ -2047,16 +2046,10 @@ static struct config_group *nvmet_ports_make(struct config_group *group, if (kstrtou16(name, 0, &portid)) return ERR_PTR(-EINVAL); - port = kzalloc_obj(*port); + port = kzalloc_flex(*port, ana_state, NVMET_MAX_ANAGRPS + 1); if (!port) return ERR_PTR(-ENOMEM); - port->ana_state = kzalloc_objs(*port->ana_state, NVMET_MAX_ANAGRPS + 1); - if (!port->ana_state) { - kfree(port); - return ERR_PTR(-ENOMEM); - } - if (IS_ENABLED(CONFIG_NVME_TARGET_TCP_TLS) && nvme_keyring_id()) { port->keyring = key_lookup(nvme_keyring_id()); if (IS_ERR(port->keyring)) { diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h index 3305a88684ec..aaba745e3c21 100644 --- a/drivers/nvme/target/nvmet.h +++ b/drivers/nvme/target/nvmet.h @@ -208,7 +208,6 @@ struct nvmet_port { struct list_head global_entry; struct config_group ana_groups_group; struct nvmet_ana_group ana_default_group; - enum nvme_ana_state *ana_state; struct key *keyring; void *priv; bool enabled; @@ -217,6 +216,7 @@ struct nvmet_port { int mdts; const struct nvmet_fabrics_ops *tr_ops; bool pi_enable; + enum nvme_ana_state ana_state[]; }; static inline struct nvmet_port *to_nvmet_port(struct config_item *item) From 48c0162f647bb47e6084ffbc71b8f213f5e2f4f8 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Thu, 4 Jun 2026 19:36:54 +0000 Subject: [PATCH 06/11] nvmet-rdma: handle inline data with a nonzero offset nvmet_rdma_use_inline_sg() maps the host-controlled inline data offset into the per-command inline scatterlist. The bounds check admits any offset with off + len <= inline_data_size, but the mapping still assumes the data begins in the first inline page: sg->offset = off; sg->length = min_t(int, len, PAGE_SIZE - off); When a port is configured with inline_data_size > PAGE_SIZE (settable up to max(SZ_16K, PAGE_SIZE)), an offset in (PAGE_SIZE, inline_data_size] makes "PAGE_SIZE - off" underflow, so sg->length is set to ~4 GiB and the block backend reads far past the first inline page. num_pages(len) also ignores the offset, so an in-bounds offset whose [off, off+len) span crosses a page boundary under-counts the scatterlist. Map the offset properly: split it into a page index and an in-page offset, start the scatterlist at that page, and size the page count from page_off + len. Because the request scatterlist may now start at inline_sg[page_idx] rather than inline_sg[0], generalize the inline-SGL identity test in nvmet_rdma_release_rsp() to a range test; otherwise the persistent inline scatterlist is mistaken for an allocated one and nvmet_req_free_sgls() frees an inline page (and warns in free_large_kmalloc()). Fixes: 0d5ee2b2ab4f ("nvmet-rdma: support max(16KB, PAGE_SIZE) inline data") Cc: stable@vger.kernel.org Suggested-by: Keith Busch Reported-by: Bryam Vargas Signed-off-by: Bryam Vargas Signed-off-by: Keith Busch --- drivers/nvme/target/rdma.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c index ac26f4f774c4..ea1185b8267e 100644 --- a/drivers/nvme/target/rdma.c +++ b/drivers/nvme/target/rdma.c @@ -666,7 +666,8 @@ static void nvmet_rdma_release_rsp(struct nvmet_rdma_rsp *rsp) if (rsp->n_rdma) nvmet_rdma_rw_ctx_destroy(rsp); - if (rsp->req.sg != rsp->cmd->inline_sg) + if (rsp->req.sg < rsp->cmd->inline_sg || + rsp->req.sg >= rsp->cmd->inline_sg + queue->dev->inline_page_count) nvmet_req_free_sgls(&rsp->req); if (unlikely(!list_empty_careful(&queue->rsp_wr_wait_list))) @@ -821,24 +822,25 @@ static void nvmet_rdma_write_data_done(struct ib_cq *cq, struct ib_wc *wc) static void nvmet_rdma_use_inline_sg(struct nvmet_rdma_rsp *rsp, u32 len, u64 off) { - int sg_count = num_pages(len); + u64 page_off = off % PAGE_SIZE; + u64 page_idx = off / PAGE_SIZE; + int sg_count = num_pages(page_off + len); struct scatterlist *sg; int i; - sg = rsp->cmd->inline_sg; + sg = &rsp->cmd->inline_sg[page_idx]; for (i = 0; i < sg_count; i++, sg++) { if (i < sg_count - 1) sg_unmark_end(sg); else sg_mark_end(sg); - sg->offset = off; - sg->length = min_t(int, len, PAGE_SIZE - off); + sg->offset = page_off; + sg->length = min_t(u64, len, PAGE_SIZE - page_off); len -= sg->length; - if (!i) - off = 0; + page_off = 0; } - rsp->req.sg = rsp->cmd->inline_sg; + rsp->req.sg = &rsp->cmd->inline_sg[page_idx]; rsp->req.sg_cnt = sg_count; } From ac48c49116d3de84fabc224c6e43f08740b1460d Mon Sep 17 00:00:00 2001 From: John Garry Date: Wed, 10 Jun 2026 08:53:32 +0000 Subject: [PATCH 07/11] nvme: make some sysfs diagnostic structures static Building with C=1 generates the following warnings: drivers/nvme/host/sysfs.c:397:25: warning: symbol 'dev_attr_io_errors' was not declared. Should it be static? drivers/nvme/host/sysfs.c:444:30: warning: symbol 'nvme_ns_diag_attr_group' was not declared. Should it be static? drivers/nvme/host/sysfs.c:1150:25: warning: symbol 'dev_attr_adm_errors' was not declared. Should it be static? Make those structures static. Closes: https://lore.kernel.org/oe-kbuild-all/202606101329.T3zXNqdy-lkp@intel.com/ Reviewed-by: Nilay Shroff Reviewed-by: Christoph Hellwig Signed-off-by: John Garry Signed-off-by: Keith Busch --- drivers/nvme/host/sysfs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index 933a5adfb7af..75b2d69b5957 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -394,7 +394,7 @@ static ssize_t nvme_io_errors_store(struct device *dev, return count; } -struct device_attribute dev_attr_io_errors = +static struct device_attribute dev_attr_io_errors = __ATTR(command_error_count, 0644, nvme_io_errors_show, nvme_io_errors_store); @@ -441,7 +441,7 @@ static umode_t nvme_ns_diag_attrs_are_visible(struct kobject *kobj, return a->mode; } -const struct attribute_group nvme_ns_diag_attr_group = { +static const struct attribute_group nvme_ns_diag_attr_group = { .name = "diag", .attrs = nvme_ns_diag_attrs, .is_visible = nvme_ns_diag_attrs_are_visible, @@ -1147,7 +1147,7 @@ static ssize_t nvme_adm_errors_store(struct device *dev, return count; } -struct device_attribute dev_attr_adm_errors = +static struct device_attribute dev_attr_adm_errors = __ATTR(command_error_count, 0644, nvme_adm_errors_show, nvme_adm_errors_store); From 869567bcbe2dcc790860e05fc0e0c5e415bb22c2 Mon Sep 17 00:00:00 2001 From: John Garry Date: Wed, 10 Jun 2026 11:16:08 +0000 Subject: [PATCH 08/11] nvme: make nvme_add_ns{_head}_cdev return void The return code from nvme_add_ns_head_cdev() and nvme_add_ns_cdev() is never checked, so make those functions return void. A cdev add failure is tolerated during initialization, and flags NVME_NS_CDEV_LIVE and NVME_NSHEAD_CDEV_LIVE are for determining whether a cdev needs to be deleted during un-initialization. Reviewed-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Signed-off-by: John Garry Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 13 +++++-------- drivers/nvme/host/multipath.c | 13 +++++-------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index c6930e43cfea..eb268148acec 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -3943,24 +3943,21 @@ static const struct file_operations nvme_ns_chr_fops = { .uring_cmd_iopoll = nvme_ns_chr_uring_cmd_iopoll, }; -static int nvme_add_ns_cdev(struct nvme_ns *ns) +static void nvme_add_ns_cdev(struct nvme_ns *ns) { - int ret; char name[32]; ns->cdev_device.parent = ns->ctrl->device; snprintf(name, sizeof(name), "ng%dn%d", ns->ctrl->instance, ns->head->instance); - ret = nvme_cdev_add(name, &ns->cdev, &ns->cdev_device, - &nvme_ns_chr_fops, ns->ctrl->ops->module); - if (ret) { + if (nvme_cdev_add(name, &ns->cdev, &ns->cdev_device, + &nvme_ns_chr_fops, ns->ctrl->ops->module)) { dev_err(ns->ctrl->device, "Unable to create the %s device\n", name); - } else { - set_bit(NVME_NS_CDEV_LIVE, &ns->flags); + return; } - return ret; + set_bit(NVME_NS_CDEV_LIVE, &ns->flags); } static struct nvme_ns_head *nvme_alloc_ns_head(struct nvme_ctrl *ctrl, diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 9cd49e2f760d..9b9a657fa330 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -639,24 +639,21 @@ static const struct file_operations nvme_ns_head_chr_fops = { .uring_cmd_iopoll = nvme_ns_chr_uring_cmd_iopoll, }; -static int nvme_add_ns_head_cdev(struct nvme_ns_head *head) +static void nvme_add_ns_head_cdev(struct nvme_ns_head *head) { - int ret; char name[32]; head->cdev_device.parent = &head->subsys->dev; snprintf(name, sizeof(name), "ng%dn%d", head->subsys->instance, head->instance); - ret = nvme_cdev_add(name, &head->cdev, &head->cdev_device, - &nvme_ns_head_chr_fops, THIS_MODULE); - if (ret) { + if (nvme_cdev_add(name, &head->cdev, &head->cdev_device, + &nvme_ns_head_chr_fops, THIS_MODULE)) { dev_err(disk_to_dev(head->disk), "Unable to create the %s device\n", name); - } else { - set_bit(NVME_NSHEAD_CDEV_LIVE, &head->flags); + return; } - return ret; + set_bit(NVME_NSHEAD_CDEV_LIVE, &head->flags); } static void nvme_partition_scan_work(struct work_struct *work) From ee38469f88492df99e1d97f03aa40ecfd218934f Mon Sep 17 00:00:00 2001 From: Mohamed Khalfella Date: Thu, 28 May 2026 11:27:34 +0200 Subject: [PATCH 09/11] nvme-fc: Do not cancel requests in io target before it is initialized A new nvme-fc controller in CONNECTING state sees admin request timeout schedules ctrl->ioerr_work to abort inflight requests. This ends up calling __nvme_fc_abort_outstanding_ios() which aborts requests in both admin and io tagsets. In case fc_ctrl->tag_set was not initialized we see the warning below. This is because ctrl.queue_count is initialized early in nvme_fc_alloc_ctrl(). nvme nvme0: NVME-FC{0}: starting error recovery Connectivity Loss INFO: trying to register non-static key. The code is fine but needs lockdep annotation, or maybe lpfc 0000:ab:00.0: queue 0 connect admin queue failed (-6). you didn't initialize this object before use? turning off the locking correctness validator. Workqueue: nvme-reset-wq nvme_fc_ctrl_ioerr_work [nvme_fc] Call Trace: dump_stack_lvl+0x57/0x80 register_lock_class+0x567/0x580 __lock_acquire+0x330/0xb90 lock_acquire.part.0+0xad/0x210 blk_mq_tagset_busy_iter+0xf9/0xc00 __nvme_fc_abort_outstanding_ios+0x23f/0x320 [nvme_fc] nvme_fc_ctrl_ioerr_work+0x172/0x210 [nvme_fc] process_one_work+0x82c/0x1450 worker_thread+0x5ee/0xfd0 kthread+0x3a0/0x750 ret_from_fork+0x439/0x670 ret_from_fork_asm+0x1a/0x30 Update the check in __nvme_fc_abort_outstanding_ios() confirm that io tagset was created before iterating over busy requests. Also make sure to cancel ctrl->ioerr_work before removing io tagset. Reviewed-by: Randy Jennings Reviewed-by: Hannes Reinecke Reviewed-by: Daniel Wagner Reviewed-by: Christoph Hellwig Signed-off-by: Mohamed Khalfella Signed-off-by: James Smart Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/host/fc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index 2c9a6d3c9797..04363b9c4489 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -2461,7 +2461,7 @@ __nvme_fc_abort_outstanding_ios(struct nvme_fc_ctrl *ctrl, bool start_queues) * io requests back to the block layer as part of normal completions * (but with error status). */ - if (ctrl->ctrl.queue_count > 1) { + if (ctrl->ctrl.queue_count > 1 && ctrl->ctrl.tagset) { nvme_quiesce_io_queues(&ctrl->ctrl); nvme_sync_io_queues(&ctrl->ctrl); blk_mq_tagset_busy_iter(&ctrl->tag_set, @@ -2900,6 +2900,11 @@ nvme_fc_create_io_queues(struct nvme_fc_ctrl *ctrl) out_delete_hw_queues: nvme_fc_delete_hw_io_queues(ctrl); out_cleanup_tagset: + /* + * In CONNECTING state ctrl->ioerr_work will abort both admin + * and io tagsets. Cancel it first before removing io tagset. + */ + cancel_work_sync(&ctrl->ioerr_work); nvme_remove_io_tag_set(&ctrl->ctrl); nvme_fc_free_io_queues(ctrl); From 779575bc35c687697ba69e904f2cd22e60112534 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 9 Jun 2026 14:24:31 -0400 Subject: [PATCH 10/11] nvmet-auth: reject short AUTH_RECEIVE buffers nvmet_execute_auth_receive() trusts the AUTH_RECEIVE allocation length after checking only that it is nonzero and matches the transfer length. In the SUCCESS1 and FAILURE1/default states, that lets a remote NVMe-oF initiator reach the fixed-size DH-HMAC-CHAP response builders with a kmalloc() buffer shorter than the response, so nvmet_auth_success1() and nvmet_auth_failure1() write past the allocation; both only WARN_ON the short length and then format the message anyway. Impact: A remote NVMe-oF initiator with access to an auth-enabled target can trigger a 16-byte heap out-of-bounds write via a one-byte AUTH_RECEIVE allocation length. Compute the minimum response length for the current DH-HMAC-CHAP step in nvmet_auth_receive_data_len() and report a zero data length when the host-supplied allocation length is shorter, so the existing zero-length check in nvmet_execute_auth_receive() rejects the command before any builder runs. The SUCCESS1 minimum is sizeof(struct nvmf_auth_dhchap_success1_data) plus the HMAC hash length, because the response hash is written into the rval[] flexible-array tail, so the minimum is state dependent rather than a flat sizeof. CHALLENGE keeps its existing variable-length guard in nvmet_auth_challenge(). This is reachable only when in-band DH-HMAC-CHAP authentication is configured on the target. Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5-5-xhigh Assisted-by: Claude:claude-opus-4-8 Reviewed-by: Hannes Reinecke Signed-off-by: Michael Bommarito Signed-off-by: Keith Busch --- drivers/nvme/target/fabrics-cmd-auth.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c index 0a85acf1e5c7..45820a12750d 100644 --- a/drivers/nvme/target/fabrics-cmd-auth.c +++ b/drivers/nvme/target/fabrics-cmd-auth.c @@ -493,7 +493,31 @@ static void nvmet_auth_failure1(struct nvmet_req *req, void *d, int al) u32 nvmet_auth_receive_data_len(struct nvmet_req *req) { - return le32_to_cpu(req->cmd->auth_receive.al); + struct nvmet_ctrl *ctrl = req->sq->ctrl; + u32 al = le32_to_cpu(req->cmd->auth_receive.al); + u32 min_len; + + /* + * Reject too-short al before kmalloc(al), since the SUCCESS1 and + * FAILURE1/default builders write fixed response headers into it. + */ + switch (req->sq->dhchap_step) { + case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE: + return al; + case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1: + min_len = sizeof(struct nvmf_auth_dhchap_success1_data); + if (req->sq->dhchap_c2) + min_len += nvme_auth_hmac_hash_len(ctrl->shash_id); + break; + default: + min_len = sizeof(struct nvmf_auth_dhchap_failure_data); + break; + } + + if (al < min_len) + return 0; + + return al; } void nvmet_execute_auth_receive(struct nvmet_req *req) From 7d953c75f0a3f905aadf3675c9394a5b9d9897bf Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Mon, 16 Mar 2026 17:44:41 +0100 Subject: [PATCH 11/11] nvmet-tcp: handle TCP_CLOSING state in nvmet_tcp_state_change When an NVMe/TCP connection shuts down, the underlying TCP socket can enter the TCP_CLOSING state (state 11). Currently, the nvmet_tcp_state_change() callback does not explicitly handle this state, which results in harmless but noisy kernel warnings: nvmet_tcp: queue 2 unhandled state 11 Add TCP_CLOSING to the switch statement alongside TCP_FIN_WAIT2 and TCP_LAST_ACK to silently ignore the state transition. Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/target/tcp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index 3568fa9a0905..08140f2476d2 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -1678,6 +1678,7 @@ static void nvmet_tcp_state_change(struct sock *sk) switch (sk->sk_state) { case TCP_FIN_WAIT2: case TCP_LAST_ACK: + case TCP_CLOSING: break; case TCP_FIN_WAIT1: case TCP_CLOSE_WAIT: