Commit 64f5e9cd authored by Sagi Grimberg's avatar Sagi Grimberg Committed by Christoph Hellwig

nvmet: fix memory leak when removing namespaces and controllers concurrently

When removing a namespace, we add an NS_CHANGE async event, however if
the controller admin queue is removed after the event was added but not
yet processed, we won't free the aens, resulting in the below memory
leak [1].

Fix that by moving nvmet_async_event_free to the final controller
release after it is detached from subsys->ctrls ensuring no async
events are added, and modify it to simply remove all pending aens.

--
$ cat /sys/kernel/debug/kmemleak
unreferenced object 0xffff888c1af2c000 (size 32):
  comm "nvmetcli", pid 5164, jiffies 4295220864 (age 6829.924s)
  hex dump (first 32 bytes):
    28 01 82 3b 8b 88 ff ff 28 01 82 3b 8b 88 ff ff  (..;....(..;....
    02 00 04 65 76 65 6e 74 5f 66 69 6c 65 00 00 00  ...event_file...
  backtrace:
    [<00000000217ae580>] nvmet_add_async_event+0x57/0x290 [nvmet]
    [<0000000012aa2ea9>] nvmet_ns_changed+0x206/0x300 [nvmet]
    [<00000000bb3fd52e>] nvmet_ns_disable+0x367/0x4f0 [nvmet]
    [<00000000e91ca9ec>] nvmet_ns_free+0x15/0x180 [nvmet]
    [<00000000a15deb52>] config_item_release+0xf1/0x1c0
    [<000000007e148432>] configfs_rmdir+0x555/0x7c0
    [<00000000f4506ea6>] vfs_rmdir+0x142/0x3c0
    [<0000000000acaaf0>] do_rmdir+0x2b2/0x340
    [<0000000034d1aa52>] do_syscall_64+0xa5/0x4d0
    [<00000000211f13bc>] entry_SYSCALL_64_after_hwframe+0x6a/0xdf

Fixes: a07b4970 ("nvmet: add a generic NVMe target")
Reported-by: default avatarDavid Milburn <dmilburn@redhat.com>
Signed-off-by: default avatarSagi Grimberg <sagi@grimberg.me>
Tested-by: default avatarDavid Milburn <dmilburn@redhat.com>
Signed-off-by: default avatarChristoph Hellwig <hch@lst.de>
parent b09160c3
...@@ -158,14 +158,12 @@ static void nvmet_async_events_process(struct nvmet_ctrl *ctrl, u16 status) ...@@ -158,14 +158,12 @@ static void nvmet_async_events_process(struct nvmet_ctrl *ctrl, u16 status)
static void nvmet_async_events_free(struct nvmet_ctrl *ctrl) static void nvmet_async_events_free(struct nvmet_ctrl *ctrl)
{ {
struct nvmet_req *req; struct nvmet_async_event *aen, *tmp;
mutex_lock(&ctrl->lock); mutex_lock(&ctrl->lock);
while (ctrl->nr_async_event_cmds) { list_for_each_entry_safe(aen, tmp, &ctrl->async_events, entry) {
req = ctrl->async_event_cmds[--ctrl->nr_async_event_cmds]; list_del(&aen->entry);
mutex_unlock(&ctrl->lock); kfree(aen);
nvmet_req_complete(req, NVME_SC_INTERNAL | NVME_SC_DNR);
mutex_lock(&ctrl->lock);
} }
mutex_unlock(&ctrl->lock); mutex_unlock(&ctrl->lock);
} }
...@@ -791,10 +789,8 @@ void nvmet_sq_destroy(struct nvmet_sq *sq) ...@@ -791,10 +789,8 @@ void nvmet_sq_destroy(struct nvmet_sq *sq)
* If this is the admin queue, complete all AERs so that our * If this is the admin queue, complete all AERs so that our
* queue doesn't have outstanding requests on it. * queue doesn't have outstanding requests on it.
*/ */
if (ctrl && ctrl->sqs && ctrl->sqs[0] == sq) { if (ctrl && ctrl->sqs && ctrl->sqs[0] == sq)
nvmet_async_events_process(ctrl, status); nvmet_async_events_process(ctrl, status);
nvmet_async_events_free(ctrl);
}
percpu_ref_kill_and_confirm(&sq->ref, nvmet_confirm_sq); percpu_ref_kill_and_confirm(&sq->ref, nvmet_confirm_sq);
wait_for_completion(&sq->confirm_done); wait_for_completion(&sq->confirm_done);
wait_for_completion(&sq->free_done); wait_for_completion(&sq->free_done);
...@@ -1427,6 +1423,7 @@ static void nvmet_ctrl_free(struct kref *ref) ...@@ -1427,6 +1423,7 @@ static void nvmet_ctrl_free(struct kref *ref)
ida_simple_remove(&cntlid_ida, ctrl->cntlid); ida_simple_remove(&cntlid_ida, ctrl->cntlid);
nvmet_async_events_free(ctrl);
kfree(ctrl->sqs); kfree(ctrl->sqs);
kfree(ctrl->cqs); kfree(ctrl->cqs);
kfree(ctrl->changed_ns_list); kfree(ctrl->changed_ns_list);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment