linux-kernel November 2007 archive
Main Archive Page > Month Archives  > linux-kernel archives
linux-kernel: [TOMOYO #5 12/18] Namespace manipulation control f

[TOMOYO #5 12/18] Namespace manipulation control functions.

From: <penguin-kernel_at_nospam>
Date: Fri Nov 16 2007 - 17:34:51 GMT
To: akpm@linux-foundation.org


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 1970-01-01 00:00:00.000000000 +0000
    +++ linux-2.6-mm/security/tomoyo/mount.c 2007-11-14 15:59:44.000000000 +0900
    @@ -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, 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;
    +
    + 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;
    +}
-- - To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html