[PATCH v2] loop: block changing lo_offset/lo_sizelimit on mounted device

From: Deepanshu Kartikey

Date: Tue Mar 31 2026 - 01:47:33 EST


LOOP_SET_STATUS{64} allows changing lo_offset and shrinking
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 CONFIG_BLK_DEV_WRITE_MOUNTED is disabled, direct writes to a
mounted block device are 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 shrinking lo_sizelimit. If the loop device has
writes blocked, return -EBUSY. Increasing lo_sizelimit is still
allowed since growing the device is harmless and has legitimate
use cases such as online resize.

Move bdev_writes_blocked() from block/bdev.c to
include/linux/blk_types.h as a static inline function so it can
be used from the loop driver without exporting a symbol.

Suggested-by: Theodore Ts'o <tytso@xxxxxxx>
Reported-by: syzbot+fb32afec111a7d61b939@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=fb32afec111a7d61b939
Tested-by: syzbot+fb32afec111a7d61b939@xxxxxxxxxxxxxxxxxxxxxxxxx
Signed-off-by: Deepanshu Kartikey <kartikey406@xxxxxxxxx>
---
v2:
- Use #ifndef CONFIG_BLK_DEV_WRITE_MOUNTED instead of exporting
bdev_writes_blocked(), as suggested by Ted Ts'o
- Move bdev_writes_blocked() to include/linux/blk_types.h as
static inline instead of exporting from block/bdev.c, as
suggested by Christoph Hellwig
- Allow increasing lo_sizelimit since growing the device is
harmless, as pointed out by Christoph Hellwig
- Remove spurious empty line
---
block/bdev.c | 5 -----
drivers/block/loop.c | 16 ++++++++++++++++
include/linux/blk_types.h | 5 +++++
3 files changed, 21 insertions(+), 5 deletions(-)

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

-static bool bdev_writes_blocked(struct block_device *bdev)
-{
- return bdev->bd_writers < 0;
-}
-
static void bdev_block_writes(struct block_device *bdev)
{
bdev->bd_writers--;
diff --git a/drivers/block/loop.c b/drivers/block/loop.c
index 0000913f7efc..34bbbf3bcb36 100644
--- a/drivers/block/loop.c
+++ b/drivers/block/loop.c
@@ -1239,6 +1239,22 @@ loop_set_status(struct loop_device *lo, const struct loop_info64 *info)
goto out_unlock;
}

+#ifndef CONFIG_BLK_DEV_WRITE_MOUNTED
+ /*
+ * Changing lo_offset or shrinking lo_sizelimit on a mounted
+ * device is equivalent to modifying the block device contents.
+ * Block this if writes to the device are blocked.
+ */
+ if ((lo->lo_offset != info->lo_offset ||
+ (info->lo_sizelimit &&
+ (lo->lo_sizelimit == 0 ||
+ info->lo_sizelimit < lo->lo_sizelimit))) &&
+ bdev_writes_blocked(lo->lo_device)) {
+ err = -EBUSY;
+ goto out_unlock;
+ }
+#endif
+
if (lo->lo_offset != info->lo_offset ||
lo->lo_sizelimit != info->lo_sizelimit) {
size_changed = true;
diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h
index 8808ee76e73c..82ece8737b85 100644
--- a/include/linux/blk_types.h
+++ b/include/linux/blk_types.h
@@ -84,6 +84,11 @@ struct block_device {
#define bdev_whole(_bdev) \
((_bdev)->bd_disk->part0)

+static inline bool bdev_writes_blocked(struct block_device *bdev)
+{
+ return bdev->bd_writers < 0;
+}
+
#define dev_to_bdev(device) \
container_of((device), struct block_device, bd_device)

--
2.43.0