GRKERNSEC_ROFS Runtime Read-only Mount Protection and GRKERNSEC_SYSCTL
This protection is designed for secure embedded systems as we can read in its description:
config GRKERNSEC_ROFS bool "Runtime read-only mount protection" help If you say Y here, a sysctl option with name "romount_protect" will be created. By setting this option to 1 at runtime, 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 This option acts independently of grsec_lock: once it is set to 1, it cannot be turned off. Therefore, please be mindful of the resulting behavior if this option is enabled in an init script on a read-only filesystem. This feature is mainly intended for secure embedded systems.
Of course, there might be other uses for it too but this is not the scope of this post. Its code is comprised by two functions located at grsecurity/grsec_mount.c file.
int gr_handle_rofs_mount(struct dentry *dentry, struct vfsmount *mnt, int mnt_flags) { #ifdef CONFIG_GRKERNSEC_ROFS if (grsec_enable_rofs && !(mnt_flags & MNT_READONLY)) { gr_log_fs_generic(GR_DO_AUDIT, GR_ROFS_MOUNT_MSG, dentry, mnt); return -EPERM; } else return 0; #endif return 0; } int gr_handle_rofs_blockwrite(struct dentry *dentry, struct vfsmount *mnt, int acc_mode) { #ifdef CONFIG_GRKERNSEC_ROFS if (grsec_enable_rofs && (acc_mode & MAY_WRITE) && dentry->d_inode && S_ISBLK(dentry->d_inode->i_mode)) { gr_log_fs_generic(GR_DO_AUDIT, GR_ROFS_BLOCKWRITE_MSG, dentry, mnt); return -EPERM; } else return 0; #endif return 0; }
The first one, gr_handle_rofs_mount() is a simple logging routine that will return “Permission Denied” after logging the event if a read-only filesystem is used and obviously, the protection is enabled. The second one is the gr_handle_rofs_blockwrite() that will perform the same operation if the protection is enabled, the access list mode includes ‘MAY_WRITE’ flag, the directory entry’s inode exist and it’s dealing with a block device.
To use this through sysctl interface you can use ‘romount_protect’ entry which is enabled regardless of ‘GRKERNSEC_SYSCTL’ which is taken into account for all the other grsecurity options. Here is its description:
config GRKERNSEC_SYSCTL bool "Sysctl support" help If you say Y here, you will be able to change the options that grsecurity runs with at bootup, without having to recompile your kernel. You can echo values to files in /proc/sys/kernel/grsecurity to enable (1) or disable (0) various features. All the sysctl entries are mutable until the "grsec_lock" entry is set to a non-zero value. All features enabled in the kernel configuration are disabled at boot if you do not say Y to the "Turn on features by default" option. All options should be set at startup, and the grsec_lock entry should be set to a non-zero value after all the options are set. *THIS IS EXTREMELY IMPORTANT*
But if we move to kernel/sysctl.c we’ll see that:
static struct ctl_table kern_table[] = { #if defined(CONFIG_GRKERNSEC_SYSCTL) || defined(CONFIG_GRKERNSEC_ROFS) { .procname = "grsecurity", .mode = 0500, .child = grsecurity_table, }, #endif ... /* * NOTE: do not add new entries to this table unless you have read * Documentation/sysctl/ctl_unnumbered.txt */ { .ctl_name = 0 } };
which is defined at grsecurity/grsec_sysctl.c:
#if defined(CONFIG_GRKERNSEC_SYSCTL) || defined(CONFIG_GRKERNSEC_ROFS) struct ctl_table grsecurity_table[] = { ... #ifdef CONFIG_GRKERNSEC_ROFS { .procname = "romount_protect", .data = &grsec_enable_rofs, .maxlen = sizeof(int), .mode = 0600, .proc_handler = &proc_dointvec_minmax, .extra1 = &one, .extra2 = &one, }, #endif ... { } }; #endif
Meaning that if ‘GRKERNSEC_ROFS’ will have its sysctl entry even if ‘GRKERNSEC_SYSCTL’ is disabled. Now, the newly added functions are used firstly at fs/namespace.c where the do_mount() routine is placed.
long do_mount(char *dev_name, char *dir_name, char *type_page, unsigned long flags, void *data_page) { struct path path; int retval = 0; int mnt_flags = 0; ... flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN | MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT | MS_STRICTATIME); if (gr_handle_rofs_mount(path.dentry, path.mnt, mnt_flags)) { retval = -EPERM; goto dput_out; } ... return retval; }
And it will perform the previously described check and possibly disallow access to the given entry. Also, there is a patch applied to fs/namei.c file that will include this code:
static struct file *do_last(struct nameidata *nd, struct path *path, int open_flag, int acc_mode, int mode, const char *pathname) { struct dentry *dir = nd->path.dentry; int flag = open_to_namei_flags(open_flag); struct file *filp; int error = -EISDIR; switch (nd->last_type) { case LAST_DOTDOT: ... /* fallthrough */ case LAST_BIND: audit_inode(pathname, dir); if (gr_handle_rofs_blockwrite(nd->path.dentry, nd->path.mnt, acc_mode)) { error = -EPERM; goto exit; } ... goto ok; } ... return ERR_PTR(error); }
Which is the equivalent code for block devices write operations.
Leave a Reply