Since commit 9952f3882ecb ("nvme: fix cdev lifetime"), nvme_add_ns_cdev() and nvme_add_ns_head_cdev() take a reference to the namespace (or namespace head) before calling nvme_cdev_add(), to be dropped by the char device's release, nvme_cdev_rel(), and drop it themselves when nvme_cdev_add() fails. But when cdev_device_add() fails, nvme_cdev_add() has already put the device, and nvme_cdev_rel() has already dropped that reference: the caller drops it a second time. With one allocation failure injected into cdev_device_add() for a namespace on an NVMe over loop controller, deleting the controller gives, with native multipath: BUG: KASAN: slab-use-after-free in nvme_put_ns_head+0x21/0x140 Write of size 4 at addr ff1100000a4ea078 by task init/1 nvme_put_ns_head nvme_put_ns nvme_remove_namespaces nvme_do_delete_ctrl nvme_delete_ctrl_sync nvme_sysfs_delete Freed by task 1: kfree nvme_ns_remove nvme_remove_namespaces refcount_t: underflow; use-after-free. Take the reference in nvme_cdev_add() instead, right before cdev_device_add(), and let the callers neither take nor drop it. Add nvme_is_ns_cdev() for the check that nvme_cdev_rel() and nvme_cdev_add() now share. Fixes: 9952f3882ecb ("nvme: fix cdev lifetime") Cc: stable@vger.kernel.org Reviewed-by: John Garry --- Notes: The bug was found with kbound, a research static analysis prototype. The patch, the changelog and the test setup were written with the help of an AI coding assistant (Claude); I have reviewed and tested them as described below. Changes in v2, after John's review: - Add nvme_is_ns_cdev() for the check shared by nvme_cdev_rel() and nvme_cdev_add(). - Shorten the comment and the changelog; the testing is below. - Add John's Reviewed-by. Tested on QEMU (x86_64, KVM) with KASAN, on next-20261030, with a loop target exporting a null_blk device, with nvme_core.multipath=N and Y: - failslab failing each allocation under nvme_cdev_add() in turn: before this patch every failure in cdev_device_add() gives the report above (or, with multipath=N, a use-after-free of the namespace in nvme_mpath_clear_ctrl_paths()); with it nothing is reported, also with lockdep and kmemleak. - the case of 9952f3882ecb: hold /dev/ng0n1 open while the controller is deleted, then issue NVME_IOCTL_ID and close it. With 9952f3882ecb reverted this gives KASAN slab-use-after-free in cdev_put() at the close, with either setting; with this patch nothing is reported. v1: https://lore.kernel.org/linux-nvme/20261101173400.98931-1-izaiahsun@gmail.com/ drivers/nvme/host/core.c | 18 +++++++++++++++--- drivers/nvme/host/multipath.c | 2 -- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index af71d02b8476..9ea9db0271c2 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -3941,10 +3941,16 @@ static int nvme_subsys_check_duplicate_ids(struct nvme_subsystem *subsys, return 0; } +/* The char device of a namespace rather than of a namespace head */ +static bool nvme_is_ns_cdev(struct device *dev) +{ + return dev->parent->class == &nvme_class; +} + static void nvme_cdev_rel(struct device *dev) { ida_free(&nvme_ns_chr_minor_ida, MINOR(dev->devt)); - if (dev->parent->class == &nvme_class) + if (nvme_is_ns_cdev(dev)) nvme_put_ns(container_of(dev, struct nvme_ns, cdev_device)); else nvme_put_ns_head(container_of(dev, struct nvme_ns_head, @@ -3978,6 +3984,14 @@ int nvme_cdev_add(struct cdev *cdev, struct device *cdev_device, device_initialize(cdev_device); cdev_init(cdev, fops); cdev->owner = owner; + /* Undone in nvme_cdev_rel() */ + if (nvme_is_ns_cdev(cdev_device)) + nvme_get_ns(container_of(cdev_device, struct nvme_ns, + cdev_device)); + else + nvme_get_ns_head(container_of(cdev_device, + struct nvme_ns_head, + cdev_device)); ret = cdev_device_add(cdev, cdev_device); if (ret) put_device(cdev_device); @@ -4010,13 +4024,11 @@ static void nvme_add_ns_cdev(struct nvme_ns *ns) { ns->cdev_device.parent = ns->ctrl->device; - nvme_get_ns(ns); /* Undone in nvme_cdev_rel() */ if (nvme_cdev_add(&ns->cdev, &ns->cdev_device, &nvme_ns_chr_fops, ns->ctrl->ops->module, ns->ctrl->instance, ns->head->instance)) { dev_err(ns->ctrl->device, "Unable to create the ng%dn%d device\n", ns->ctrl->instance, ns->head->instance); - nvme_put_ns(ns); return; } set_bit(NVME_NS_CDEV_LIVE, &ns->flags); diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 9e0a0fd77c56..ae8059366b57 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -681,14 +681,12 @@ static void nvme_add_ns_head_cdev(struct nvme_ns_head *head) { head->cdev_device.parent = &head->subsys->dev; - nvme_get_ns_head(head); /* Undone in nvme_cdev_rel() */ if (nvme_cdev_add(&head->cdev, &head->cdev_device, &nvme_ns_head_chr_fops, THIS_MODULE, head->subsys->instance, head->instance)) { dev_err(disk_to_dev(head->disk), "Unable to create the ng%dn%d device\n", head->subsys->instance, head->instance); - nvme_put_ns_head(head); return; } set_bit(NVME_NSHEAD_CDEV_LIVE, &head->flags); base-commit: d135ab453a7400a34930bdda1ef4bdc8acfac80e -- 2.53.0