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: a07b4970f4 ("nvmet: add a generic NVMe target")
Reported-by: David Milburn <dmilburn@redhat.com>
Signed-off-by: Sagi Grimberg <sagi@grimberg.me>
Tested-by: David Milburn <dmilburn@redhat.com>
Signed-off-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
Sagi Grimberg 2020-05-20 12:48:12 -07:00 коммит произвёл Christoph Hellwig
Родитель b09160c399
Коммит 64f5e9cdd7
1 изменённых файлов: 6 добавлений и 9 удалений

Просмотреть файл

@ -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)
* 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)
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);