[TOMOYO 10/15](repost) Namespace manipulation control functions.

Kentaro Takeda
2007-10-02T07:37:36Z

Mount access control functions for TOMOYO Linux.
TOMOYO Linux checks permission according to
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 | 1019 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1019 insertions(+)

--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/mount.c 2007-10-02 11:26:22.000000000 +0900
@@ -0,0 +1,1019 @@
+/*
+ * security/tomoyo/mount.c
+ *
+ * Mount access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+#include <linux/namei.h>
+#include <linux/mnt_namespace.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 mount_entry *next; /* NULL if none. */
+ const struct path_info *dev_name;
+ const struct path_info *dir_name;
+ const struct path_info *fs_type;
+ unsigned int flags; /* Mount flags. */
+ u8 is_deleted;
+};
+
+struct no_umount_entry {
+ struct no_umount_entry *next; /* NULL if none. */
+ const struct path_info *dir;
+ u8 is_deleted;
+};
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_mount_log(const u8 is_granted,
+ const u8 is_enforce,
+ const char *fmt, ...)
+ __attribute__((format(printf, 3, 4)));
+
+static int tmy_audit_mount_log(const u8 is_granted,
+ const u8 is_enforce,
+ const char *fmt, ...)
+{
+ char *buf1;
+ char *buf2;
+ unsigned int len;
+ va_list args;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ buf1 = tmy_alloc(PAGE_SIZE);
+
+ if (!buf1)
+ return -ENOMEM;
+
+ va_start(args, fmt);
+ len = vsnprintf(buf1, PAGE_SIZE, fmt, args);
+ va_end(args);
+
+ if (len >= PAGE_SIZE) {
+ tmy_free(buf1);
+ return -ENOMEM;
+ }
+
+ buf2 = tmy_init_audit_log(&len);
+
+ if (!buf2) {
+ tmy_free(buf1);
+ return -ENOMEM;
+ }
+
+ snprintf(buf2 + strlen(buf2),
+ len - strlen(buf2),
+ "%s", buf1);
+
+ tmy_free(buf1);
+
+ return tmy_write_audit_log(buf2, is_granted, is_enforce);
+}
+
+/************************ MOUNT RESTRICTION HANDLER ************************/
+
+static void put_filesystem(struct file_system_type *fs)
+{
+ module_put(fs->owner);
+}
+
+static struct mount_entry *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 u8 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 DECLARE_MUTEX(lock);
+ 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, 1, 0, 1, __FUNCTION__))
+ return -EINVAL;
+
+ dev = tmy_save_name(dev_name);
+ if (!dev)
+ return -ENOMEM;
+ dir = tmy_save_name(dir_name);
+ if (!dir)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = mount_list; ptr; ptr = ptr->next) {
+ if (ptr->flags != flags ||
+ tmy_pathcmp(ptr->dev_name, dev) ||
+ tmy_pathcmp(ptr->dir_name, dir) ||
+ tmy_pathcmp(ptr->fs_type, fs))
+ continue;
+
+ if (is_delete) {
+ ptr->is_deleted = 1;
+ error = 0;
+ goto out;
+ } else {
+ if (ptr->is_deleted) {
+ ptr->flags = flags;
+ ptr->is_deleted = 0;
+ } else {
+ if (ptr->flags == flags) {
+ error = 0;
+ goto out; /* No changes. */
+ }
+ ptr->flags = flags;
+ }
+ 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;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = mount_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ mount_list = new_entry;
+
+ error = 0;
+ ptr = new_entry;
+
+out: ;
+ up(&lock);
+
+ 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 is_enforce)
+{
+ int error = -EPERM;
+ const char *realname1 = tmy_realpath(dev_name);
+ const char *realname2 = tmy_realpath(dir_name);
+ const char *exename = tmy_get_exe();
+
+ if (!strcmp(type, MOUNT_REMOUNT_KEYWORD)) {
+
+ tmy_audit_mount_log(0, is_enforce,
+ "mount -o remount %s 0x%lX",
+ realname2 ? realname2 : dir_name,
+ flags);
+ 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_mount_log(0, is_enforce, "mount %s %s %s 0x%lX",
+ type, realname1 ? realname1 : dev_name,
+ realname2 ? realname2 : dir_name, flags);
+ 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_mount_log(0, is_enforce, "mount %s %s 0x%lX", type,
+ realname2 ? realname2 : dir_name, flags);
+ 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_mount_log(0, is_enforce, "mount -t %s %s %s 0x%lX",
+ type, realname1 ? realname1 : dev_name,
+ realname2 ? realname2 : dir_name, flags);
+ 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 is_enforce = tmy_enforce(TMY_RESTRICT_MOUNT);
+ int error = -EPERM;
+
+ if (!tmy_flags(TMY_RESTRICT_MOUNT))
+ 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_mount_log(0, 1, "%s%s%sare given "
+ "for single mount operation",
+ 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_mount_log(0, 1, "%s%s%s%sare given "
+ "for single mount operation",
+ 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;
+ }
+
+ for (ptr = mount_list; ptr; ptr = ptr->next) {
+ 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_mount_log(1, is_enforce,
+ "mount -t %s %s %s 0x%lX",
+ type,
+ requested_dev_name,
+ requested_dir_name, flags);
+ else if (need_dev < 0)
+ tmy_audit_mount_log(1, is_enforce,
+ "mount %s %s %s 0x%lX",
+ type, requested_dev_name,
+ requested_dir_name, flags);
+ else if (!strcmp(type, MOUNT_REMOUNT_KEYWORD))
+ tmy_audit_mount_log(1, is_enforce,
+ "mount -o remount %s 0x%lX",
+ 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_mount_log(1, is_enforce,
+ "mount %s %s 0x%lX",
+ type, requested_dir_name,
+ flags);
+ else
+ tmy_audit_mount_log(1, is_enforce,
+ "mount %s on %s 0x%lX",
+ type, requested_dir_name,
+ flags);
+
+ break;
+ }
+
+ if (error)
+ error = tmy_mount_perm_error(dev_name, dir_name,
+ type, flags, is_enforce);
+
+ if (error && tmy_accept(TMY_RESTRICT_MOUNT, NULL)) {
+ 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 u8 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 mount_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = mount_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (ptr->is_deleted == 0 &&
+ 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))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 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->mnt->mnt_root == vfsmnt->mnt_root &&
+ nd->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;
+ struct mnt_namespace *namespace = current->nsproxy->mnt_ns;
+ u8 is_enforce;
+ char *dir;
+
+ if (!tmy_flags(TMY_DENY_CONCEAL_MOUNT))
+ 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;
+ }
+ }
+
+ is_enforce = tmy_enforce(TMY_DENY_CONCEAL_MOUNT);
+ dir = tmy_realpath_dentry(nd->dentry, nd->mnt);
+
+ if (flag) {
+ int error = -EPERM;
+ if (dir) {
+ const char *exename = tmy_get_exe();
+
+ tmy_audit_mount_log(0, is_enforce, "mount %s", dir);
+ 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;
+ } else {
+ if (dir)
+ tmy_audit_mount_log(1, is_enforce, "mount %s", dir);
+ tmy_free(dir);
+ }
+
+ return 0;
+}
+
+/************************ UMOUNT RESTRICTION HANDLER ************************/
+
+static struct no_umount_entry *no_umount_list;
+
+/* Add or remove a no-unmount entry. */
+static int tmy_add_no_umount_acl(const char *dir, const u8 is_delete)
+{
+ struct no_umount_entry *new_entry;
+ struct no_umount_entry *ptr;
+ const struct path_info *saved_dir;
+ static DECLARE_MUTEX(lock);
+ 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;
+
+ down(&lock);
+
+ for (ptr = no_umount_list; ptr; ptr = ptr->next) {
+ 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;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = no_umount_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ no_umount_list = new_entry;
+
+ error = 0;
+out: ;
+
+ up(&lock);
+
+ 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 u8 is_enforce = tmy_enforce(TMY_RESTRICT_UMOUNT);
+
+ if (!tmy_flags(TMY_RESTRICT_UMOUNT))
+ return 0;
+
+ dir0 = tmy_realpath_dentry(mnt->mnt_root, mnt);
+
+ if (dir0) {
+ struct no_umount_entry *ptr;
+ struct path_info dir;
+
+ dir.name = dir0;
+ tmy_fill_path_info(&dir);
+
+ for (ptr = no_umount_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+ if (tmy_path_match(&dir, ptr->dir))
+ break;
+ }
+
+ if (ptr) {
+ const char *exename = tmy_get_exe();
+
+ tmy_audit_mount_log(0, is_enforce, "umount %s", dir0);
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\nunmount %s\n",
+ exename, dir0) == 0)
+ error = 0;
+
+ tmy_free(exename);
+ } else {
+ tmy_audit_mount_log(1, is_enforce, "umount %s", dir0);
+ 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 u8 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 no_umount_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = no_umount_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (ptr->is_deleted == 0 &&
+ tmy_io_printf(head, TMY_DENY_UNMOUNT "%s\n",
+ ptr->dir->name))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/***** The structure for pivot_root restrictions. *****/
+
+struct pivot_root_entry {
+ struct pivot_root_entry *next;
+ const struct path_info *old_root;
+ const struct path_info *new_root;
+ u8 is_deleted;
+};
+
+/********************** PIVOT_ROOT RESTRICTION HANDLER **********************/
+
+static struct pivot_root_entry *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 u8 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 DECLARE_MUTEX(lock);
+ 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;
+
+ down(&lock);
+
+ for (ptr = pivot_root_list; ptr; ptr = ptr->next) {
+ 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;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = pivot_root_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ pivot_root_list = new_entry;
+
+ error = 0;
+out: ;
+
+ up(&lock);
+
+ 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;
+ char *old_root;
+ char *new_root;
+ u8 is_enforce;
+ struct path_info old_root_dir;
+ struct path_info new_root_dir;
+
+ if (!tmy_flags(TMY_RESTRICT_PIVOT_ROOT))
+ return 0;
+
+ old_root = tmy_realpath_dentry(old_nd->dentry, old_nd->mnt);
+ new_root = tmy_realpath_dentry(new_nd->dentry, new_nd->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;
+
+ for (ptr = pivot_root_list; ptr; ptr = ptr->next) {
+ 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: ;
+
+ is_enforce = tmy_enforce(TMY_RESTRICT_PIVOT_ROOT);
+ tmy_audit_mount_log(!error, is_enforce, "pivot_root %s %s",
+ new_root, old_root);
+
+ if (error) {
+ const char *exename = tmy_get_exe();
+
+ 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 && tmy_accept(TMY_RESTRICT_PIVOT_ROOT, NULL)
+ && 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 u8 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 pivot_root_entry *ptr = head->read_var2;
+ if (!ptr)
+ ptr = pivot_root_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (ptr->is_deleted == 0 &&
+ tmy_io_printf(head, TMY_ALLOW_PIVOT_ROOT "%s %s\n",
+ ptr->new_root->name, ptr->old_root->name))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}