diff options
author | Ilya Dryomov <idryomov@gmail.com> | 2020-03-13 11:20:51 +0100 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2020-04-24 08:01:14 +0200 |
commit | fce4bd5793775570e461fc5d2da9b47e12a62c74 (patch) | |
tree | 850d4ca6b0c2320210c86d1eb08fdf74df00e161 /drivers/block | |
parent | c6c4c74a0fe1bbe0141311f1bcec9e175897fd99 (diff) |
rbd: avoid a deadlock on header_rwsem when flushing notifies
[ 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>
Diffstat (limited to 'drivers/block')
-rw-r--r-- | drivers/block/rbd.c | 17 |
1 files changed, 13 insertions, 4 deletions
diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c index f2b1994d58a0..fb1b9b8946f0 100644 --- a/drivers/block/rbd.c +++ b/drivers/block/rbd.c @@ -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; @@ -6136,6 +6146,8 @@ 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: rbd_dev->image_format = 0; @@ -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 */ |