nvme: fix RCU hole that allowed for endless looping in multipath round robin
authorChris Leech <cleech@redhat.com>
Mon, 21 Mar 2022 22:43:04 +0000 (15:43 -0700)
committerChristoph Hellwig <hch@lst.de>
Tue, 29 Mar 2022 07:29:06 +0000 (09:29 +0200)
Make nvme_ns_remove match the assumptions elsewhere.

1) !NVME_NS_READY needs to be srcu synchronized to make sure nothing is
   running in __nvme_find_path or nvme_round_robin_path that will
   re-assign this ns to current_path.

2) Any matching current_path entries need to be cleared before removing
   from the siblings list, to prevent calling nvme_round_robin_path with
   an "old" ns that's off list.

3) Finally the list_del_rcu can happen, and then synchronize again
   before releasing any reference counts.

Signed-off-by: Christoph Hellwig <hch@lst.de>
drivers/nvme/host/core.c

index 6dc05c1..a7d72ef 100644 (file)
@@ -4031,6 +4031,16 @@ static void nvme_ns_remove(struct nvme_ns *ns)
        set_capacity(ns->disk, 0);
        nvme_fault_inject_fini(&ns->fault_inject);
 
+       /*
+        * Ensure that !NVME_NS_READY is seen by other threads to prevent
+        * this ns going back into current_path.
+        */
+       synchronize_srcu(&ns->head->srcu);
+
+       /* wait for concurrent submissions */
+       if (nvme_mpath_clear_current_path(ns))
+               synchronize_srcu(&ns->head->srcu);
+
        mutex_lock(&ns->ctrl->subsys->lock);
        list_del_rcu(&ns->siblings);
        if (list_empty(&ns->head->list)) {
@@ -4042,10 +4052,6 @@ static void nvme_ns_remove(struct nvme_ns *ns)
        /* guarantee not available in head->list */
        synchronize_rcu();
 
-       /* wait for concurrent submissions */
-       if (nvme_mpath_clear_current_path(ns))
-               synchronize_srcu(&ns->head->srcu);
-
        if (!nvme_ns_head_multipath(ns->head))
                nvme_cdev_del(&ns->cdev, &ns->cdev_device);
        del_gendisk(ns->disk);