[PATCH RFC v2 34/42] loop: refuse files on pseudo filesystems as backing files
From: Christian Brauner
Date: Sat Nov 14 2026 - 08:15:43 EST
Currently loop accepts any regular file that implements ->read_iter() as
a backing file and any that also implements ->write_iter() as a writable
one. That includes files on procfs, sysfs and other pseudo filesystems.
For example, /proc/kcore attaches read-only as a loop device the size of
the kernel's virtual address space, 64 PiB with 5-level paging. sysfs
attributes attach read-write as 4096 byte loop devices. Writing a block
to the loop device of /sys/kernel/mm/transparent_hugepage/enabled
changes the THP setting.
That's not just nonsensical, it can be abused. Loop calls ->read_iter()
and ->write_iter() of the backing file directly from its worker which
runs with kernel credentials. But pseudo files enforce part of their
access rules at read and write time against current. Dozens of sysfs
->show() and ->store() methods call capable(). Behind a loop device all
of these checks are done against the loop worker instead of the task
that opened the file. And setting up a loop device requires no
capabilities, only access to the device nodes. So on v7.4-rc1:
- uid 0 without any capabilities can't write to /sys/kernel/reboot/mode
as mode_store() requires CAP_SYS_BOOT. Writing "warm" through a loop
device works.
- uid 0 without any capabilities can't read the files in
/sys/firmware/dmi/entries/*/ as dmi_sysfs_attr_show() requires
CAP_SYS_ADMIN. Reading them through a loop device works.
- An unprivileged container that was handed a loop device can't change
gro_flush_timeout of its own network devices as
gro_flush_timeout_store() requires CAP_NET_ADMIN in the initial user
namespace. Writing it through a loop device works.
The same goes for hard_offline_page, the md and sd attributes, rfkill
and others.
Refuse files on pseudo filesystems as loop backing files by switching to
file_check_backing(). Note that it looks through overlayfs so stacking
overlayfs on top of sysfs doesn't get around this.
The helper also does the file type, open mode and method checks that
loop did itself so far. The check for a regular file or a block device
now happens before the loop device is locked and it looks at the inode
of the file instead of the host of ->f_mapping. For regular files and
block devices both have the same type.
Signed-off-by: Christian Brauner (Amutable) <brauner@xxxxxxxxxx>
---
drivers/block/loop.c | 17 ++++-------------
1 file changed, 4 insertions(+), 13 deletions(-)
diff --git a/drivers/block/loop.c b/drivers/block/loop.c
index c96e35c3ef423..776bd063f900c 100644
--- a/drivers/block/loop.c
+++ b/drivers/block/loop.c
@@ -492,7 +492,6 @@ static inline int is_loop_device(struct file *file)
static int loop_validate_file(struct file *file, struct block_device *bdev)
{
- struct inode *inode = file->f_mapping->host;
struct file *f = file;
/* Avoid recursion */
@@ -510,8 +509,6 @@ static int loop_validate_file(struct file *file, struct block_device *bdev)
rmb();
f = l->lo_backing_file;
}
- if (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))
- return -EINVAL;
return 0;
}
@@ -528,16 +525,10 @@ static void loop_assign_backing_file(struct loop_device *lo, struct file *file)
static int loop_check_backing_file(struct file *file)
{
- if (!(file->f_mode & FMODE_READ))
- return -EBADF;
-
- if (!file->f_op->read_iter)
- return -EINVAL;
-
- if ((file->f_mode & FMODE_WRITE) && !file->f_op->write_iter)
- return -EINVAL;
-
- return 0;
+ /* A backing file that isn't open for writing gives a read-only device. */
+ return file_check_backing(file,
+ FMODE_READ | (file->f_mode & FMODE_WRITE),
+ S_IFBLK);
}
/* loop sysfs attributes */
--
2.53.0