TOMOYO Linux checks mount permission based on device name, mount point, filesystem type and optional flags. TOMOYO Linux also checks permission in umount and pivot_root. Each permission can be automatically accumulated into the policy using 'learning mode'. Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa --- security/tomoyo/mount.c | 908 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 908 insertions(+) --- /dev/null +++ linux-2.6-mm/security/tomoyo/mount.c @@ -0,0 +1,908 @@ +/* + * security/tomoyo/mount.c + * + * Mount access control functions for TOMOYO Linux. + */ + +#include "tomoyo.h" +#include "realpath.h" + +/***** KEYWORDS for mount restrictions. *****/ + +#define MOUNT_BIND_KEYWORD "--bind" +#define MOUNT_MOVE_KEYWORD "--move" +#define MOUNT_REMOUNT_KEYWORD "--remount" +#define MOUNT_MAKE_UNBINDABLE_KEYWORD "--make-unbindable" +#define MOUNT_MAKE_PRIVATE_KEYWORD "--make-private" +#define MOUNT_MAKE_SLAVE_KEYWORD "--make-slave" +#define MOUNT_MAKE_SHARED_KEYWORD "--make-shared" + +/***** The structure for mount restrictions. *****/ + +struct mount_entry { + struct list_head list; + const struct path_info *dev_name; + const struct path_info *dir_name; + const struct path_info *fs_type; + unsigned int flags; /* Mount flags. */ + bool is_deleted; +}; + +struct no_umount_entry { + struct list_head list; + const struct path_info *dir; + bool is_deleted; +}; + +/************************ MOUNT RESTRICTION HANDLER ************************/ + +static LIST_HEAD(mount_list); + +/* Add or remove a mount entry. */ +static int tmy_add_mount_acl(const char *dev_name, + const char *dir_name, + const char *fs_type, + const unsigned int flags, + const bool is_delete) +{ + struct mount_entry *new_entry; + struct mount_entry *ptr; + const struct path_info *fs; + const struct path_info *dev; + const struct path_info *dir; + static DEFINE_MUTEX(mutex); + int error = -ENOMEM; + + fs = tmy_save_name(fs_type); + if (!fs) + return -EINVAL; + + if (!dev_name) + /* Map dev_name to "" for if no dev_name given. */ + dev_name = ""; + if (strcmp(fs->name, MOUNT_REMOUNT_KEYWORD) == 0) + /* Fix dev_name to "any" for remount permission. */ + dev_name = "any"; + if (strcmp(fs->name, MOUNT_MAKE_UNBINDABLE_KEYWORD) == 0 || + strcmp(fs->name, MOUNT_MAKE_PRIVATE_KEYWORD) == 0 || + strcmp(fs->name, MOUNT_MAKE_SLAVE_KEYWORD) == 0 || + strcmp(fs->name, MOUNT_MAKE_SHARED_KEYWORD) == 0) + dev_name = "any"; + + if (!tmy_correct_path(dev_name, 0, 0, 0, __FUNCTION__) || + !tmy_correct_path(dir_name, 0, 0, 0, __FUNCTION__)) + return -EINVAL; + + dev = tmy_save_name(dev_name); + if (!dev) + return -ENOMEM; + dir = tmy_save_name(dir_name); + if (!dir) + return -ENOMEM; + + mutex_lock(&mutex); + + list_for_each_entry(ptr, &mount_list, list) { + if (ptr->flags != flags || + tmy_pathcmp(ptr->dev_name, dev) || + tmy_pathcmp(ptr->dir_name, dir) || + tmy_pathcmp(ptr->fs_type, fs)) + continue; + ptr->is_deleted = is_delete; + error = 0; + goto out; + } + + if (is_delete) { + error = -ENOENT; + goto out; + } + + new_entry = tmy_alloc_element(sizeof(*new_entry)); + if (!new_entry) + goto out; + + new_entry->dev_name = dev; + new_entry->dir_name = dir; + new_entry->fs_type = fs; + new_entry->flags = flags; + list_add_tail_mb(&new_entry->list, &mount_list); + error = 0; +out: ; + mutex_unlock(&mutex); + return error; +} + +/* Print error message for mount request. */ +static inline int tmy_mount_perm_error(char *dev_name, + char *dir_name, + char *type, + unsigned long flags, + const u8 profile, + const unsigned int mode) +{ + int error = -EPERM; + const char *realname1 = tmy_realpath(dev_name); + const char *realname2 = tmy_realpath(dir_name); + const char *exename = tmy_get_exe(); + const bool is_enforce = (mode == 3); + + if (!strcmp(type, MOUNT_REMOUNT_KEYWORD)) { + tmy_audit(KERN_WARNING "TOMOYO-%s: mount -o remount %s 0x%lX " + "(pid=%d:exe=%s): Permission denied.\n", + tmy_getmsg(is_enforce), + realname2 ? realname2 : dir_name, flags, + current->pid, exename); + if (is_enforce && + !tmy_supervisor("# %s is requesting\nmount -o remount %s\n", + exename, realname2 ? realname2 : dir_name)) + error = 0; + + } else if (!strcmp(type, MOUNT_BIND_KEYWORD) || + !strcmp(type, MOUNT_MOVE_KEYWORD)) { + tmy_audit(KERN_WARNING "TOMOYO-%s: mount %s %s %s 0x%lX " + "(pid=%d:exe=%s): Permission denied.\n", + tmy_getmsg(is_enforce), type, + realname1 ? realname1 : dev_name, + realname2 ? realname2 : dir_name, + flags, current->pid, exename); + if (is_enforce && + tmy_supervisor("# %s is requesting\nmount %s %s %s 0x%lX\n", + exename, type, + realname1 ? realname1 : dev_name, + realname2 ? realname2 : dir_name, + flags) == 0) + error = 0; + + } else if (!strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) || + !strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) || + !strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) || + !strcmp(type, MOUNT_MAKE_SHARED_KEYWORD)) { + tmy_audit(KERN_WARNING "TOMOYO-%s: mount %s %s 0x%lX " + "(pid=%d:exe=%s): Permission denied.\n", + tmy_getmsg(is_enforce), type, + realname2 ? realname2 : dir_name, + flags, current->pid, exename); + if (is_enforce && + tmy_supervisor("# %s is requesting\nmount %s %s 0x%lX", + exename, type, + realname2 ? realname2 : dir_name, + flags) == 0) + error = 0; + + } else { + tmy_audit(KERN_WARNING "TOMOYO-%s: mount -t %s %s %s 0x%lX " + "(pid=%d:exe=%s): Permission denied.\n", + tmy_getmsg(is_enforce), type, + realname1 ? realname1 : dev_name, + realname2 ? realname2 : dir_name, + flags, current->pid, exename); + if (is_enforce && + tmy_supervisor("# %s is requesting\n" + "mount -t %s %s %s 0x%lX\n", + exename, type, + realname1 ? realname1 : dev_name, + realname2 ? realname2 : dir_name, + flags) == 0) + error = 0; + + } + + tmy_free(exename); + tmy_free(realname2); + tmy_free(realname1); + return error; +} + +/** + * tmy_mount_perm - check for mount permission. + * @dev_name: pointer to device name. May be NULL. + * @dir_name: pointer to mount point. + * @type: pointer to filesystem. May be NULL. + * @flags: mount flags. + * + * Returns zero if permission granted. + * Returns nonzero if permission denied. + */ +int tmy_mount_perm(char *dev_name, + char *dir_name, + char *type, + unsigned long flags) +{ + const u8 profile = TMY_SECURITY->domain->profile; + const unsigned int mode = tmy_flags(TMY_RESTRICT_MOUNT); + const bool is_enforce = (mode == 3); + int error = -EPERM; + + if (!mode) + return 0; + if (!type) + type = ""; + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + + switch (flags & (MS_REMOUNT | MS_MOVE | MS_BIND)) { + case MS_REMOUNT: + case MS_MOVE: + case MS_BIND: + case 0: + break; + default: + tmy_audit(KERN_WARNING "TOMOYO-ERROR: %s%s%sare given " + "for single mount operation.\n", + flags & MS_REMOUNT ? "'remount' " : "", + flags & MS_MOVE ? "'move' " : "", + flags & MS_BIND ? "'bind' " : ""); + return -EINVAL; + } + + switch (flags & (MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE | MS_SHARED)) { + case MS_UNBINDABLE: + case MS_PRIVATE: + case MS_SLAVE: + case MS_SHARED: + case 0: + break; + default: + tmy_audit(KERN_WARNING "TOMOYO-ERROR: %s%s%s%sare given " + "for single mount operation.\n", + flags & MS_UNBINDABLE ? "'unbindable' " : "", + flags & MS_PRIVATE ? "'private' " : "", + flags & MS_SLAVE ? "'slave' " : "", + flags & MS_SHARED ? "'shared' " : ""); + return -EINVAL; + } + + if (flags & MS_REMOUNT) + error = tmy_mount_perm(dev_name, dir_name, + MOUNT_REMOUNT_KEYWORD, + flags & ~MS_REMOUNT); + else if (flags & MS_MOVE) + error = tmy_mount_perm(dev_name, dir_name, + MOUNT_MOVE_KEYWORD, + flags & ~MS_MOVE); + else if (flags & MS_BIND) + error = tmy_mount_perm(dev_name, dir_name, + MOUNT_BIND_KEYWORD, + flags & ~MS_BIND); + else if (flags & MS_UNBINDABLE) + error = tmy_mount_perm(dev_name, dir_name, + MOUNT_MAKE_UNBINDABLE_KEYWORD, + flags & ~MS_UNBINDABLE); + else if (flags & MS_PRIVATE) + error = tmy_mount_perm(dev_name, dir_name, + MOUNT_MAKE_PRIVATE_KEYWORD, + flags & ~MS_PRIVATE); + else if (flags & MS_SLAVE) + error = tmy_mount_perm(dev_name, dir_name, + MOUNT_MAKE_SLAVE_KEYWORD, + flags & ~MS_SLAVE); + else if (flags & MS_SHARED) + error = tmy_mount_perm(dev_name, dir_name, + MOUNT_MAKE_SHARED_KEYWORD, + flags & ~MS_SHARED); + else { + struct mount_entry *ptr; + struct file_system_type *fstype = NULL; + const char *requested_dir_name = NULL; + const char *requested_dev_name = NULL; + struct path_info rdev; + struct path_info rdir; + int need_dev = 0; + + requested_dir_name = tmy_realpath(dir_name); + if (!requested_dir_name) { + error = -ENOENT; + goto cleanup; + } + rdir.name = requested_dir_name; + tmy_fill_path_info(&rdir); + + /* Compare fs name. */ + fstype = get_fs_type(type); + if (strcmp(type, MOUNT_REMOUNT_KEYWORD) == 0) + /* Needn't to resolve dev_name */; + else if (strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) == 0 || + strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) == 0 || + strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) == 0 || + strcmp(type, MOUNT_MAKE_SHARED_KEYWORD) == 0) + /* Needn't to resolve dev_name */; + else if (strcmp(type, MOUNT_BIND_KEYWORD) == 0 || + strcmp(type, MOUNT_MOVE_KEYWORD) == 0) { + requested_dev_name = tmy_realpath(dev_name); + if (!requested_dev_name) { + error = -ENOENT; + goto cleanup; + } + rdev.name = requested_dev_name; + tmy_fill_path_info(&rdev); + need_dev = -1; + } else if (fstype) { + if (fstype->fs_flags & FS_REQUIRES_DEV) { + requested_dev_name = tmy_realpath(dev_name); + if (!requested_dev_name) { + error = -ENOENT; + goto cleanup; + } + rdev.name = requested_dev_name; + tmy_fill_path_info(&rdev); + need_dev = 1; + } + } else { + error = -ENODEV; + goto cleanup; + } + + list_for_each_entry(ptr, &mount_list, list) { + if (ptr->is_deleted) + continue; + + /* Compare options */ + if (ptr->flags != flags) + continue; + + /* Compare fs name. */ + if (strcmp(type, ptr->fs_type->name)) + continue; + + /* Compare mount point. */ + if (tmy_path_match(&rdir, ptr->dir_name) == 0) + continue; + + /* Compare device name. */ + if (requested_dev_name && + tmy_path_match(&rdev, ptr->dev_name) == 0) + continue; + + /* OK. */ + error = 0; + + if (need_dev > 0) + tmy_audit(KERN_DEBUG "TOMOYO-NOTICE: " + "'mount -t %s %s %s 0x%lX' " + "accepted.\n", + type, requested_dev_name, + requested_dir_name, flags); + else if (need_dev < 0) + tmy_audit(KERN_DEBUG "TOMOYO-NOTICE: " + "'mount %s %s %s 0x%lX' accepted.\n", + type, requested_dev_name, + requested_dir_name, flags); + else if (!strcmp(type, MOUNT_REMOUNT_KEYWORD)) + tmy_audit(KERN_DEBUG "TOMOYO-NOTICE: " + "'mount -o remount %s 0x%lX' " + "accepted.\n", + requested_dir_name, flags); + else if (!strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) || + !strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) || + !strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) || + !strcmp(type, MOUNT_MAKE_SHARED_KEYWORD)) + tmy_audit(KERN_DEBUG "TOMOYO-NOTICE: " + "'mount %s %s 0x%lX' accepted.\n", + type, requested_dir_name, flags); + else + tmy_audit(KERN_DEBUG "TOMOYO-NOTICE: " + "'mount %s on %s 0x%lX' accepted.\n", + type, requested_dir_name, flags); + break; + } + + if (error) + error = tmy_mount_perm_error(dev_name, dir_name, type, + flags, profile, mode); + + if (error && mode == 1) { + tmy_add_mount_acl(need_dev ? + requested_dev_name : dev_name, + requested_dir_name, type, flags, 0); + tmy_update_counter(TMY_UPDATE_SYSTEMPOLICY); + } + +cleanup: + tmy_free(requested_dev_name); + tmy_free(requested_dir_name); + if (fstype) + put_filesystem(fstype); + + } + + if (!is_enforce) + error = 0; + return error; + +} + +/** + * tmy_add_mount_policy - add or delete mount policy. + * @data: a line to parse. + * @is_delete: is this delete request? + * + * Returns zero on success. + * Returns nonzero on failure. + */ +int tmy_add_mount_policy(char *data, const bool is_delete) +{ + char *cp; + char *cp2; + const char *fs; + const char *dev; + const char *dir; + unsigned int flags = 0; + + cp2 = data; + cp = strchr(cp2, ' '); + if (!cp) + return -EINVAL; + *cp = '\0'; + dev = cp2; + + cp2 = cp + 1; + cp = strchr(cp2, ' '); + if (!cp) + return -EINVAL; + *cp = '\0'; + dir = cp2; + + cp2 = cp + 1; + cp = strchr(cp2, ' '); + if (!cp) + return -EINVAL; + *cp = '\0'; + fs = cp2; + + flags = simple_strtoul(cp + 1, NULL, 0); + return tmy_add_mount_acl(dev, dir, fs, flags, is_delete); +} + +/** + * tmy_read_mount_policy - read mount policy. + * @head: pointer to "struct io_buffer". + * + * Returns nonzero if reading incomplete. + * Returns zero otherwise. + */ +int tmy_read_mount_policy(struct io_buffer *head) +{ + struct list_head *pos; + list_for_each_cookie(pos, head->read_var2, &mount_list) { + struct mount_entry *ptr; + ptr = list_entry(pos, struct mount_entry, list); + if (ptr->is_deleted) + continue; + if (tmy_io_printf(head, TMY_ALLOW_MOUNT "%s %s %s 0x%x\n", + ptr->dev_name->name, ptr->dir_name->name, + ptr->fs_type->name, ptr->flags)) + return -ENOMEM; + } + return 0; +} + +static int tmy_find_conceal(struct nameidata *nd, + struct vfsmount *vfsmnt, + struct dentry *dentry) +{ + int flag = 0; + + if (IS_ROOT(dentry) || !d_unhashed(dentry)) { + while (1) { + + if (nd->path.mnt->mnt_root == vfsmnt->mnt_root && + nd->path.dentry == dentry) { + flag = 1; + break; + } + + if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { + + spin_lock(&vfsmount_lock); + + if (vfsmnt->mnt_parent == vfsmnt) { + spin_unlock(&vfsmount_lock); + break; + } + dentry = vfsmnt->mnt_mountpoint; + vfsmnt = vfsmnt->mnt_parent; + + spin_unlock(&vfsmount_lock); + + continue; + } + dentry = dentry->d_parent; + + } + } + + return flag; +} + +/** + * tmy_conceal_mount - check for conceal mount permission. + * @nd: pointer to "struct nameidata". + * + * Returns zero if permission granted. + * Returns nonzero if permission denied. + * + * People seldom mount on directries that have submounts. + * For example, you don't mount on /usr/ directory if /usr/local/ directory + * is already mounted, do you? + * This function forbids such mount requests. + */ +int tmy_conceal_mount(struct nameidata *nd) +{ + int flag = 0; + const unsigned int mode = tmy_flags(TMY_DENY_CONCEAL_MOUNT); + const bool is_enforce = (mode == 3); + struct mnt_namespace *namespace = current->nsproxy->mnt_ns; + + if (!mode) + return 0; + + if (namespace) { + struct list_head *p; + + list_for_each(p, &namespace->list) { + struct vfsmount *vfsmnt = + list_entry(p, struct vfsmount, mnt_list); + struct dentry *dentry = vfsmnt->mnt_root; + + spin_lock(&dcache_lock); + + flag = tmy_find_conceal(nd, vfsmnt, dentry); + + spin_unlock(&dcache_lock); + + if (flag) + break; + } + } + + + if (flag) { + int error = -EPERM; + char *dir; + dir = tmy_realpath_dentry(nd->path.dentry, nd->path.mnt); + if (dir) { + const char *exename = tmy_get_exe(); + tmy_audit("TOMOYO-%s: mount %s (pid=%d:exe=%s): " + "Permission denied.\n", + tmy_getmsg(is_enforce), + dir, current->pid, exename); + if (is_enforce && + tmy_supervisor("# %s is requesting\nmount on %s\n", + exename, dir) == 0) + error = 0; + + tmy_free(exename); + } + tmy_free(dir); + + if (is_enforce) + return error; + } + + return 0; +} + +/************************ UMOUNT RESTRICTION HANDLER ************************/ + +static LIST_HEAD(no_umount_list); + +/* Add or remove a no-unmount entry. */ +static int tmy_add_no_umount_acl(const char *dir, const bool is_delete) +{ + struct no_umount_entry *new_entry; + struct no_umount_entry *ptr; + const struct path_info *saved_dir; + static DEFINE_MUTEX(mutex); + int error = -ENOMEM; + + if (!tmy_correct_path(dir, 1, 0, 1, __FUNCTION__)) + return -EINVAL; + saved_dir = tmy_save_name(dir); + if (!saved_dir) + return -ENOMEM; + + mutex_lock(&mutex); + + list_for_each_entry(ptr, &no_umount_list, list) { + if (ptr->dir == saved_dir) { + ptr->is_deleted = is_delete; + error = 0; + goto out; + } + } + + if (is_delete) { + error = -ENOENT; + goto out; + } + + new_entry = tmy_alloc_element(sizeof(*new_entry)); + if (!new_entry) + goto out; + + new_entry->dir = saved_dir; + list_add_tail_mb(&new_entry->list, &no_umount_list); + error = 0; +out: ; + + mutex_unlock(&mutex); + + return error; +} + +/** + * tmy_umount_perm - check for no-unmount permission. + * @mnt: pointer to "struct vfsmount". + * + * Returns zero if permission granted. + * Returns nonzero if permission denied. + */ +int tmy_umount_perm(struct vfsmount *mnt) +{ + int error = -EPERM; + const char *dir0; + const unsigned int mode = tmy_flags(TMY_RESTRICT_UMOUNT); + const bool is_enforce = (mode == 3); + + if (!mode) + return 0; + + dir0 = tmy_realpath_dentry(mnt->mnt_root, mnt); + + if (dir0) { + struct no_umount_entry *ptr; + struct path_info dir; + bool found = 0; + + dir.name = dir0; + tmy_fill_path_info(&dir); + + list_for_each_entry(ptr, &no_umount_list, list) { + if (ptr->is_deleted) + continue; + if (tmy_path_match(&dir, ptr->dir)) { + found = 1; + break; + } + } + + if (found) { + const char *exename = tmy_get_exe(); + tmy_audit("TOMOYO-%s: umount %s (pid=%d:exe=%s): " + "Permission denied.\n", + tmy_getmsg(is_enforce), + dir0, current->pid, exename); + if (is_enforce && + tmy_supervisor("# %s is requesting\nunmount %s\n", + exename, dir0) == 0) + error = 0; + + tmy_free(exename); + } else { + error = 0; + } + + tmy_free(dir0); + } + + if (!is_enforce) + error = 0; + return error; +} + +/** + * tmy_add_no_umount_policy - add or delete no-unmount policy. + * @data: a line to parse. + * @is_delete: is this delete request? + * + * Returns zero on success. + * Returns nonzero on failure. + */ +int tmy_add_no_umount_policy(char *data, const bool is_delete) +{ + return tmy_add_no_umount_acl(data, is_delete); +} + +/** + * tmy_read_no_umount_policy - read no-unmount policy. + * @head: pointer to "struct io_buffer". + * + * Returns nonzero if reading incomplete. + * Returns zero otherwise. + */ +int tmy_read_no_umount_policy(struct io_buffer *head) +{ + struct list_head *pos; + list_for_each_cookie(pos, head->read_var2, &no_umount_list) { + struct no_umount_entry *ptr; + ptr = list_entry(pos, struct no_umount_entry, list); + if (ptr->is_deleted) + continue; + if (tmy_io_printf(head, TMY_DENY_UNMOUNT "%s\n", + ptr->dir->name)) + return -ENOMEM; + } + return 0; +} + +/***** The structure for pivot_root restrictions. *****/ + +struct pivot_root_entry { + struct list_head list; + const struct path_info *old_root; + const struct path_info *new_root; + bool is_deleted; +}; + +/********************** PIVOT_ROOT RESTRICTION HANDLER **********************/ + +static LIST_HEAD(pivot_root_list); + +/* Add or remove a pivot_root entry. */ +static int tmy_add_pivot_root_acl(const char *old_root, + const char *new_root, + const bool is_delete) +{ + struct pivot_root_entry *new_entry; + struct pivot_root_entry *ptr; + const struct path_info *saved_old_root; + const struct path_info *saved_new_root; + static DEFINE_MUTEX(mutex); + int error = -ENOMEM; + + if (!tmy_correct_path(old_root, 1, 0, 1, __FUNCTION__) || + !tmy_correct_path(new_root, 1, 0, 1, __FUNCTION__)) + return -EINVAL; + + saved_old_root = tmy_save_name(old_root); + if (!saved_old_root) + return -ENOMEM; + saved_new_root = tmy_save_name(new_root); + if (!saved_new_root) + return -ENOMEM; + + mutex_lock(&mutex); + + list_for_each_entry(ptr, &pivot_root_list, list) { + if (ptr->old_root == saved_old_root && + ptr->new_root == saved_new_root) { + ptr->is_deleted = is_delete; + error = 0; + goto out; + } + } + + if (is_delete) { + error = -ENOENT; + goto out; + } + + new_entry = tmy_alloc_element(sizeof(*new_entry)); + if (!new_entry) + goto out; + + new_entry->old_root = saved_old_root; + new_entry->new_root = saved_new_root; + list_add_tail_mb(&new_entry->list, &pivot_root_list); + error = 0; +out: ; + + mutex_unlock(&mutex); + + return error; +} + +/** + * tmy_pivot_root_perm - check for pivot_root permission. + * @old_nd: pointer to "struct nameidata". + * @new_nd: pointer to "struct nameidata". + * + * Returns zero if permission granted. + * Returns nonzero if permission denied. + */ +int tmy_pivot_root_perm(struct nameidata *old_nd, struct nameidata *new_nd) +{ + int error = -EPERM; + const unsigned int mode = tmy_flags(TMY_RESTRICT_PIVOT_ROOT); + const bool is_enforce = (mode == 3); + char *old_root; + char *new_root; + struct path_info old_root_dir; + struct path_info new_root_dir; + + if (!mode) + return 0; + + old_root = tmy_realpath_dentry(old_nd->path.dentry, old_nd->path.mnt); + new_root = tmy_realpath_dentry(new_nd->path.dentry, new_nd->path.mnt); + + if (!old_root || !new_root) + goto out; + + old_root_dir.name = old_root; + tmy_fill_path_info(&old_root_dir); + new_root_dir.name = new_root; + tmy_fill_path_info(&new_root_dir); + + if (old_root_dir.is_dir && new_root_dir.is_dir) { + struct pivot_root_entry *ptr; + list_for_each_entry(ptr, &pivot_root_list, list) { + if (ptr->is_deleted) + continue; + if (tmy_path_match(&old_root_dir, ptr->old_root) && + tmy_path_match(&new_root_dir, ptr->new_root)) { + error = 0; + break; + } + } + } + +out: ; + if (error) { + const char *exename = tmy_get_exe(); + tmy_audit("TOMOYO-%s: pivot_root %s %s (pid=%d:exe=%s): " + "Permission denied.\n", tmy_getmsg(is_enforce), + new_root, old_root, current->pid, exename); + if (is_enforce && + tmy_supervisor("# %s is requesting\npivot_root %s %s\n", + exename, new_root, old_root) == 0) + error = 0; + + if (exename) + tmy_free(exename); + + if (!is_enforce && mode == 1 && old_root && new_root) { + tmy_add_pivot_root_acl(old_root, new_root, 0); + tmy_update_counter(TMY_UPDATE_SYSTEMPOLICY); + } + + if (!is_enforce) + error = 0; + } + + tmy_free(old_root); + tmy_free(new_root); + return error; +} + +/** + * tmy_add_pivot_root_policy - add or delete pivot_root policy. + * @data: a line to parse. + * @is_delete: is this delete request? + * + * Returns zero on success. + * Returns nonzero on failure. + */ +int tmy_add_pivot_root_policy(char *data, const bool is_delete) +{ + char *cp = strchr(data, ' '); + + if (!cp) + return -EINVAL; + *cp++ = '\0'; + + return tmy_add_pivot_root_acl(cp, data, is_delete); +} + +/** + * tmy_read_pivot_root_policy - read pivot_root policy. + * @head: pointer to "struct io_buffer". + * + * Returns nonzero if reading incomplete. + * Returns zero otherwise. + */ +int tmy_read_pivot_root_policy(struct io_buffer *head) +{ + struct list_head *pos; + list_for_each_cookie(pos, head->read_var2, &pivot_root_list) { + struct pivot_root_entry *ptr; + ptr = list_entry(pos, struct pivot_root_entry, list); + if (ptr->is_deleted) + continue; + if (tmy_io_printf(head, TMY_ALLOW_PIVOT_ROOT "%s %s\n", + ptr->new_root->name, ptr->old_root->name)) + return -ENOMEM; + } + return 0; +} --