]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
rbd: avoid a deadlock on header_rwsem when flushing notifies
authorIlya Dryomov <idryomov@gmail.com>
Fri, 13 Mar 2020 10:20:51 +0000 (11:20 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 24 Apr 2020 06:01:14 +0000 (08:01 +0200)
[ Upstream commit 0e4e1de5b63fa423b13593337a27fd2d2b0bcf77 ]

rbd_unregister_watch() flushes notifies and therefore cannot be called
under header_rwsem because a header update notify takes header_rwsem to
synchronize with "rbd map".  If mapping an image fails after the watch
is established and a header update notify sneaks in, we deadlock when
erroring out from rbd_dev_image_probe().

Move watch registration and unregistration out of the critical section.
The only reason they were put there was to make header_rwsem management
slightly more obvious.

Fixes: 811c66887746 ("rbd: fix rbd map vs notify races")
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
Reviewed-by: Jason Dillaman <dillaman@redhat.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/block/rbd.c

index f2b1994d58a067e9b81bbd30af9327fcf7f601fe..fb1b9b8946f076c10d600cb4a6516a85ab17e810 100644 (file)
@@ -3847,6 +3847,10 @@ static void cancel_tasks_sync(struct rbd_device *rbd_dev)
        cancel_work_sync(&rbd_dev->unlock_work);
 }
 
+/*
+ * header_rwsem must not be held to avoid a deadlock with
+ * rbd_dev_refresh() when flushing notifies.
+ */
 static void rbd_unregister_watch(struct rbd_device *rbd_dev)
 {
        WARN_ON(waitqueue_active(&rbd_dev->lock_waitq));
@@ -6057,6 +6061,9 @@ static void rbd_dev_image_release(struct rbd_device *rbd_dev)
  * device.  If this image is the one being mapped (i.e., not a
  * parent), initiate a watch on its header object before using that
  * object to get detailed information about the rbd image.
+ *
+ * On success, returns with header_rwsem held for write if called
+ * with @depth == 0.
  */
 static int rbd_dev_image_probe(struct rbd_device *rbd_dev, int depth)
 {
@@ -6087,6 +6094,9 @@ static int rbd_dev_image_probe(struct rbd_device *rbd_dev, int depth)
                }
        }
 
+       if (!depth)
+               down_write(&rbd_dev->header_rwsem);
+
        ret = rbd_dev_header_info(rbd_dev);
        if (ret)
                goto err_out_watch;
@@ -6135,6 +6145,8 @@ static int rbd_dev_image_probe(struct rbd_device *rbd_dev, int depth)
 err_out_probe:
        rbd_dev_unprobe(rbd_dev);
 err_out_watch:
+       if (!depth)
+               up_write(&rbd_dev->header_rwsem);
        if (!depth)
                rbd_unregister_watch(rbd_dev);
 err_out_format:
@@ -6194,12 +6206,9 @@ static ssize_t do_rbd_add(struct bus_type *bus,
                goto err_out_rbd_dev;
        }
 
-       down_write(&rbd_dev->header_rwsem);
        rc = rbd_dev_image_probe(rbd_dev, 0);
-       if (rc < 0) {
-               up_write(&rbd_dev->header_rwsem);
+       if (rc < 0)
                goto err_out_rbd_dev;
-       }
 
        /* If we are mapping a snapshot it must be marked read-only */