Commit c76f48eb5c08 ("block: take bd_mutex around delete_partitions in del_gendisk") adds disk->part0->bd_mutex in del_gendisk(), this way causes the following AB/BA deadlock between removing loop and opening loop: 1) loop_control_ioctl(LOOP_CTL_REMOVE) - mutex_lock(&loop_ctl_mutex) - mutex_lock(&disk->part0->bd_mutex) //del_gendisk 2) open look device - mutex_lock(&disk->part0->bd_mutex) //blkdev_get_by_dev - mutex_lock(&loop_ctl_mutex) //lo_open() <- __blkdev_get Fixes the issue by not holding loop_ctl_mutex in lo_open(), and cover the protection on bdev->bd_disk->private_data via disk->part0->bd_mutex. Reported-by: Colin Ian King <colin.king@xxxxxxxxxxxxx> Fixes: c76f48eb5c08 ("block: take bd_mutex around delete_partitions in del_gendisk") Cc: Pavel Tatashin <pasha.tatashin@xxxxxxxxxx> Signed-off-by: Ming Lei <ming.lei@xxxxxxxxxx> --- drivers/block/loop.c | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/drivers/block/loop.c b/drivers/block/loop.c index d58d68f3c7cd..b03d8f4c1cdf 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -1883,20 +1883,14 @@ static int lo_open(struct block_device *bdev, fmode_t mode) int err; /* - * take loop_ctl_mutex to protect lo pointer from race with - * loop_control_ioctl(LOOP_CTL_REMOVE), however, to reduce contention - * release it prior to updating lo->lo_refcnt. + * both ->private_data and ->lo_refcnt are covered by disk's + * open_mutex, so race between open and remove can be avoided */ - err = mutex_lock_killable(&loop_ctl_mutex); - if (err) - return err; lo = bdev->bd_disk->private_data; - if (!lo) { - mutex_unlock(&loop_ctl_mutex); + if (!lo) return -ENXIO; - } + err = mutex_lock_killable(&lo->lo_mutex); - mutex_unlock(&loop_ctl_mutex); if (err) return err; atomic_inc(&lo->lo_refcnt); @@ -2272,21 +2266,26 @@ static long loop_control_ioctl(struct file *file, unsigned int cmd, ret = loop_lookup(&lo, parm); if (ret < 0) break; - ret = mutex_lock_killable(&lo->lo_mutex); + /* cover removing vs. opening loop device */ + ret = mutex_lock_killable(&lo->lo_disk->part0->bd_mutex); if (ret) break; - if (lo->lo_state != Lo_unbound) { - ret = -EBUSY; - mutex_unlock(&lo->lo_mutex); + ret = mutex_lock_killable(&lo->lo_mutex); + if (ret) { + mutex_unlock(&lo->lo_disk->part0->bd_mutex); break; } - if (atomic_read(&lo->lo_refcnt) > 0) { + if (lo->lo_state != Lo_unbound || + atomic_read(&lo->lo_refcnt) > 0) { ret = -EBUSY; mutex_unlock(&lo->lo_mutex); + mutex_unlock(&lo->lo_disk->part0->bd_mutex); break; } - lo->lo_disk->private_data = NULL; mutex_unlock(&lo->lo_mutex); + lo->lo_disk->private_data = NULL; + mutex_unlock(&lo->lo_disk->part0->bd_mutex); + idr_remove(&loop_index_idr, lo->lo_number); loop_remove(lo); break; -- 2.29.2