[PATCH] loop: block loop reconfiguration of offset/sizelimit on mounted device

Deepanshu Kartikey
2026-03-30T04:43:42Z

LOOP_SET_STATUS{64} allows changing lo_offset and lo_sizelimit while
a filesystem is mounted on the loop device. This effectively mutates
the data visible to the mounted filesystem, which is equivalent to
writing directly to the block device.

When bdev_allow_write_mounted is false, direct writes to a mounted
block device are blocked via bdev_writes_blocked(). However,
LOOP_SET_STATUS{64} bypasses this protection because it modifies
the loop configuration through an ioctl rather than opening the
block device for writing.

Fix this by checking bdev_writes_blocked() before allowing changes
to lo_offset or lo_sizelimit. If the loop device has writes blocked
(indicating a filesystem is mounted with write protection), return
-EBUSY. Other loop status fields that do not affect the visible
data can still be changed while mounted.

Export bdev_writes_blocked() so it can be used from the loop driver.

Suggested-by: Theodore Ts'o <tytso@mit.edu>
Reported-by: syzbot+fb32afec111a7d61b939@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=fb32afec111a7d61b939
Tested-by: syzbot+fb32afec111a7d61b939@syzkaller.appspotmail.com
Signed-off-by: Deepanshu Kartikey <kartikey406@gmail.com>
---
block/bdev.c | 4 +++-
drivers/block/loop.c | 12 ++++++++++++
include/linux/blkdev.h | 1 +
3 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/block/bdev.c b/block/bdev.c
index ed022f8c48c7..96520fac7b2f 100644
--- a/block/bdev.c
+++ b/block/bdev.c
@@ -860,10 +860,12 @@ void blkdev_put_no_open(struct block_device *bdev)
put_device(&bdev->bd_device);
}

-static bool bdev_writes_blocked(struct block_device *bdev)
+bool bdev_writes_blocked(struct block_device *bdev)
{
return bdev->bd_writers < 0;
}
+EXPORT_SYMBOL_GPL(bdev_writes_blocked);
+

static void bdev_block_writes(struct block_device *bdev)
{
diff --git a/drivers/block/loop.c b/drivers/block/loop.c
index 0000913f7efc..3f3a29abad1f 100644
--- a/drivers/block/loop.c
+++ b/drivers/block/loop.c
@@ -1239,6 +1239,18 @@ loop_set_status(struct loop_device *lo, const struct loop_info64 *info)
goto out_unlock;
}

+ /*
+ * Changing lo_offset or lo_sizelimit on a mounted device is
+ * equivalent to modifying the block device contents, block
+ * this if writes are blocked on the device.
+ */
+ if ((lo->lo_offset != info->lo_offset ||
+ lo->lo_sizelimit != info->lo_sizelimit) &&
+ bdev_writes_blocked(lo->lo_device)) {
+ err = -EBUSY;
+ goto out_unlock;
+ }
+
if (lo->lo_offset != info->lo_offset ||
lo->lo_sizelimit != info->lo_sizelimit) {
size_changed = true;
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index d463b9b5a0a5..6b908e9dd035 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -820,6 +820,7 @@ static inline bool bdev_read_only(struct block_device *bdev)
return bdev_test_flag(bdev, BD_READ_ONLY) || get_disk_ro(bdev->bd_disk);
}

+bool bdev_writes_blocked(struct block_device *bdev);
bool set_capacity_and_notify(struct gendisk *disk, sector_t size);
void disk_force_media_change(struct gendisk *disk);
void bdev_mark_dead(struct block_device *bdev, bool surprise);
--
2.43.0