diff --git a/Documentation/admin-guide/sysctl/fs.rst b/Documentation/admin-guide/sysctl/fs.rst index 2a501c9ddc556..ff3a712778b63 100644 --- a/Documentation/admin-guide/sysctl/fs.rst +++ b/Documentation/admin-guide/sysctl/fs.rst @@ -45,6 +45,7 @@ Currently, these files are in /proc/sys/fs: - protected_hardlinks - protected_regular - protected_symlinks +- romount_protect - suid_dumpable - super-max - super-nr @@ -272,6 +273,23 @@ follower match, or when the directory owner matches the symlink's owner. This protection is based on the restrictions in Openwall and grsecurity. +romount_protect +--------------- + +This toggle enables read-only mount protection. + +If romount_protect is set to (0), there are no protections. +If romount_protect is set to (1), filesystems will be +protected in the following ways: + * No new writable mounts will be allowed + * Existing read-only mounts won't be able to be remounted read/write + * Write operations will be denied on all block devices + +Once romount_protect is set to (1), it cannot be disabled. + +This feature is mainly intended for secure embedded systems. + + suid_dumpable: -------------- diff --git a/fs/Makefile b/fs/Makefile index 3215fe205256d..ff53aea405b79 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -14,7 +14,7 @@ obj-y := open.o read_write.o file_table.o super.o \ pnode.o splice.o sync.o utimes.o d_path.o \ stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \ fs_types.o fs_context.o fs_parser.o fsopen.o init.o \ - kernel_read_file.o remap_range.o + kernel_read_file.o remap_range.o rofs.o ifeq ($(CONFIG_BLOCK),y) obj-y += buffer.o block_dev.o direct-io.o mpage.o diff --git a/fs/namei.c b/fs/namei.c index c34c2be24c6a1..65418ad179187 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2998,6 +2998,9 @@ static int may_open(struct user_namespace *mnt_userns, const struct path *path, if (flag & O_NOATIME && !inode_owner_or_capable(mnt_userns, inode)) return -EPERM; + if (handle_rofs_blockwrite(dentry, path->mnt, acc_mode)) + return -EPERM; + return 0; } diff --git a/fs/namespace.c b/fs/namespace.c index 56bb5a5fdc0d0..90ebe43fe216d 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "pnode.h" #include "internal.h" @@ -3219,6 +3220,11 @@ int path_mount(const char *dev_name, struct path *path, SB_LAZYTIME | SB_I_VERSION); + if (handle_rofs_mount(path.dentry, path.mnt, mnt_flags)) { + retval = -EPERM; + goto dput_out; + } + if ((flags & (MS_REMOUNT | MS_BIND)) == (MS_REMOUNT | MS_BIND)) return do_reconfigure_mnt(path, mnt_flags); if (flags & MS_REMOUNT) diff --git a/fs/rofs.c b/fs/rofs.c new file mode 100644 index 0000000000000..54336a9989200 --- /dev/null +++ b/fs/rofs.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + +int enable_rofs __read_mostly = 0; + +int +handle_rofs_mount(struct dentry *dentry, struct vfsmount *mnt, int mnt_flags) +{ + if (enable_rofs && !(mnt_flags & MNT_READONLY)) + return -EPERM; + else + return 0; +} + +int +handle_rofs_blockwrite(struct dentry *dentry, struct vfsmount *mnt, int acc_mode) +{ + struct inode *inode = d_backing_inode(dentry); + + if (enable_rofs && (acc_mode & MAY_WRITE) && + inode && (S_ISBLK(inode->i_mode) || (S_ISCHR(inode->i_mode) && imajor(inode) == RAW_MAJOR))) + return -EPERM; + else + return 0; +} diff --git a/include/linux/mount.h b/include/linux/mount.h index 5d92a7e1a742d..0e94301136ada 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h @@ -120,4 +120,8 @@ extern bool path_is_mountpoint(const struct path *path); extern void kern_unmount_array(struct vfsmount *mnt[], unsigned int num); +extern int enable_rofs; +extern int handle_rofs_mount(struct dentry *dentry, struct vfsmount *mnt, int mnt_flags); +extern int handle_rofs_blockwrite(struct dentry *dentry, struct vfsmount *mnt, int acc_mode); + #endif /* _LINUX_MOUNT_H */ diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 36470990b2e6e..28bacad11135f 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -3362,6 +3362,15 @@ static struct ctl_table fs_table[] = { .extra1 = SYSCTL_ZERO, .extra2 = &two, }, + { + .procname = "romount_protect", + .data = &enable_rofs, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax_sysadmin, + .extra1 = SYSCTL_ONE, + .extra2 = SYSCTL_ONE, + }, { .procname = "suid_dumpable", .data = &suid_dumpable,