mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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 <mlombard@redhat.com> Signed-off-by: Keith Busch <kbusch@kernel.org>
This commit is contained in:
parent
34b9a83c50
commit
26acdaa357
|
|
@ -3894,7 +3894,8 @@ void nvme_cdev_del(struct cdev *cdev, struct device *cdev_device)
|
||||||
put_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)
|
const struct file_operations *fops, struct module *owner)
|
||||||
{
|
{
|
||||||
int minor, ret;
|
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);
|
minor = ida_alloc(&nvme_ns_chr_minor_ida, GFP_KERNEL);
|
||||||
if (minor < 0)
|
if (minor < 0)
|
||||||
return minor;
|
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->devt = MKDEV(MAJOR(nvme_ns_chr_devt), minor);
|
||||||
cdev_device->class = &nvme_ns_chr_class;
|
cdev_device->class = &nvme_ns_chr_class;
|
||||||
cdev_device->release = nvme_cdev_rel;
|
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)
|
static int nvme_add_ns_cdev(struct nvme_ns *ns)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
char name[32];
|
||||||
|
|
||||||
ns->cdev_device.parent = ns->ctrl->device;
|
ns->cdev_device.parent = ns->ctrl->device;
|
||||||
ret = dev_set_name(&ns->cdev_device, "ng%dn%d",
|
snprintf(name, sizeof(name), "ng%dn%d", ns->ctrl->instance,
|
||||||
ns->ctrl->instance, ns->head->instance);
|
ns->head->instance);
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
return nvme_cdev_add(&ns->cdev, &ns->cdev_device, &nvme_ns_chr_fops,
|
ret = nvme_cdev_add(name, &ns->cdev, &ns->cdev_device,
|
||||||
ns->ctrl->ops->module);
|
&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,
|
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 */
|
/* guarantee not available in head->list */
|
||||||
synchronize_srcu(&ns->head->srcu);
|
synchronize_srcu(&ns->head->srcu);
|
||||||
|
|
||||||
if (!nvme_ns_head_multipath(ns->head))
|
if (!nvme_ns_head_multipath(ns->head)) {
|
||||||
nvme_cdev_del(&ns->cdev, &ns->cdev_device);
|
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);
|
nvme_mpath_remove_sysfs_link(ns);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
static int nvme_add_ns_head_cdev(struct nvme_ns_head *head)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
char name[32];
|
||||||
|
|
||||||
head->cdev_device.parent = &head->subsys->dev;
|
head->cdev_device.parent = &head->subsys->dev;
|
||||||
ret = dev_set_name(&head->cdev_device, "ng%dn%d",
|
snprintf(name, sizeof(name), "ng%dn%d", head->subsys->instance,
|
||||||
head->subsys->instance, head->instance);
|
head->instance);
|
||||||
if (ret)
|
|
||||||
return ret;
|
ret = nvme_cdev_add(name, &head->cdev, &head->cdev_device,
|
||||||
ret = nvme_cdev_add(&head->cdev, &head->cdev_device,
|
|
||||||
&nvme_ns_head_chr_fops, THIS_MODULE);
|
&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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -694,7 +700,8 @@ static void nvme_remove_head(struct nvme_ns_head *head)
|
||||||
*/
|
*/
|
||||||
kblockd_schedule_work(&head->requeue_work);
|
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);
|
synchronize_srcu(&head->srcu);
|
||||||
del_gendisk(head->disk);
|
del_gendisk(head->disk);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -573,6 +573,7 @@ struct nvme_ns_head {
|
||||||
atomic_long_t io_fail_no_available_path_count;
|
atomic_long_t io_fail_no_available_path_count;
|
||||||
#define NVME_NSHEAD_DISK_LIVE 0
|
#define NVME_NSHEAD_DISK_LIVE 0
|
||||||
#define NVME_NSHEAD_QUEUE_IF_NO_PATH 1
|
#define NVME_NSHEAD_QUEUE_IF_NO_PATH 1
|
||||||
|
#define NVME_NSHEAD_CDEV_LIVE 2
|
||||||
struct nvme_ns __rcu *current_path[];
|
struct nvme_ns __rcu *current_path[];
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
@ -611,6 +612,7 @@ struct nvme_ns {
|
||||||
#define NVME_NS_FORCE_RO 3
|
#define NVME_NS_FORCE_RO 3
|
||||||
#define NVME_NS_READY 4
|
#define NVME_NS_READY 4
|
||||||
#define NVME_NS_SYSFS_ATTR_LINK 5
|
#define NVME_NS_SYSFS_ATTR_LINK 5
|
||||||
|
#define NVME_NS_CDEV_LIVE 6
|
||||||
|
|
||||||
struct cdev cdev;
|
struct cdev cdev;
|
||||||
struct device cdev_device;
|
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);
|
void *log, size_t size, u64 offset);
|
||||||
bool nvme_tryget_ns_head(struct nvme_ns_head *head);
|
bool nvme_tryget_ns_head(struct nvme_ns_head *head);
|
||||||
void nvme_put_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);
|
const struct file_operations *fops, struct module *owner);
|
||||||
void nvme_cdev_del(struct cdev *cdev, struct device *cdev_device);
|
void nvme_cdev_del(struct cdev *cdev, struct device *cdev_device);
|
||||||
int nvme_ioctl(struct block_device *bdev, blk_mode_t mode,
|
int nvme_ioctl(struct block_device *bdev, blk_mode_t mode,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user