loop_configure() takes any file as long as it implements ->read_iter(). Loop itself never checked whether the file was actually opened for reading. Buffered I/O went through vfs_iter_read() which refuses files without FMODE_READ. Direct I/O has called ->read_iter() of the backing file directly ever since loop grew support for it. Since commit f2fed441c69b ("loop: stop using vfs_iter_{read,write} for buffered I/O") buffered I/O calls ->read_iter() directly as well. That commit was about O_DIRECT handling and the FMODE_READ check went away as a side effect. So whoever can set up a loop device can read a file it was only allowed to open for writing. Open the file O_WRONLY, configure a loop device with it and read the contents through the loop device. Refuse backing files that aren't open for reading. Loop always reads from its backing file so a write-only backing file never made sense in the first place. Fixes: bc07c10a3603 ("block: loop: support DIO & AIO") Fixes: f2fed441c69b ("loop: stop using vfs_iter_{read,write} for buffered I/O") Cc: stable@vger.kernel.org Reviewed-by: Jan Kara Reviewed-by: Christoph Hellwig Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- drivers/block/loop.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/block/loop.c b/drivers/block/loop.c index a3325ceab484a..c96e35c3ef423 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -528,6 +528,9 @@ 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; -- 2.53.0 The file backend calls ->read_iter() and ->write_iter() of the backing file directly but never checks that they exist. Everything that isn't a block device ends up in the file backend. So a namespace pointing to a file without them calls a NULL pointer on its first I/O. Only buffered_io=1 gets that far as the O_DIRECT open fails for such files. Refuse them when the namespace is enabled the same way zloop does since commit 0c10f8a1a7ae ("zloop: check that the zone backing file support ->{read,write}_iter"). Fixes: d5eff33ee6f8 ("nvmet: add simple file backed ns support") Reviewed-by: Jan Kara Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- drivers/nvme/target/io-cmd-file.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/nvme/target/io-cmd-file.c b/drivers/nvme/target/io-cmd-file.c index 695963dd932ee..dfa8aa46188c8 100644 --- a/drivers/nvme/target/io-cmd-file.c +++ b/drivers/nvme/target/io-cmd-file.c @@ -47,6 +47,11 @@ int nvmet_file_ns_enable(struct nvmet_ns *ns) return ret; } + if (!ns->file->f_op->read_iter || !ns->file->f_op->write_iter) { + ret = -EINVAL; + goto err; + } + nvmet_file_ns_revalidate(ns); /* -- 2.53.0 With fd_async_io=1 the fileio backend calls ->read_iter() and ->write_iter() of the backing file directly. The synchronous path goes through vfs_iter_read() and vfs_iter_write() which check for them but the aio path doesn't. So a backing file without them calls a NULL pointer on its first I/O. Refuse such files when configuring the device. On the synchronous path every read or write that needed the missing method failed with EINVAL anyway. Fixes: 769031c92c8b ("scsi: target: target/file: Add support of direct and async I/O") Reviewed-by: Jan Kara Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- drivers/target/target_core_file.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/target/target_core_file.c b/drivers/target/target_core_file.c index ab9824a4852f1..a5391e57e9aa5 100644 --- a/drivers/target/target_core_file.c +++ b/drivers/target/target_core_file.c @@ -144,6 +144,14 @@ static int fd_configure_device(struct se_device *dev) goto fail; } fd_dev->fd_file = file; + + if (!file->f_op->read_iter || !file->f_op->write_iter) { + pr_err("FILEIO: %s lacks ->read_iter or ->write_iter\n", + fd_dev->fd_dev_name); + ret = -EINVAL; + goto fail; + } + /* * If using a block backend with this struct file, we extract * fd_dev->fd_[block,dev]_size from struct block_device. -- 2.53.0 Currently, there's no way to tell whether the files of a filesystem store data or whether they are kernel interfaces. Files on pseudo filesystems such as procfs or sysfs generate their contents when they're read and writes to them are commands to the kernel. But a few places in the kernel take an arbitrary file and use it as storage: loop, zloop, the nvme target's file backend, the scsi target's fileio backend, fuse passthrough, the usb mass storage gadget and erofs file-backed mounts. At most they check the file type and whether the file implements ->read_iter() and ->write_iter(). That's not enough. kernfs provides both for every sysfs attribute for example. Add SB_I_PSEUDO so a filesystem can state that its regular files are kernel interfaces and must not be used as storage. Pseudo filesystems also never hold executables or device nodes. But currently each of them decides on its own whether to raise SB_I_NOEXEC and SB_I_NODEV. Some do, most don't. Add fc_init_pseudo() as the common initialization point for all pseudo filesystems that raises all three flags and switch init_pseudo() over to it. The following patches convert the mountable pseudo filesystems. Nothing checks SB_I_PSEUDO yet so no functional changes. Reviewed-by: Jan Kara Reviewed-by: Christoph Hellwig Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- fs/libfs.c | 2 +- include/linux/fs/super_types.h | 1 + include/linux/pseudo_fs.h | 11 +++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/fs/libfs.c b/fs/libfs.c index 96a6e9b1535fb..8c8789489fdf9 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -735,7 +735,7 @@ struct pseudo_fs_context *init_pseudo(struct fs_context *fc, fc->fs_private = ctx; fc->ops = &pseudo_fs_context_ops; fc->sb_flags |= SB_NOUSER; - fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; + fc_init_pseudo(fc); fc->global = true; } return ctx; diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h index 72f4288e16c52..472200bdf081a 100644 --- a/include/linux/fs/super_types.h +++ b/include/linux/fs/super_types.h @@ -359,5 +359,6 @@ struct super_block { #define SB_I_NOIDMAP 0x00002000 /* No idmapped mounts on this superblock */ #define SB_I_ALLOW_HSM 0x00004000 /* Allow HSM events on this superblock */ #define SB_I_NO_DATA_INTEGRITY 0x00008000 /* fs cannot guarantee data persistence on sync */ +#define SB_I_PSEUDO 0x00010000 /* regular files are kernel interfaces, not storage */ #endif /* _LINUX_FS_SUPER_TYPES_H */ diff --git a/include/linux/pseudo_fs.h b/include/linux/pseudo_fs.h index a651e60d94107..0ca408aa60aff 100644 --- a/include/linux/pseudo_fs.h +++ b/include/linux/pseudo_fs.h @@ -2,6 +2,7 @@ #define __LINUX_PSEUDO_FS__ #include +#include struct pseudo_fs_context { const struct super_operations *ops; @@ -15,4 +16,14 @@ struct pseudo_fs_context { struct pseudo_fs_context *init_pseudo(struct fs_context *fc, unsigned long magic); +/* + * Common initialization for all pseudo filesystems, mountable or not. Their + * regular files are kernel interfaces. They don't store data and pseudo + * filesystems never hold executables or device nodes. + */ +static inline void fc_init_pseudo(struct fs_context *fc) +{ + fc->s_iflags |= SB_I_PSEUDO | SB_I_NOEXEC | SB_I_NODEV; +} + #endif -- 2.53.0 Initialize all kernfs based filesystems as pseudo filesystems in kernfs_get_tree() instead of raising SB_I_NOEXEC and SB_I_NODEV by hand in kernfs_fill_super(). This adds SB_I_PSEUDO for sysfs, cgroupfs and resctrl. Reviewed-by: Jan Kara Reviewed-by: Christoph Hellwig Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/mount.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/kernfs/mount.c b/fs/kernfs/mount.c index a0be784bfb064..6b60d212b003d 100644 --- a/fs/kernfs/mount.c +++ b/fs/kernfs/mount.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -298,8 +299,6 @@ static int kernfs_fill_super(struct super_block *sb, struct kernfs_fs_context *k struct dentry *root; info->sb = sb; - /* Userspace would break if executables or devices appear on sysfs */ - sb->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; sb->s_blocksize = PAGE_SIZE; sb->s_blocksize_bits = PAGE_SHIFT; sb->s_magic = kfc->magic; @@ -388,6 +387,7 @@ int kernfs_get_tree(struct fs_context *fc) info->ns = kfc->ns_tag; INIT_LIST_HEAD(&info->node); + fc_init_pseudo(fc); fc->s_fs_info = info; sb = sget_fc(fc, kernfs_test_super, kernfs_set_super); if (IS_ERR(sb)) -- 2.53.0 Initialize procfs as a pseudo filesystem instead of raising SB_I_NOEXEC and SB_I_NODEV by hand. This adds SB_I_PSEUDO. Reviewed-by: Jan Kara Reviewed-by: Christoph Hellwig Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- fs/proc/root.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/proc/root.c b/fs/proc/root.c index 7fbbe92bf73a7..ca0e75081934f 100644 --- a/fs/proc/root.c +++ b/fs/proc/root.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -265,8 +266,6 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc) if (ret) return ret; - /* User space would break if executables or devices appear on proc */ - s->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; s->s_flags |= SB_NODIRATIME | SB_NOSUID | SB_NOEXEC; s->s_blocksize = 1024; s->s_blocksize_bits = 10; @@ -350,6 +349,7 @@ static int proc_init_fs_context(struct fs_context *fc) put_user_ns(fc->user_ns); fc->user_ns = get_user_ns(ctx->pid_ns->user_ns); fc->fs_private = ctx; + fc_init_pseudo(fc); fc->ops = &proc_fs_context_ops; return 0; } -- 2.53.0 Initialize binfmt_misc as a pseudo filesystem instead of raising SB_I_NOEXEC and SB_I_NODEV by hand. This adds SB_I_PSEUDO. Reviewed-by: Jan Kara Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index d945b4f6e1581..7dc233becd98d 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -1575,8 +1576,7 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc) if (user_ns != current_user_ns()) return -EINVAL; - /* Never exec off this instance and never let anything stack on it. */ - sb->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; + /* Never let anything stack on this instance. */ sb->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH; /* @@ -1660,6 +1660,7 @@ static void bm_kill_sb(struct super_block *sb) static int bm_init_fs_context(struct fs_context *fc) { + fc_init_pseudo(fc); fc->ops = &bm_context_ops; return 0; } -- 2.53.0 Initialize mqueue as a pseudo filesystem instead of raising SB_I_NOEXEC and SB_I_NODEV by hand. This adds SB_I_PSEUDO. Reviewed-by: Christoph Hellwig Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- ipc/mqueue.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipc/mqueue.c b/ipc/mqueue.c index 322c3dd98c447..8bbe32d0881f2 100644 --- a/ipc/mqueue.c +++ b/ipc/mqueue.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -405,7 +406,6 @@ static int mqueue_fill_super(struct super_block *sb, struct fs_context *fc) struct inode *inode; struct ipc_namespace *ns = sb->s_fs_info; - sb->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; sb->s_blocksize = PAGE_SIZE; sb->s_blocksize_bits = PAGE_SHIFT; sb->s_magic = MQUEUE_MAGIC; @@ -457,6 +457,7 @@ static int mqueue_init_fs_context(struct fs_context *fc) put_user_ns(fc->user_ns); fc->user_ns = get_user_ns(ctx->ipc_ns->user_ns); fc->fs_private = ctx; + fc_init_pseudo(fc); fc->ops = &mqueue_fs_context_ops; return 0; } -- 2.53.0 Initialize bpffs as a pseudo filesystem instead of raising SB_I_NOEXEC and SB_I_NODEV by hand. This adds SB_I_PSEUDO. Reviewed-by: Christoph Hellwig Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- kernel/bpf/inode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/inode.c b/kernel/bpf/inode.c index c6f328e4752e1..57cf3775c0766 100644 --- a/kernel/bpf/inode.c +++ b/kernel/bpf/inode.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1197,8 +1198,6 @@ static int bpf_fill_super(struct super_block *sb, struct fs_context *fc) sb->s_magic = BPF_FS_MAGIC; sb->s_op = &bpf_super_ops; sb->s_xattr = bpf_fs_xattr_handlers; - sb->s_iflags |= SB_I_NOEXEC; - sb->s_iflags |= SB_I_NODEV; sb->s_time_gran = 1; inode = bpf_get_inode(sb, NULL, S_IFDIR | 0777); @@ -1261,6 +1260,7 @@ static int bpf_init_fs_context(struct fs_context *fc) opts->delegate_attachs = 0; fc->s_fs_info = opts; + fc_init_pseudo(fc); fc->ops = &bpf_context_ops; return 0; } -- 2.53.0 Initialize nullfs as a pseudo filesystem instead of raising SB_I_NOEXEC and SB_I_NODEV by hand. This adds SB_I_PSEUDO. Reviewed-by: Jan Kara Reviewed-by: Christoph Hellwig Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- fs/nullfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/nullfs.c b/fs/nullfs.c index 319c2f5c5a671..2fe24cb97160e 100644 --- a/fs/nullfs.c +++ b/fs/nullfs.c @@ -2,6 +2,7 @@ /* Copyright (c) 2026 Christian Brauner */ #include #include +#include #include #include #include @@ -174,7 +175,7 @@ static int nullfs_init_fs_context(struct fs_context *fc) { fc->ops = &nullfs_fs_context_ops; fc->sb_flags |= SB_NOUSER; - fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; + fc_init_pseudo(fc); return 0; } -- 2.53.0 Initialize failfs as a pseudo filesystem instead of raising SB_I_NOEXEC and SB_I_NODEV by hand. This adds SB_I_PSEUDO. Reviewed-by: Jan Kara Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- fs/failfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/failfs.c b/fs/failfs.c index 437cdc981c2de..f03334f361c0e 100644 --- a/fs/failfs.c +++ b/fs/failfs.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -132,7 +133,7 @@ static int failfs_init_fs_context(struct fs_context *fc) fc->ops = &failfs_context_ops; fc->global = true; fc->sb_flags |= SB_NOUSER; - fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; + fc_init_pseudo(fc); return 0; } -- 2.53.0 Initialize binderfs as a pseudo filesystem instead of raising SB_I_NOEXEC by hand. This adds SB_I_PSEUDO. binderfs_fill_super() keeps clearing SB_I_NODEV for the binder device nodes. Signed-off-by: Christian Brauner (Amutable) --- drivers/android/binder/rust_binderfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/android/binder/rust_binderfs.c b/drivers/android/binder/rust_binderfs.c index 5f020226720db..04468c5b5c116 100644 --- a/drivers/android/binder/rust_binderfs.c +++ b/drivers/android/binder/rust_binderfs.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -667,7 +668,6 @@ static int binderfs_fill_super(struct super_block *sb, struct fs_context *fc) * necessary and safe. */ sb->s_iflags &= ~SB_I_NODEV; - sb->s_iflags |= SB_I_NOEXEC; sb->s_magic = RUST_BINDERFS_SUPER_MAGIC; sb->s_op = &binderfs_super_ops; sb->s_time_gran = 1; @@ -759,6 +759,7 @@ static int binderfs_init_fs_context(struct fs_context *fc) ctx->stats_mode = binderfs_stats_mode_unset; fc->fs_private = ctx; + fc_init_pseudo(fc); fc->ops = &binderfs_fs_context_ops; return 0; -- 2.53.0 Initialize devpts as a pseudo filesystem. This raises SB_I_PSEUDO and SB_I_NOEXEC. devpts never holds executables. devpts_fill_super() keeps clearing SB_I_NODEV for the pty device nodes. Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- fs/devpts/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index bd1e8eb26edb7..47ef12241d92d 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -438,6 +439,7 @@ static int devpts_init_fs_context(struct fs_context *fc) fsi->mount_opts.reserve = true; fc->s_fs_info = fsi; + fc_init_pseudo(fc); fc->ops = &devpts_context_ops; return 0; } -- 2.53.0 Initialize debugfs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. debugfs never holds executables or device nodes. SB_I_NOEXEC also refuses PROT_EXEC mappings. The debugfs files that can be mapped, kcov, relay channels and powernv's memtrace, map data. Reviewed-by: Jan Kara Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- fs/debugfs/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index b4915551ad315..f163f0bc5703a 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -317,6 +318,7 @@ static int debugfs_init_fs_context(struct fs_context *fc) fsi->mode = DEBUGFS_DEFAULT_MODE; fc->s_fs_info = fsi; + fc_init_pseudo(fc); fc->ops = &debugfs_context_ops; return 0; } -- 2.53.0 Initialize tracefs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. tracefs never holds executables or device nodes. SB_I_NOEXEC also refuses PROT_EXEC mappings. The only tracefs files that can be mapped are the ring buffers and __rb_map_vma() refuses VM_EXEC already. Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- fs/tracefs/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 5020365ca7048..e04febd868eb4 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -510,6 +511,7 @@ static int tracefs_init_fs_context(struct fs_context *fc) fsi->mode = TRACEFS_DEFAULT_MODE; fc->s_fs_info = fsi; + fc_init_pseudo(fc); fc->ops = &tracefs_context_ops; return 0; } -- 2.53.0 Initialize configfs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. configfs never holds executables or device nodes. Reviewed-by: Jan Kara Acked-by: Breno Leitao Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- fs/configfs/mount.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/configfs/mount.c b/fs/configfs/mount.c index d8cac1cbf3bd5..60ba24e7ae2b9 100644 --- a/fs/configfs/mount.c +++ b/fs/configfs/mount.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -106,6 +107,7 @@ static const struct fs_context_operations configfs_context_ops = { static int configfs_init_fs_context(struct fs_context *fc) { + fc_init_pseudo(fc); fc->ops = &configfs_context_ops; return 0; } -- 2.53.0 Initialize securityfs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. securityfs never holds executables or device nodes. AppArmor puts a .null device node into securityfs but only ever opens it via dentry_open() which doesn't care about SB_I_NODEV. Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- security/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/security/inode.c b/security/inode.c index 0804023676741..93997a9f16fcb 100644 --- a/security/inode.c +++ b/security/inode.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -64,6 +65,7 @@ static const struct fs_context_operations securityfs_context_ops = { static int securityfs_init_fs_context(struct fs_context *fc) { + fc_init_pseudo(fc); fc->ops = &securityfs_context_ops; return 0; } -- 2.53.0 Initialize selinuxfs as a pseudo filesystem. This raises SB_I_PSEUDO and SB_I_NOEXEC. selinuxfs never holds executables. SB_I_NOEXEC also refuses PROT_EXEC mappings but the status and policy files are only ever mapped for reading. But selinuxfs needs device nodes. The kernel replaces file descriptors with /sys/fs/selinux/null on exec via dentry_open() which doesn't care about SB_I_NODEV. Android init however opens /sys/fs/selinux/null by path as /dev/null doesn't exist yet at that point. So clear SB_I_NODEV again in sel_fill_super(). Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- security/selinux/selinuxfs.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 310e19efff1d0..d7ae853b6d685 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -1852,6 +1853,9 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc) /* last one */ {"", NULL, 0} }; + /* Android init opens the null device below by path. */ + sb->s_iflags &= ~SB_I_NODEV; + ret = selinux_fs_info_create(sb); if (ret) goto err; @@ -1955,6 +1959,7 @@ static const struct fs_context_operations sel_context_ops = { static int sel_init_fs_context(struct fs_context *fc) { + fc_init_pseudo(fc); fc->ops = &sel_context_ops; return 0; } -- 2.53.0 Initialize smackfs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. smackfs never holds executables or device nodes. Acked-by: Casey Schaufler Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- security/smack/smackfs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index a72bc7fabea99..4a56bbeaeca37 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "smack.h" #define BEBITS (sizeof(__be32) * 8) @@ -2880,6 +2881,7 @@ static const struct fs_context_operations smk_context_ops = { */ static int smk_init_fs_context(struct fs_context *fc) { + fc_init_pseudo(fc); fc->ops = &smk_context_ops; return 0; } -- 2.53.0 Initialize apparmorfs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. apparmorfs never holds executables or device nodes. Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- security/apparmor/apparmorfs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index a993664672173..351ea3a7120e3 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -263,6 +264,7 @@ static const struct fs_context_operations apparmorfs_context_ops = { static int apparmorfs_init_fs_context(struct fs_context *fc) { + fc_init_pseudo(fc); fc->ops = &apparmorfs_context_ops; return 0; } -- 2.53.0 Initialize the nfsd filesystem as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. It never holds executables or device nodes. Acked-by: Chuck Lever Signed-off-by: Christian Brauner (Amutable) --- fs/nfsd/nfsctl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index f32311f2d7cf9..ce2666deddbd9 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -1375,6 +1376,7 @@ static int nfsd_init_fs_context(struct fs_context *fc) { put_user_ns(fc->user_ns); fc->user_ns = get_user_ns(fc->net_ns->user_ns); + fc_init_pseudo(fc); fc->ops = &nfsd_fs_context_ops; return 0; } -- 2.53.0 Initialize rpc_pipefs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. rpc_pipefs never holds executables or device nodes. Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- net/sunrpc/rpc_pipe.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/sunrpc/rpc_pipe.c b/net/sunrpc/rpc_pipe.c index 9d349cfbc4831..37277a6d24156 100644 --- a/net/sunrpc/rpc_pipe.c +++ b/net/sunrpc/rpc_pipe.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1181,6 +1182,7 @@ static int rpc_init_fs_context(struct fs_context *fc) { put_user_ns(fc->user_ns); fc->user_ns = get_user_ns(fc->net_ns->user_ns); + fc_init_pseudo(fc); fc->ops = &rpc_fs_context_ops; return 0; } -- 2.53.0 Initialize fusectl as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. fusectl never holds executables or device nodes. Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- fs/fuse/control.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/fuse/control.c b/fs/fuse/control.c index 21ffde596d611..4497362e7df06 100644 --- a/fs/fuse/control.c +++ b/fs/fuse/control.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #define FUSE_CTL_SUPER_MAGIC 0x65735543 @@ -334,6 +335,7 @@ static const struct fs_context_operations fuse_ctl_context_ops = { static int fuse_ctl_init_fs_context(struct fs_context *fsc) { + fc_init_pseudo(fsc); fsc->ops = &fuse_ctl_context_ops; return 0; } -- 2.53.0 Initialize efivarfs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. efivarfs never holds executables or device nodes. Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- fs/efivarfs/super.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/efivarfs/super.c b/fs/efivarfs/super.c index 12746ced6dc63..e598d42c5f42c 100644 --- a/fs/efivarfs/super.c +++ b/fs/efivarfs/super.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -554,6 +555,7 @@ static int efivarfs_init_fs_context(struct fs_context *fc) } fc->s_fs_info = sfi; + fc_init_pseudo(fc); fc->ops = &efivarfs_context_ops; return 0; -- 2.53.0 Initialize pstore as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. pstore never holds executables or device nodes. Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Christian Brauner (Amutable) --- fs/pstore/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/pstore/inode.c b/fs/pstore/inode.c index cf499ba727026..4658561cf90b7 100644 --- a/fs/pstore/inode.c +++ b/fs/pstore/inode.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -491,6 +492,7 @@ static int pstore_init_fs_context(struct fs_context *fc) ctx->kmsg_bytes = kmsg_bytes; fc->fs_private = ctx; + fc_init_pseudo(fc); fc->ops = &pstore_context_ops; return 0; -- 2.53.0 Initialize xenfs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. xenfs never holds executables or device nodes. SB_I_NOEXEC also refuses PROT_EXEC mappings. xenfs maps the xenstore page and guest memory through privcmd, both data. Signed-off-by: Christian Brauner (Amutable) --- drivers/xen/xenfs/super.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/xen/xenfs/super.c b/drivers/xen/xenfs/super.c index 37ea7c5c03469..aba7d038ed735 100644 --- a/drivers/xen/xenfs/super.c +++ b/drivers/xen/xenfs/super.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -80,6 +81,7 @@ static const struct fs_context_operations xenfs_context_ops = { static int xenfs_init_fs_context(struct fs_context *fc) { + fc_init_pseudo(fc); fc->ops = &xenfs_context_ops; return 0; } -- 2.53.0 Initialize openpromfs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. openpromfs never holds executables or device nodes. Signed-off-by: Christian Brauner (Amutable) --- fs/openpromfs/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/openpromfs/inode.c b/fs/openpromfs/inode.c index fb8d84bdedfb2..6cd02d456bac9 100644 --- a/fs/openpromfs/inode.c +++ b/fs/openpromfs/inode.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -419,6 +420,7 @@ static const struct fs_context_operations openpromfs_context_ops = { static int openpromfs_init_fs_context(struct fs_context *fc) { + fc_init_pseudo(fc); fc->ops = &openpromfs_context_ops; return 0; } -- 2.53.0 Initialize dlmfs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. dlmfs never holds executables or device nodes. Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/ocfs2/dlmfs/dlmfs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/ocfs2/dlmfs/dlmfs.c b/fs/ocfs2/dlmfs/dlmfs.c index d3bfcada3e3bf..1789ec96dc130 100644 --- a/fs/ocfs2/dlmfs/dlmfs.c +++ b/fs/ocfs2/dlmfs/dlmfs.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -563,6 +564,7 @@ static const struct fs_context_operations dlmfs_context_ops = { static int dlmfs_init_fs_context(struct fs_context *fc) { + fc_init_pseudo(fc); fc->ops = &dlmfs_context_ops; return 0; -- 2.53.0 Initialize hypfs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. hypfs never holds executables or device nodes. Signed-off-by: Christian Brauner (Amutable) --- arch/s390/hypfs/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/s390/hypfs/inode.c b/arch/s390/hypfs/inode.c index 559b011e68800..8662169de477a 100644 --- a/arch/s390/hypfs/inode.c +++ b/arch/s390/hypfs/inode.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -301,6 +302,7 @@ static int hypfs_init_fs_context(struct fs_context *fc) sbi->gid = current_gid(); fc->s_fs_info = sbi; + fc_init_pseudo(fc); fc->ops = &hypfs_context_ops; return 0; } -- 2.53.0 Initialize spufs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. spufs never holds executables or device nodes. SB_I_NOEXEC also refuses PROT_EXEC mappings. The spufs files that can be mapped expose local store and problem state areas of the SPU, nothing the PPU executes. Signed-off-by: Christian Brauner (Amutable) --- arch/powerpc/platforms/cell/spufs/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/powerpc/platforms/cell/spufs/inode.c b/arch/powerpc/platforms/cell/spufs/inode.c index f066d9c7344d3..54b45932aecd2 100644 --- a/arch/powerpc/platforms/cell/spufs/inode.c +++ b/arch/powerpc/platforms/cell/spufs/inode.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -741,6 +742,7 @@ static int spufs_init_fs_context(struct fs_context *fc) fc->fs_private = ctx; fc->s_fs_info = sbi; + fc_init_pseudo(fc); fc->ops = &spufs_context_ops; return 0; -- 2.53.0 Initialize functionfs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. functionfs never holds executables or device nodes. Signed-off-by: Christian Brauner (Amutable) --- drivers/usb/gadget/function/f_fs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index c64a268e98a43..a6b3788399a56 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -15,6 +15,7 @@ /* #define VERBOSE_DEBUG */ #include +#include #include #include #include @@ -2166,6 +2167,7 @@ static int ffs_fs_init_fs_context(struct fs_context *fc) ctx->no_disconnect = false; fc->fs_private = ctx; + fc_init_pseudo(fc); fc->ops = &ffs_fs_context_ops; return 0; } -- 2.53.0 Initialize gadgetfs as a pseudo filesystem. This raises SB_I_PSEUDO and also SB_I_NOEXEC and SB_I_NODEV. gadgetfs never holds executables or device nodes. Signed-off-by: Christian Brauner (Amutable) --- drivers/usb/gadget/legacy/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index 7f6d8460152f9..8490e5d0351e1 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -2223,6 +2224,7 @@ static const struct fs_context_operations gadgetfs_context_ops = { static int gadgetfs_init_fs_context(struct fs_context *fc) { + fc_init_pseudo(fc); fc->ops = &gadgetfs_context_ops; return 0; } -- 2.53.0 Loop, zloop, the nvme target, the scsi target, fuse passthrough, the usb mass storage gadget, erofs and acct all take a file from userspace and use it as storage. Currently, each of them has its own idea of what to check before using a file like that: - loop and the gadget check for a regular file or a block device, the nvme target, the scsi target and zloop take any file type - loop checks the open mode, the gadget checks FMODE_CAN_READ, the nvme target, the scsi target and zloop only check for ->read_iter() and ->write_iter() - apart from acct which refuses procfs and sysfs files none of them cares whether the file is a kernel interface Add a helper that does all of it in one place. It only accepts regular files and, if the caller passes S_IFBLK, block devices. It refuses files on pseudo filesystems. And it checks that the file is open for what the caller is going to do with it and implements ->read_iter() for reading and ->write_iter() for writing. O_PATH files are refused unless the caller passes FMODE_PATH. That is for callers that only need the path of the file. fuse passthrough is one of them and has accepted O_PATH file descriptors since commit efad7153bf93 ("fuse: allow O_PATH fd for FUSE_DEV_IOC_BACKING_OPEN"). Block devices are fine even though bdevfs is a pseudo filesystem. A block device that userspace hands in is a device node on devtmpfs or some other filesystem. Files on bdevfs only come from bdev_file_open_by_*() and never end up here. The pseudo filesystem check looks at the inode that actually provides the data via d_real_inode(). overlayfs accepts sysfs, cgroup2, debugfs, tracefs, configfs and securityfs as lower layers and reads of such an overlayfs file are served by the file on the pseudo filesystem. Checking the overlayfs inode would miss that. Note that the file type is taken from the inode of the file and not from the host of ->f_mapping like loop and the gadget do it. A driver can point ->f_mapping somewhere else in ->open(). It makes no difference for block devices as both inodes are block devices. No functional changes. Nothing calls the helper yet. Signed-off-by: Christian Brauner (Amutable) --- fs/read_write.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/fs.h | 2 ++ 2 files changed, 50 insertions(+) diff --git a/fs/read_write.c b/fs/read_write.c index 6767283108991..cedbe654add65 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -866,6 +866,54 @@ static ssize_t do_loop_readv_writev(struct file *filp, struct iov_iter *iter, return ret; } +/** + * file_check_backing - check that the kernel can use a file as storage + * @file: file that userspace handed in + * @mode: FMODE_READ and/or FMODE_WRITE the kernel will do I/O with, or + * FMODE_PATH if only the path of @file is used + * @type: S_IFBLK if a block device is fine as well, 0 otherwise + * + * Call this where a file from userspace is taken as a backing file. Only + * regular files and, if @type is S_IFBLK, block devices are storage. Files + * on pseudo filesystems are kernel interfaces and are refused. The file + * must be open for @mode and implement ->read_iter() for reading and + * ->write_iter() for writing. O_PATH files are only fine with FMODE_PATH. + * + * Return: 0 on success, -EBADF if @file isn't open for @mode, -EINVAL if it + * can't be used as storage. + */ +int file_check_backing(const struct file *file, fmode_t mode, umode_t type) +{ + fmode_t io = mode & (FMODE_READ | FMODE_WRITE); + /* Drivers may point ->f_mapping at something else. */ + umode_t fmt = file_inode(file)->i_mode & S_IFMT; + + /* O_PATH files are only fine for callers that just use the path. */ + if ((file->f_mode & FMODE_PATH) && !(mode & FMODE_PATH)) + return -EBADF; + + if ((file->f_mode & io) != io) + return -EBADF; + + if (fmt != S_IFREG && !(type == S_IFBLK && fmt == S_IFBLK)) + return -EINVAL; + + /* Look through stacking filesystems at the inode with the data. */ + if (d_real_inode(file->f_path.dentry)->i_sb->s_iflags & SB_I_PSEUDO) + return -EINVAL; + + if ((mode & FMODE_READ) && + (!(file->f_mode & FMODE_CAN_READ) || !file->f_op->read_iter)) + return -EINVAL; + + if ((mode & FMODE_WRITE) && + (!(file->f_mode & FMODE_CAN_WRITE) || !file->f_op->write_iter)) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(file_check_backing); + ssize_t vfs_iocb_iter_read(struct file *file, struct kiocb *iocb, struct iov_iter *iter) { diff --git a/include/linux/fs.h b/include/linux/fs.h index 99caef43ff3c4..b1c7ebafbadbe 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2553,6 +2553,8 @@ static inline const struct inode *file_user_inode(const struct file *f) return file_inode(f); } +int file_check_backing(const struct file *file, fmode_t mode, umode_t type); + static inline struct file *file_clone_open(struct file *file) { return dentry_open(&file->f_path, file->f_flags, file->f_cred); -- 2.53.0 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) --- 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 zloop opens its zone files by name below the base directory but a bind mount can put any file there, including a procfs or sysfs file. Refuse files on pseudo filesystems as zone files just like loop does by switching to file_check_backing(). Note that the helper only accepts regular files. So far zloop took anything with ->read_iter() and ->write_iter() as a zone file. That includes character devices, FIFOs and block devices if someone put them where zloop expects a zone file. Zone files are created by zloop itself and zloop truncates them to size and to reset and finish zones which only works for regular files. So refuse everything else. Signed-off-by: Christian Brauner (Amutable) --- drivers/block/zloop.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c index 65634a39b0bfc..6f79b9ddbbe00 100644 --- a/drivers/block/zloop.c +++ b/drivers/block/zloop.c @@ -1132,10 +1132,9 @@ static int zloop_init_zone(struct zloop_device *zlo, struct zloop_options *opts, return PTR_ERR(zone->file); } - if (!zone->file->f_op->read_iter || !zone->file->f_op->write_iter) { - ret = -EINVAL; + ret = file_check_backing(zone->file, FMODE_READ | FMODE_WRITE, 0); + if (ret) goto out_fput; - } if (zlo->block_size) { ret = zloop_get_block_size(zlo, zone); -- 2.53.0 The file backend takes any file that can be opened for reading and writing. With buffered_io=1 that includes files on procfs or sysfs which then get read and written by a kernel worker on behalf of the host. Refuse files on pseudo filesystems just like loop does by switching to file_check_backing(). Note that the helper only accepts regular files and block devices. A block device ends up in the file backend when buffered_io is set. But so far the file backend also took anything else with ->read_iter() and ->write_iter() such as character devices and FIFOs. The size of the namespace is the size of the inode which is zero for all of them. So such a namespace was unusable anyway. Refuse them. Signed-off-by: Christian Brauner (Amutable) --- drivers/nvme/target/io-cmd-file.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/target/io-cmd-file.c b/drivers/nvme/target/io-cmd-file.c index dfa8aa46188c8..2249ad3bb46d8 100644 --- a/drivers/nvme/target/io-cmd-file.c +++ b/drivers/nvme/target/io-cmd-file.c @@ -47,10 +47,9 @@ int nvmet_file_ns_enable(struct nvmet_ns *ns) return ret; } - if (!ns->file->f_op->read_iter || !ns->file->f_op->write_iter) { - ret = -EINVAL; + ret = file_check_backing(ns->file, FMODE_READ | FMODE_WRITE, S_IFBLK); + if (ret) goto err; - } nvmet_file_ns_revalidate(ns); -- 2.53.0 fd_dev_name can name any file including files on procfs or sysfs. The initiator then gets to read and write kernel interfaces through the LUN with kernel credentials. Refuse files on pseudo filesystems just like loop does by switching to file_check_backing(). Do the same for the protection file. With DIF protection enabled the fileio backend keeps the protection information in a second file. It is named like the backing file with ".protection" appended and fd_init_prot() opens it by path. Whatever that path leads to was taken without any checks. Note that the helper only accepts regular files and block devices as backing file and only regular files as protection file. So far the backing file could be anything with ->read_iter() and ->write_iter() including character devices and FIFOs. Reported-by: Sashiko Closes: https://lore.kernel.org/all/sashiko-outbox-206765@kernel.org Signed-off-by: Christian Brauner (Amutable) --- drivers/target/target_core_file.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/target/target_core_file.c b/drivers/target/target_core_file.c index a5391e57e9aa5..c747cc4a647ff 100644 --- a/drivers/target/target_core_file.c +++ b/drivers/target/target_core_file.c @@ -145,8 +145,8 @@ static int fd_configure_device(struct se_device *dev) } fd_dev->fd_file = file; - if (!file->f_op->read_iter || !file->f_op->write_iter) { - pr_err("FILEIO: %s lacks ->read_iter or ->write_iter\n", + if (file_check_backing(file, FMODE_READ | FMODE_WRITE, S_IFBLK)) { + pr_err("FILEIO: %s can't be used as a backing file\n", fd_dev->fd_dev_name); ret = -EINVAL; goto fail; @@ -861,6 +861,12 @@ static int fd_init_prot(struct se_device *dev) ret = PTR_ERR(prot_file); return ret; } + + if (file_check_backing(prot_file, FMODE_READ | FMODE_WRITE, 0)) { + pr_err("FILEIO: %s can't be used as a protection file\n", buf); + filp_close(prot_file, NULL); + return -EINVAL; + } fd_dev->fd_prot_file = prot_file; return 0; -- 2.53.0 The fuse server can register any regular file as a backing file. procfs and binfmt_misc files are already refused by the stacking depth check as both set s_stack_depth to FILESYSTEM_MAX_STACK_DEPTH. sysfs and the other pseudo filesystems aren't. Reads and writes of the fuse file would then be done on the pseudo file with the fuse server's credentials. Refuse files on pseudo filesystems just like loop does. Note that the backing file is only registered here and opened again for every fuse file that uses it. Only its path is used and O_PATH file descriptors are allowed since commit efad7153bf93 ("fuse: allow O_PATH fd for FUSE_DEV_IOC_BACKING_OPEN"). So pass FMODE_PATH to file_check_backing() to keep them working. Signed-off-by: Christian Brauner (Amutable) --- fs/fuse/backing.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 8c21a8a74efb9..7527f3ecb90dc 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -147,6 +147,10 @@ static int fuse_backing_open_file(struct fuse_conn *fc, struct fuse_backing *fb, if (inode->i_sb->s_stack_depth >= fc->max_stack_depth) return -ELOOP; + err = file_check_backing(file, FMODE_PATH, 0); + if (err) + return err; + fb->type = FUSE_BACKING_PATH; fb->path = file->f_path; path_get(&fb->path); -- 2.53.0 fsg_lun_open() takes any regular file as the backing file of a LUN. The file-storage kthread then reads and writes it with kernel_read() and kernel_write() on behalf of the USB host. With a procfs or sysfs file behind a LUN the host gets to read and write kernel interfaces with kernel credentials. Refuse files on pseudo filesystems just like loop does by switching to file_check_backing(). The helper also does the file type and readability checks. So the messages for an unsuitable backing file become one. 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. It also wants ->read_iter() where FMODE_CAN_READ was enough so far. But kernel_read() never worked without ->read_iter() anyway. Signed-off-by: Christian Brauner (Amutable) --- drivers/usb/gadget/function/storage_common.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/usb/gadget/function/storage_common.c b/drivers/usb/gadget/function/storage_common.c index 75831f2c7abeb..af0c37a9fe029 100644 --- a/drivers/usb/gadget/function/storage_common.c +++ b/drivers/usb/gadget/function/storage_common.c @@ -205,23 +205,18 @@ int fsg_lun_open(struct fsg_lun *curlun, const char *filename) if (!(filp->f_mode & FMODE_WRITE)) ro = 1; - inode = filp->f_mapping->host; - if ((!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))) { - LINFO(curlun, "invalid file type: %s\n", filename); - goto out; - } - /* * If we can't read the file, it's no good. * If we can't write the file, use it read-only. */ - if (!(filp->f_mode & FMODE_CAN_READ)) { - LINFO(curlun, "file not readable: %s\n", filename); + if (file_check_backing(filp, FMODE_READ, S_IFBLK)) { + LINFO(curlun, "invalid backing file: %s\n", filename); goto out; } if (!(filp->f_mode & FMODE_CAN_WRITE)) ro = 1; + inode = filp->f_mapping->host; size = i_size_read(inode); if (size < 0) { LINFO(curlun, "unable to find file size: %s\n", filename); -- 2.53.0 File-backed mounts take any regular file as the image and the same goes for the extra devices of such a mount. erofs reads them with the credentials of the mounter so this doesn't hand out anything the mounter couldn't read anyway. But files on pseudo filesystems aren't images. Refuse them just like loop does. file_check_backing() also checks that the file is open for reading. So a source file descriptor that erofs can't read from fails the mount right away instead of on the first read. Signed-off-by: Christian Brauner (Amutable) --- fs/erofs/super.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/erofs/super.c b/fs/erofs/super.c index ce05557bd9983..517a289b9395c 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -158,7 +158,7 @@ static int erofs_init_device(struct erofs_buf *buf, struct super_block *sb, if (!erofs_is_fileio_mode(sbi)) { dif->dax_dev = fs_dax_get_by_bdev(file_bdev(file), &dif->dax_part_off, NULL, NULL); - } else if (!S_ISREG(file_inode(file)->i_mode)) { + } else if (file_check_backing(file, FMODE_READ, 0)) { fput(file); return -EINVAL; } @@ -835,6 +835,9 @@ static int erofs_file_get_tree(struct fs_context *fc) { struct erofs_sb_info *sbi = fc->s_fs_info; + if (file_check_backing(sbi->dif0.file, FMODE_READ, 0)) + return invalfc(fc, "source can't be used as a backing file"); + if (!test_opt(&sbi->opt, SUPERBLOCK_SHARE)) return get_tree_nodev(fc, erofs_fc_fill_super); -- 2.53.0 acct(2) already refuses to write its records to files on kernel internal filesystems and to procfs and sysfs files. It recognizes the latter two by FS_USERNS_MOUNT_RESTRICTED which misses every other pseudo filesystem. So a cgroup2 or binfmt_misc file is accepted as the accounting file. The records are written with the credentials of whoever turned on accounting so this isn't abusable the way loop is. But files on pseudo filesystems aren't a place to store accounting records. Use file_check_backing() to refuse all of them. SB_NOUSER doesn't need to be checked separately anymore. It is only raised by pseudo filesystems and by tmpfs instances that are either kernel internal or can't be mounted. Suggested-by: Alexander Mikhalitsyn Link: https://lore.kernel.org/r/ae189903d4cda27130317a9312d94b53d07415ba.camel@amutable.com Signed-off-by: Christian Brauner (Amutable) --- kernel/acct.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/acct.c b/kernel/acct.c index c440d43479ca5..0c502f778fde0 100644 --- a/kernel/acct.c +++ b/kernel/acct.c @@ -245,11 +245,11 @@ static int acct_on(const char __user *name) return -EACCES; /* Exclude kernel internal filesystems. */ - if (file_inode(file)->i_sb->s_flags & (SB_NOUSER | SB_KERNMOUNT)) + if (file_inode(file)->i_sb->s_flags & SB_KERNMOUNT) return -EINVAL; - /* Exclude procfs and sysfs. */ - if (file_inode(file)->i_sb->s_type->fs_flags & FS_USERNS_MOUNT_RESTRICTED) + /* Exclude pseudo filesystems. */ + if (file_check_backing(file, 0, 0)) return -EINVAL; if (!(file->f_mode & FMODE_CAN_WRITE)) -- 2.53.0 Add tests that files on pseudo filesystems can't be used as storage: - a procfs file attached read-only to a loop device - sysfs files attached read-only and read-write to a loop device - a sysfs file behind a lower-only overlayfs attached to a loop device - a symlink and a magic link to a sysfs file attached to a loop device - a procfs file as the backing file of an nvme target namespace - a procfs file as the backing file and as the protection file of a fileio device of the scsi target - a procfs file as a zloop zone file - a sysfs file as the backing file of a usb mass storage gadget LUN - a sysfs file as the source of a file-backed erofs mount Add tests that FIFOs and character devices can't be used as storage by loop, the nvme target, the fileio backend and zloop either. And make sure that a memfd and another loop device still work as loop backing files, that a regular file and a block device still work for the nvme target and the fileio backend and that loop refuses a backing file that isn't open for reading. The nvme target test uses buffered_io as asked for in [1]. Most pseudo files can't be opened with O_DIRECT and the test would pass without ever reaching the check. [1]: https://lore.kernel.org/r/71f76e5e-ce24-4a71-863a-169ad262c510@gmail.com Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/filesystems/.gitignore | 1 + tools/testing/selftests/filesystems/Makefile | 2 +- tools/testing/selftests/filesystems/config | 14 + .../selftests/filesystems/pseudo_backing_test.c | 570 +++++++++++++++++++++ 4 files changed, 586 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index cf79000e4092a..8df38eabb1252 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -7,3 +7,4 @@ kernfs_test idmapped_tmpfile ustat_test rw_hint_test +pseudo_backing_test diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile index fbd5c27505ef5..0ebb9005bcd28 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile @@ -2,7 +2,7 @@ CFLAGS += $(KHDR_INCLUDES) TEST_GEN_PROGS := devpts_pts anon_inode_test kernfs_test fclog ustat_test rw_hint_test -TEST_GEN_PROGS += idmapped_tmpfile +TEST_GEN_PROGS += idmapped_tmpfile pseudo_backing_test TEST_GEN_PROGS_EXTENDED := dnotify_test include ../lib.mk diff --git a/tools/testing/selftests/filesystems/config b/tools/testing/selftests/filesystems/config index 9f45bc493a30a..9375164f46d70 100644 --- a/tools/testing/selftests/filesystems/config +++ b/tools/testing/selftests/filesystems/config @@ -6,3 +6,17 @@ CONFIG_NET=y CONFIG_NET_NS=y CONFIG_INET=y CONFIG_SYSFS=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_OVERLAY_FS=y +CONFIG_TMPFS=y +CONFIG_CONFIGFS_FS=y +CONFIG_USB_GADGET=y +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_EROFS_FS=y +CONFIG_EROFS_FS_BACKED_BY_FILE=y +CONFIG_NVME_TARGET=y +CONFIG_TARGET_CORE=y +CONFIG_TCM_FILEIO=y +CONFIG_BLK_DEV_ZONED=y +CONFIG_BLK_DEV_ZONED_LOOP=y diff --git a/tools/testing/selftests/filesystems/pseudo_backing_test.c b/tools/testing/selftests/filesystems/pseudo_backing_test.c new file mode 100644 index 0000000000000..04315655e0587 --- /dev/null +++ b/tools/testing/selftests/filesystems/pseudo_backing_test.c @@ -0,0 +1,570 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kselftest_harness.h" +#include "wrappers.h" + +/* + * Put @fd behind a free loop device. The device is set up with + * LO_FLAGS_AUTOCLEAR and goes away again when it is closed here. + */ +static int loop_attach(int fd, bool rw, char *dev, size_t len) +{ + struct loop_config cfg = {}; + int ctl, nr, lfd, ret = 0; + + ctl = open("/dev/loop-control", O_RDWR | O_CLOEXEC); + if (ctl < 0) + return -errno; + nr = ioctl(ctl, LOOP_CTL_GET_FREE); + if (nr < 0) + ret = -errno; + close(ctl); + if (ret) + return ret; + + snprintf(dev, len, "/dev/loop%d", nr); + lfd = open(dev, (rw ? O_RDWR : O_RDONLY) | O_CLOEXEC); + if (lfd < 0) + return -errno; + + cfg.fd = fd; + cfg.info.lo_flags = LO_FLAGS_AUTOCLEAR; + if (!rw) + cfg.info.lo_flags |= LO_FLAGS_READ_ONLY; + if (ioctl(lfd, LOOP_CONFIGURE, &cfg)) { + ret = -errno; + close(lfd); + } else { + ret = lfd; + } + return ret; +} + +static int attach_path(const char *path, bool rw) +{ + char dev[PATH_MAX]; + int fd, ret; + + fd = open(path, (rw ? O_RDWR : O_RDONLY) | O_CLOEXEC); + if (fd < 0) + return -errno; + ret = loop_attach(fd, rw, dev, sizeof(dev)); + close(fd); + if (ret >= 0) { + close(ret); + ret = 0; + } + return ret; +} + +static bool can_use_loop(void) +{ + return geteuid() == 0 && access("/dev/loop-control", R_OK | W_OK) == 0; +} + +/* A pseudo file that can be opened for reading and writing. */ +#define PSEUDO_FILE "/proc/sys/kernel/hostname" + +static int write_string(const char *path, const char *buf) +{ + int fd, ret = 0; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd < 0) + return -errno; + if (write(fd, buf, strlen(buf)) < 0) + ret = -errno; + close(fd); + return ret; +} + +static int create_file(const char *path) +{ + int fd, ret = 0; + + fd = open(path, O_RDWR | O_CREAT | O_CLOEXEC, 0600); + if (fd < 0) + return -errno; + if (ftruncate(fd, 1 << 20)) + ret = -errno; + close(fd); + return ret; +} + +/* Loop device on top of a memfd. It goes away when the fd is closed. */ +static int create_loop_device(char *dev, size_t len) +{ + int fd, lfd; + + fd = memfd_create("pseudo_backing_test", MFD_CLOEXEC); + if (fd < 0) + return -errno; + if (ftruncate(fd, 1 << 20)) { + lfd = -errno; + close(fd); + return lfd; + } + lfd = loop_attach(fd, true, dev, len); + close(fd); + return lfd; +} + +/* Private tmpfs on /tmp for files, links and FIFOs. */ +static int setup_tmp(void) +{ + if (unshare(CLONE_NEWNS)) + return -errno; + if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) + return -errno; + if (mount("tmpfs", "/tmp", "tmpfs", 0, NULL)) + return -errno; + return 0; +} + +TEST(memfd_backing_file) +{ + char dev[PATH_MAX]; + int fd, lfd; + + if (!can_use_loop()) + SKIP(return, "loop devices unavailable"); + + fd = memfd_create("pseudo_backing_test", MFD_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(ftruncate(fd, 1 << 20), 0); + + lfd = loop_attach(fd, true, dev, sizeof(dev)); + ASSERT_GE(lfd, 0); + EXPECT_EQ(close(lfd), 0); + EXPECT_EQ(close(fd), 0); +} + +TEST(loop_device_backing_file) +{ + char dev[PATH_MAX], dev2[PATH_MAX]; + int fd, lfd, bfd, lfd2; + + if (!can_use_loop()) + SKIP(return, "loop devices unavailable"); + + fd = memfd_create("pseudo_backing_test", MFD_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(ftruncate(fd, 1 << 20), 0); + lfd = loop_attach(fd, true, dev, sizeof(dev)); + ASSERT_GE(lfd, 0); + + /* A block device stays fine as a backing file. */ + bfd = open(dev, O_RDWR | O_CLOEXEC); + ASSERT_GE(bfd, 0); + lfd2 = loop_attach(bfd, true, dev2, sizeof(dev2)); + ASSERT_GE(lfd2, 0); + + EXPECT_EQ(close(lfd2), 0); + EXPECT_EQ(close(bfd), 0); + EXPECT_EQ(close(lfd), 0); + EXPECT_EQ(close(fd), 0); +} + +TEST(write_only_backing_file) +{ + char dev[PATH_MAX], path[PATH_MAX]; + int fd, wfd, lfd; + + if (!can_use_loop()) + SKIP(return, "loop devices unavailable"); + + fd = memfd_create("pseudo_backing_test", MFD_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(ftruncate(fd, 1 << 20), 0); + + /* Loop reads from its backing file so it must be open for reading. */ + snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); + wfd = open(path, O_WRONLY | O_CLOEXEC); + ASSERT_GE(wfd, 0); + + lfd = loop_attach(wfd, true, dev, sizeof(dev)); + if (lfd >= 0) + close(lfd); + EXPECT_EQ(lfd, -EBADF); + EXPECT_EQ(close(wfd), 0); + EXPECT_EQ(close(fd), 0); +} + +TEST(procfs_backing_file) +{ + if (!can_use_loop()) + SKIP(return, "loop devices unavailable"); + + ASSERT_EQ(attach_path("/proc/cpuinfo", false), -EINVAL); +} + +TEST(sysfs_backing_file) +{ + if (!can_use_loop()) + SKIP(return, "loop devices unavailable"); + + ASSERT_EQ(attach_path("/sys/kernel/warn_count", false), -EINVAL); + + if (access("/sys/kernel/reboot/mode", F_OK)) + SKIP(return, "no /sys/kernel/reboot/mode"); + ASSERT_EQ(attach_path("/sys/kernel/reboot/mode", true), -EINVAL); +} + +TEST(overlayfs_on_sysfs_backing_file) +{ + if (!can_use_loop()) + SKIP(return, "loop devices unavailable"); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + ASSERT_EQ(mount("tmpfs", "/tmp", "tmpfs", 0, NULL), 0); + ASSERT_EQ(mkdir("/tmp/empty", 0755), 0); + ASSERT_EQ(mkdir("/tmp/merged", 0755), 0); + + /* A lower-only overlayfs serves reads from sysfs. */ + if (mount("overlay", "/tmp/merged", "overlay", MS_RDONLY, + "lowerdir=/sys/kernel:/tmp/empty")) + SKIP(return, "no overlayfs on top of sysfs: %s", strerror(errno)); + + ASSERT_EQ(attach_path("/tmp/merged/warn_count", false), -EINVAL); +} + +TEST(link_to_sysfs_backing_file) +{ + char path[PATH_MAX]; + int fd; + + if (!can_use_loop()) + SKIP(return, "loop devices unavailable"); + + ASSERT_EQ(setup_tmp(), 0); + + /* Links are followed before the file is checked. */ + ASSERT_EQ(symlink("/sys/kernel/warn_count", "/tmp/link"), 0); + EXPECT_EQ(attach_path("/tmp/link", false), -EINVAL); + + fd = open("/sys/kernel/warn_count", O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); + EXPECT_EQ(attach_path(path, false), -EINVAL); + EXPECT_EQ(close(fd), 0); +} + +TEST(special_file_backing_file) +{ + if (!can_use_loop()) + SKIP(return, "loop devices unavailable"); + + ASSERT_EQ(setup_tmp(), 0); + ASSERT_EQ(mkfifo("/tmp/fifo", 0600), 0); + + /* Only regular files and block devices are storage. */ + EXPECT_EQ(attach_path("/tmp/fifo", true), -EINVAL); + EXPECT_EQ(attach_path("/dev/null", true), -EINVAL); +} + +#define CONFIGFS_DIR "/sys/kernel/config" +#define GADGET_DIR CONFIGFS_DIR "/usb_gadget/pseudo_backing_test" +#define MASS_STORAGE_DIR GADGET_DIR "/functions/mass_storage.0" + +TEST(usb_mass_storage_backing_file) +{ + const char *path = "/sys/kernel/warn_count"; + struct statfs sfs; + ssize_t ret; + int fd, err; + + if (geteuid()) + SKIP(return, "needs root"); + + /* Mounting configfs on top of itself fails with EBUSY. */ + if (statfs(CONFIGFS_DIR, &sfs) || sfs.f_type != CONFIGFS_MAGIC) { + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + if (mount("configfs", CONFIGFS_DIR, "configfs", 0, NULL)) + SKIP(return, "no configfs: %s", strerror(errno)); + } + + if (mkdir(GADGET_DIR, 0755)) + SKIP(return, "no usb gadget configfs: %s", strerror(errno)); + if (mkdir(MASS_STORAGE_DIR, 0755)) { + err = errno; + rmdir(GADGET_DIR); + SKIP(return, "no usb mass storage function: %s", strerror(err)); + } + + /* Writing the path opens the LUN's backing file right away. */ + fd = open(MASS_STORAGE_DIR "/lun.0/file", O_WRONLY | O_CLOEXEC); + EXPECT_GE(fd, 0); + if (fd >= 0) { + ret = write(fd, path, strlen(path)); + err = errno; + EXPECT_EQ(ret, -1); + EXPECT_EQ(err, EINVAL); + EXPECT_EQ(close(fd), 0); + } + + EXPECT_EQ(rmdir(MASS_STORAGE_DIR), 0); + EXPECT_EQ(rmdir(GADGET_DIR), 0); +} + +/* Mounting configfs on top of itself fails with EBUSY. */ +static int setup_configfs(void) +{ + struct statfs sfs; + + if (!statfs(CONFIGFS_DIR, &sfs) && sfs.f_type == CONFIGFS_MAGIC) + return 0; + if (mount("configfs", CONFIGFS_DIR, "configfs", 0, NULL)) + return -errno; + return 0; +} + +#define NVMET_SUBSYS_DIR CONFIGFS_DIR "/nvmet/subsystems/pseudo_backing_test" +#define NVMET_NS_DIR NVMET_SUBSYS_DIR "/namespaces/1" + +static int nvmet_enable(const char *path) +{ + int ret; + + ret = write_string(NVMET_NS_DIR "/device_path", path); + if (ret) + return ret; + ret = write_string(NVMET_NS_DIR "/enable", "1"); + if (!ret) + ret = write_string(NVMET_NS_DIR "/enable", "0"); + return ret; +} + +TEST(nvmet_backing_file) +{ + char dev[PATH_MAX]; + int lfd; + + if (!can_use_loop()) + SKIP(return, "loop devices unavailable"); + + ASSERT_EQ(setup_tmp(), 0); + if (setup_configfs()) + SKIP(return, "no configfs"); + if (mkdir(NVMET_SUBSYS_DIR, 0755)) + SKIP(return, "no nvme target: %s", strerror(errno)); + EXPECT_EQ(mkdir(NVMET_NS_DIR, 0755), 0); + + /* With direct I/O most pseudo files already fail to open. */ + EXPECT_EQ(write_string(NVMET_NS_DIR "/buffered_io", "1"), 0); + + EXPECT_EQ(create_file("/tmp/file"), 0); + EXPECT_EQ(mkfifo("/tmp/fifo", 0600), 0); + + lfd = create_loop_device(dev, sizeof(dev)); + EXPECT_GE(lfd, 0); + + /* With buffered_io the file backend handles block devices as well. */ + EXPECT_EQ(nvmet_enable("/tmp/file"), 0); + if (lfd >= 0) + EXPECT_EQ(nvmet_enable(dev), 0); + EXPECT_EQ(nvmet_enable(PSEUDO_FILE), -EINVAL); + EXPECT_EQ(nvmet_enable("/tmp/fifo"), -EINVAL); + EXPECT_EQ(nvmet_enable("/dev/null"), -EINVAL); + + if (lfd >= 0) + EXPECT_EQ(close(lfd), 0); + EXPECT_EQ(rmdir(NVMET_NS_DIR), 0); + EXPECT_EQ(rmdir(NVMET_SUBSYS_DIR), 0); +} + +#define FILEIO_HBA_DIR CONFIGFS_DIR "/target/core/fileio_4711" +#define FILEIO_DEV_DIR FILEIO_HBA_DIR "/pseudo_backing_test" +#define FILEIO_PROT_TYPE FILEIO_DEV_DIR "/attrib/pi_prot_type" + +static int fileio_create(const char *path) +{ + char buf[PATH_MAX]; + int ret; + + if (mkdir(FILEIO_DEV_DIR, 0755)) + return -errno; + snprintf(buf, sizeof(buf), "fd_dev_name=%s,fd_dev_size=1048576", path); + ret = write_string(FILEIO_DEV_DIR "/control", buf); + if (!ret) + ret = write_string(FILEIO_DEV_DIR "/enable", "1"); + if (ret) + rmdir(FILEIO_DEV_DIR); + return ret; +} + +static int fileio_enable(const char *path) +{ + int ret; + + ret = fileio_create(path); + if (!ret) + rmdir(FILEIO_DEV_DIR); + return ret; +} + +TEST(fileio_backing_file) +{ + char dev[PATH_MAX]; + int lfd; + + if (!can_use_loop()) + SKIP(return, "loop devices unavailable"); + + ASSERT_EQ(setup_tmp(), 0); + if (setup_configfs()) + SKIP(return, "no configfs"); + if (mkdir(FILEIO_HBA_DIR, 0755)) + SKIP(return, "no fileio backend: %s", strerror(errno)); + + EXPECT_EQ(create_file("/tmp/file"), 0); + EXPECT_EQ(mkfifo("/tmp/fifo", 0600), 0); + + lfd = create_loop_device(dev, sizeof(dev)); + EXPECT_GE(lfd, 0); + + EXPECT_EQ(fileio_enable("/tmp/file"), 0); + if (lfd >= 0) + EXPECT_EQ(fileio_enable(dev), 0); + EXPECT_EQ(fileio_enable(PSEUDO_FILE), -EINVAL); + EXPECT_EQ(fileio_enable("/tmp/fifo"), -EINVAL); + EXPECT_EQ(fileio_enable("/dev/null"), -EINVAL); + + if (lfd >= 0) + EXPECT_EQ(close(lfd), 0); + EXPECT_EQ(rmdir(FILEIO_HBA_DIR), 0); +} + +TEST(fileio_protection_file) +{ + int ret; + + if (geteuid()) + SKIP(return, "needs root"); + + ASSERT_EQ(setup_tmp(), 0); + if (setup_configfs()) + SKIP(return, "no configfs"); + if (mkdir(FILEIO_HBA_DIR, 0755)) + SKIP(return, "no fileio backend: %s", strerror(errno)); + + EXPECT_EQ(create_file("/tmp/file"), 0); + ret = fileio_create("/tmp/file"); + EXPECT_EQ(ret, 0); + if (!ret) { + /* Turning on DIF opens the protection file. */ + EXPECT_EQ(symlink(PSEUDO_FILE, "/tmp/file.protection"), 0); + EXPECT_EQ(write_string(FILEIO_PROT_TYPE, "1"), -EINVAL); + EXPECT_EQ(unlink("/tmp/file.protection"), 0); + + EXPECT_EQ(mkfifo("/tmp/file.protection", 0600), 0); + EXPECT_EQ(write_string(FILEIO_PROT_TYPE, "1"), -EINVAL); + EXPECT_EQ(unlink("/tmp/file.protection"), 0); + + EXPECT_EQ(write_string(FILEIO_PROT_TYPE, "1"), 0); + EXPECT_EQ(write_string(FILEIO_PROT_TYPE, "0"), 0); + EXPECT_EQ(rmdir(FILEIO_DEV_DIR), 0); + } + + EXPECT_EQ(rmdir(FILEIO_HBA_DIR), 0); +} + +#define ZLOOP_CTL "/dev/zloop-control" + +/* One conventional and two sequential zones in /tmp/@id. */ +static int zloop_add(int id) +{ + char buf[128]; + int ret; + + snprintf(buf, sizeof(buf), + "add id=%d,base_dir=/tmp,capacity_mb=12,zone_size_mb=4,conv_zones=1,buffered_io", + id); + ret = write_string(ZLOOP_CTL, buf); + if (!ret) { + snprintf(buf, sizeof(buf), "remove id=%d", id); + ret = write_string(ZLOOP_CTL, buf); + } + return ret; +} + +TEST(zloop_zone_file) +{ + if (geteuid()) + SKIP(return, "needs root"); + if (access(ZLOOP_CTL, W_OK)) + SKIP(return, "no zloop"); + + ASSERT_EQ(setup_tmp(), 0); + + ASSERT_EQ(mkdir("/tmp/4711", 0755), 0); + EXPECT_EQ(zloop_add(4711), 0); + + /* Zone files that already exist are used as they are. */ + ASSERT_EQ(mkdir("/tmp/4712", 0755), 0); + ASSERT_EQ(symlink(PSEUDO_FILE, "/tmp/4712/seq-000001"), 0); + EXPECT_EQ(zloop_add(4712), -EINVAL); + + ASSERT_EQ(mkdir("/tmp/4713", 0755), 0); + ASSERT_EQ(mkfifo("/tmp/4713/seq-000001", 0600), 0); + EXPECT_EQ(zloop_add(4713), -EINVAL); +} + +TEST(erofs_backing_file) +{ + bool refused = false; + char buf[256]; + int fsfd, ret, err; + ssize_t n; + + if (geteuid()) + SKIP(return, "needs root"); + + fsfd = sys_fsopen("erofs", FSOPEN_CLOEXEC); + if (fsfd < 0) + SKIP(return, "no erofs: %s", strerror(errno)); + + ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", + "/sys/kernel/warn_count", 0), 0); + ret = sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0); + err = errno; + if (ret && err == ENOTBLK) { + close(fsfd); + SKIP(return, "no file-backed erofs mounts"); + } + ASSERT_EQ(ret, -1); + ASSERT_EQ(err, EINVAL); + + /* + * Something that isn't an erofs image fails with EINVAL as well so + * check that the source itself was refused. + */ + while ((n = read(fsfd, buf, sizeof(buf) - 1)) > 0) { + buf[n] = '\0'; + if (strstr(buf, "backing file")) + refused = true; + } + EXPECT_TRUE(refused); + EXPECT_EQ(close(fsfd), 0); +} + +TEST_HARNESS_MAIN -- 2.53.0