loop: Don't change loop device under exclusive opener
authorJan Kara <jack@suse.cz>
Thu, 16 May 2019 14:01:27 +0000 (16:01 +0200)
committerJens Axboe <axboe@kernel.dk>
Mon, 27 May 2019 13:34:04 +0000 (07:34 -0600)
Loop module allows calling LOOP_SET_FD while there are other openers of
the loop device. Even exclusive ones. This can lead to weird
consequences such as kernel deadlocks like:

mount_bdev() lo_ioctl()
  udf_fill_super()
    udf_load_vrs()
      sb_set_blocksize() - sets desired block size B
      udf_tread()
        sb_bread()
          __bread_gfp(bdev, block, B)
  loop_set_fd()
    set_blocksize()
            - now __getblk_slow() indefinitely loops because B != bdev
              block size

Fix the problem by disallowing LOOP_SET_FD ioctl when there are
exclusive openers of a loop device.

[Deliberately chosen not to CC stable as a user with priviledges to
trigger this race has other means of taking the system down and this
has a potential of breaking some weird userspace setup]

Reported-and-tested-by: syzbot+10007d66ca02b08f0e60@syzkaller.appspotmail.com
Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
drivers/block/loop.c

index 102d795..f11b7dc 100644 (file)
@@ -945,9 +945,20 @@ static int loop_set_fd(struct loop_device *lo, fmode_t mode,
        if (!file)
                goto out;
 
+       /*
+        * If we don't hold exclusive handle for the device, upgrade to it
+        * 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)
+                       goto out_putf;
+       }
+
        error = mutex_lock_killable(&loop_ctl_mutex);
        if (error)
-               goto out_putf;
+               goto out_bdev;
 
        error = -EBUSY;
        if (lo->lo_state != Lo_unbound)
@@ -1012,10 +1023,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);
        return 0;
 
 out_unlock:
        mutex_unlock(&loop_ctl_mutex);
+out_bdev:
+       if (!(mode & FMODE_EXCL))
+               blkdev_put(bdev, mode | FMODE_EXCL);
 out_putf:
        fput(file);
 out: