block/bdev.c | 4 +++- drivers/block/loop.c | 12 ++++++++++++ include/linux/blkdev.h | 1 + 3 files changed, 16 insertions(+), 1 deletion(-)
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
On Mon, Mar 30, 2026 at 10:13:34AM +0530, Deepanshu Kartikey wrote:
> 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.
Increasing the size certainly does not do that.
> Export bdev_writes_blocked() so it can be used from the loop driver.
I'm not sure exporting this is a good idea. Besides the growing the
device part above, if someone insist on changing the size, they could
do this just fine with a remove block device, so prohibiting it locally
just because we can seems odd. And exporting a helper with obscure
usage for a strange use case without documenting that is rarely a
good idea.
> -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)
... and if we were to make it public it should be inline, and in a
separate patch.
And if not this would still add a spurious empty line.
On Sun, Mar 29, 2026 at 10:54:12PM -0700, Christoph Hellwig wrote:
> On Mon, Mar 30, 2026 at 10:13:34AM +0530, Deepanshu Kartikey wrote:
> > 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.
>
> Increasing the size certainly does not do that.
>
> > Export bdev_writes_blocked() so it can be used from the loop driver.
>
> I'm not sure exporting this is a good idea. Besides the growing the
> device part above, if someone insist on changing the size, they could
> do this just fine with a remove block device, so prohibiting it locally
> just because we can seems odd. And exporting a helper with obscure
> usage for a strange use case without documenting that is rarely a
> good idea.
The patch was missing the...
#ifndef CONFIG_BLK_DEV_WRITE_MOUNTED)
... in loop.c which was in my original sketch of a patch which I sent
to Deepanshu.
The intent was to suppress Syzkaller noise. Inherent in my assumption
was that changing either lo_offset or lo_sizelimit parameter was going
to mutate the loop device when it was mounted, which was the intent of
!CONFIG_BLK_DEV_WRITE_MOUNTED. I also assumed that at least for now,
the only user of !CONFIG_BLK_DEV_WRITE_MOUNTED was syzkaller, since
there are file systems (in particular ext4) that are commonly in use
whose userspace utilities fundamentally assume that you can write to
the block device. Even after we promulgate the tune2fs changes to use
the new EXT4_IOC_SET_TUNE_SB_PARAM ioctl and we can assume that it is
common use in deployed kernels, some grub operations still assume they
can modify the block device where the second-stage grub bootloader is
located.
In the long term, when ext4 can start assuming that we can suppress
writes to the block device (and we find a solution to the grub issue),
we'll probably want to have a way to suppress write access to block
devices on a per-super basis, without clearing the Kconfig
CONFIG_BLK_DEV_WRITE_MOUNTED. But that's a problem for another day...
Cheers,
- Ted
On Mon, Mar 30, 2026 at 7:08 PM Theodore Tso <tytso@mit.edu> wrote:
>
> The patch was missing the...
>
> #ifndef CONFIG_BLK_DEV_WRITE_MOUNTED)
>
> ... in loop.c which was in my original sketch of a patch which I sent
> to Deepanshu.
>
> The intent was to suppress Syzkaller noise. Inherent in my assumption
> was that changing either lo_offset or lo_sizelimit parameter was going
> to mutate the loop device when it was mounted, which was the intent of
> !CONFIG_BLK_DEV_WRITE_MOUNTED. I also assumed that at least for now,
> the only user of !CONFIG_BLK_DEV_WRITE_MOUNTED was syzkaller, since
> there are file systems (in particular ext4) that are commonly in use
> whose userspace utilities fundamentally assume that you can write to
> the block device. Even after we promulgate the tune2fs changes to use
> the new EXT4_IOC_SET_TUNE_SB_PARAM ioctl and we can assume that it is
> common use in deployed kernels, some grub operations still assume they
> can modify the block device where the second-stage grub bootloader is
> located.
>
> In the long term, when ext4 can start assuming that we can suppress
> writes to the block device (and we find a solution to the grub issue),
> we'll probably want to have a way to suppress write access to block
> devices on a per-super basis, without clearing the Kconfig
> CONFIG_BLK_DEV_WRITE_MOUNTED. But that's a problem for another day...
>
> Cheers,
>
> - Ted
Hi Ted, Christoph,
Thank you both for the feedback. Based on your suggestions, I'll
submit a v2 with the following approach:
- Use #ifndef CONFIG_BLK_DEV_WRITE_MOUNTED as Ted originally
intended, since this is meant to suppress syzkaller noise
- Check bd_writers directly instead of exporting
bdev_writes_blocked(), addressing Christoph's concern
- No changes to block/bdev.c or include/linux/blkdev.h, keeping
the patch minimal and limited to drivers/block/loop.c
The check would be:
#ifndef CONFIG_BLK_DEV_WRITE_MOUNTED
if ((lo->lo_offset != info->lo_offset ||
lo->lo_sizelimit != info->lo_sizelimit) &&
lo->lo_device->bd_writers < 0) {
err = -EBUSY;
goto out_unlock;
}
#endif
Regarding Christoph's point about increasing lo_sizelimit being
safe - should I narrow the check to only block shrinking and
offset changes?
Thanks,
Deepanshu
On Mon, Mar 30, 2026 at 08:03:55PM +0530, Deepanshu Kartikey wrote:
>
> The check would be:
>
> #ifndef CONFIG_BLK_DEV_WRITE_MOUNTED
> if ((lo->lo_offset != info->lo_offset ||
> lo->lo_sizelimit != info->lo_sizelimit) &&
> lo->lo_device->bd_writers < 0) {
> err = -EBUSY;
> goto out_unlock;
> }
> #endif
Can you please move bdev_writes_blocked() into
include/linux/blk_types.h as an inline function as Christoph
suggested?
The reason for that is that "bdev_writers < 0" is an implementation
detail and it might change in the future, and then loop driver might
break unexpectedly. Using bdev_writes_blocked() is also much clearer
from a code readability perspective.
> Regarding Christoph's point about increasing lo_sizelimit being
> safe - should I narrow the check to only block shrinking and
> offset changes?
Yes, I think we should allow the loop device to grow, since that's
harmless and there are some legitimate use cases when people might
want to do this and then trigger an online resize so the file system
grows to use the added space.
- Ted
© 2016 - 2026 Red Hat, Inc.