[TOMOYO #6 retry 15/21] Namespace manipulation control functions.

Kentaro Takeda
2008-01-09T01:01:22Z

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 <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
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 "<NULL>" for if no dev_name given. */
+ dev_name = "<NULL>";
+ 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 = "<NULL>";
+ 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;
+}

--