Skip to content

Commit

Permalink
loop: Fix mount(2) failure due to race with LOOP_SET_FD
Browse files Browse the repository at this point in the history
Commit 33ec3e5 ("loop: Don't change loop device under exclusive
opener") made LOOP_SET_FD ioctl acquire exclusive block device reference
while it updates loop device binding. However this can make perfectly
valid mount(2) fail with EBUSY due to racing LOOP_SET_FD holding
temporarily the exclusive bdev reference in cases like this:

for i in {a..z}{a..z}; do
        dd if=/dev/zero of=$i.image bs=1k count=0 seek=1024
        mkfs.ext2 $i.image
        mkdir mnt$i
done

echo "Run"
for i in {a..z}{a..z}; do
        mount -o loop -t ext2 $i.image mnt$i &
done

Fix the problem by not getting full exclusive bdev reference in
LOOP_SET_FD but instead just mark the bdev as being claimed while we
update the binding information. This just blocks new exclusive openers
instead of failing them with EBUSY thus fixing the problem.

Fixes: 33ec3e5 ("loop: Don't change loop device under exclusive opener")
Cc: stable@vger.kernel.org
Tested-by: Kai-Heng Feng <kai.heng.feng@canonical.com>
Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
  • Loading branch information
jankara authored and axboe committed Jul 30, 2019
1 parent 71d6c50 commit 89e524c
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 32 deletions.
16 changes: 9 additions & 7 deletions drivers/block/loop.c
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,7 @@ static int loop_set_fd(struct loop_device *lo, fmode_t mode,
struct file *file;
struct inode *inode;
struct address_space *mapping;
struct block_device *claimed_bdev = NULL;
int lo_flags = 0;
int error;
loff_t size;
Expand All @@ -942,10 +943,11 @@ static int loop_set_fd(struct loop_device *lo, fmode_t mode,
* here to avoid changing device under exclusive owner.
*/
if (!(mode & FMODE_EXCL)) {
bdgrab(bdev);
error = blkdev_get(bdev, mode | FMODE_EXCL, loop_set_fd);
if (error)
claimed_bdev = bd_start_claiming(bdev, loop_set_fd);
if (IS_ERR(claimed_bdev)) {
error = PTR_ERR(claimed_bdev);
goto out_putf;
}
}

error = mutex_lock_killable(&loop_ctl_mutex);
Expand Down Expand Up @@ -1015,15 +1017,15 @@ static int loop_set_fd(struct loop_device *lo, fmode_t mode,
mutex_unlock(&loop_ctl_mutex);
if (partscan)
loop_reread_partitions(lo, bdev);
if (!(mode & FMODE_EXCL))
blkdev_put(bdev, mode | FMODE_EXCL);
if (claimed_bdev)
bd_abort_claiming(bdev, claimed_bdev, loop_set_fd);
return 0;

out_unlock:
mutex_unlock(&loop_ctl_mutex);
out_bdev:
if (!(mode & FMODE_EXCL))
blkdev_put(bdev, mode | FMODE_EXCL);
if (claimed_bdev)
bd_abort_claiming(bdev, claimed_bdev, loop_set_fd);
out_putf:
fput(file);
out:
Expand Down
83 changes: 58 additions & 25 deletions fs/block_dev.c
Original file line number Diff line number Diff line change
Expand Up @@ -1181,8 +1181,7 @@ static struct gendisk *bdev_get_gendisk(struct block_device *bdev, int *partno)
* Pointer to the block device containing @bdev on success, ERR_PTR()
* value on failure.
*/
static struct block_device *bd_start_claiming(struct block_device *bdev,
void *holder)
struct block_device *bd_start_claiming(struct block_device *bdev, void *holder)
{
struct gendisk *disk;
struct block_device *whole;
Expand Down Expand Up @@ -1229,6 +1228,62 @@ static struct block_device *bd_start_claiming(struct block_device *bdev,
return ERR_PTR(err);
}
}
EXPORT_SYMBOL(bd_start_claiming);

static void bd_clear_claiming(struct block_device *whole, void *holder)
{
lockdep_assert_held(&bdev_lock);
/* tell others that we're done */
BUG_ON(whole->bd_claiming != holder);
whole->bd_claiming = NULL;
wake_up_bit(&whole->bd_claiming, 0);
}

/**
* bd_finish_claiming - finish claiming of a block device
* @bdev: block device of interest
* @whole: whole block device (returned from bd_start_claiming())
* @holder: holder that has claimed @bdev
*
* Finish exclusive open of a block device. Mark the device as exlusively
* open by the holder and wake up all waiters for exclusive open to finish.
*/
void bd_finish_claiming(struct block_device *bdev, struct block_device *whole,
void *holder)
{
spin_lock(&bdev_lock);
BUG_ON(!bd_may_claim(bdev, whole, holder));
/*
* Note that for a whole device bd_holders will be incremented twice,
* and bd_holder will be set to bd_may_claim before being set to holder
*/
whole->bd_holders++;
whole->bd_holder = bd_may_claim;
bdev->bd_holders++;
bdev->bd_holder = holder;
bd_clear_claiming(whole, holder);
spin_unlock(&bdev_lock);
}
EXPORT_SYMBOL(bd_finish_claiming);

/**
* bd_abort_claiming - abort claiming of a block device
* @bdev: block device of interest
* @whole: whole block device (returned from bd_start_claiming())
* @holder: holder that has claimed @bdev
*
* Abort claiming of a block device when the exclusive open failed. This can be
* also used when exclusive open is not actually desired and we just needed
* to block other exclusive openers for a while.
*/
void bd_abort_claiming(struct block_device *bdev, struct block_device *whole,
void *holder)
{
spin_lock(&bdev_lock);
bd_clear_claiming(whole, holder);
spin_unlock(&bdev_lock);
}
EXPORT_SYMBOL(bd_abort_claiming);

#ifdef CONFIG_SYSFS
struct bd_holder_disk {
Expand Down Expand Up @@ -1698,29 +1753,7 @@ int blkdev_get(struct block_device *bdev, fmode_t mode, void *holder)

/* finish claiming */
mutex_lock(&bdev->bd_mutex);
spin_lock(&bdev_lock);

if (!res) {
BUG_ON(!bd_may_claim(bdev, whole, holder));
/*
* Note that for a whole device bd_holders
* will be incremented twice, and bd_holder
* will be set to bd_may_claim before being
* set to holder
*/
whole->bd_holders++;
whole->bd_holder = bd_may_claim;
bdev->bd_holders++;
bdev->bd_holder = holder;
}

/* tell others that we're done */
BUG_ON(whole->bd_claiming != holder);
whole->bd_claiming = NULL;
wake_up_bit(&whole->bd_claiming, 0);

spin_unlock(&bdev_lock);

bd_finish_claiming(bdev, whole, holder);
/*
* Block event polling for write claims if requested. Any
* write holder makes the write_holder state stick until
Expand Down
6 changes: 6 additions & 0 deletions include/linux/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -2598,6 +2598,12 @@ extern struct block_device *blkdev_get_by_path(const char *path, fmode_t mode,
void *holder);
extern struct block_device *blkdev_get_by_dev(dev_t dev, fmode_t mode,
void *holder);
extern struct block_device *bd_start_claiming(struct block_device *bdev,
void *holder);
extern void bd_finish_claiming(struct block_device *bdev,
struct block_device *whole, void *holder);
extern void bd_abort_claiming(struct block_device *bdev,
struct block_device *whole, void *holder);
extern void blkdev_put(struct block_device *bdev, fmode_t mode);
extern int __blkdev_reread_part(struct block_device *bdev);
extern int blkdev_reread_part(struct block_device *bdev);
Expand Down

0 comments on commit 89e524c

Please sign in to comment.