vfs: Add setns support for the mount namespace
[firefly-linux-kernel-4.4.55.git] / fs / namespace.c
index 24960626bb6bfc7b9eff78631876d4eb7ca7a40e..d287e7e7464429c111d61d12eeb7195f1a1dfc74 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/fs_struct.h>   /* get_fs_root et.al. */
 #include <linux/fsnotify.h>    /* fsnotify_vfsmount_delete */
 #include <linux/uaccess.h>
+#include <linux/proc_fs.h>
 #include "pnode.h"
 #include "internal.h"
 
@@ -1308,6 +1309,26 @@ static int mount_is_safe(struct path *path)
 #endif
 }
 
+static bool mnt_ns_loop(struct path *path)
+{
+       /* Could bind mounting the mount namespace inode cause a
+        * mount namespace loop?
+        */
+       struct inode *inode = path->dentry->d_inode;
+       struct proc_inode *ei;
+       struct mnt_namespace *mnt_ns;
+
+       if (!proc_ns_inode(inode))
+               return false;
+
+       ei = PROC_I(inode);
+       if (ei->ns_ops != &mntns_operations)
+               return false;
+
+       mnt_ns = ei->ns;
+       return current->nsproxy->mnt_ns->seq >= mnt_ns->seq;
+}
+
 struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
                                        int flag)
 {
@@ -1655,6 +1676,10 @@ static int do_loopback(struct path *path, const char *old_name,
        if (err)
                return err;
 
+       err = -EINVAL;
+       if (mnt_ns_loop(&old_path))
+               goto out; 
+
        err = lock_mount(path);
        if (err)
                goto out;
@@ -2261,6 +2286,15 @@ dput_out:
        return retval;
 }
 
+/*
+ * Assign a sequence number so we can detect when we attempt to bind
+ * mount a reference to an older mount namespace into the current
+ * mount namespace, preventing reference counting loops.  A 64bit
+ * number incrementing at 10Ghz will take 12,427 years to wrap which
+ * is effectively never, so we can ignore the possibility.
+ */
+static atomic64_t mnt_ns_seq = ATOMIC64_INIT(1);
+
 static struct mnt_namespace *alloc_mnt_ns(void)
 {
        struct mnt_namespace *new_ns;
@@ -2268,6 +2302,7 @@ static struct mnt_namespace *alloc_mnt_ns(void)
        new_ns = kmalloc(sizeof(struct mnt_namespace), GFP_KERNEL);
        if (!new_ns)
                return ERR_PTR(-ENOMEM);
+       new_ns->seq = atomic64_add_return(1, &mnt_ns_seq);
        atomic_set(&new_ns->count, 1);
        new_ns->root = NULL;
        INIT_LIST_HEAD(&new_ns->list);
@@ -2681,3 +2716,63 @@ bool our_mnt(struct vfsmount *mnt)
 {
        return check_mnt(real_mount(mnt));
 }
+
+static void *mntns_get(struct task_struct *task)
+{
+       struct mnt_namespace *ns = NULL;
+       struct nsproxy *nsproxy;
+
+       rcu_read_lock();
+       nsproxy = task_nsproxy(task);
+       if (nsproxy) {
+               ns = nsproxy->mnt_ns;
+               get_mnt_ns(ns);
+       }
+       rcu_read_unlock();
+
+       return ns;
+}
+
+static void mntns_put(void *ns)
+{
+       put_mnt_ns(ns);
+}
+
+static int mntns_install(struct nsproxy *nsproxy, void *ns)
+{
+       struct fs_struct *fs = current->fs;
+       struct mnt_namespace *mnt_ns = ns;
+       struct path root;
+
+       if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SYS_CHROOT))
+               return -EINVAL;
+
+       if (fs->users != 1)
+               return -EINVAL;
+
+       get_mnt_ns(mnt_ns);
+       put_mnt_ns(nsproxy->mnt_ns);
+       nsproxy->mnt_ns = mnt_ns;
+
+       /* Find the root */
+       root.mnt    = &mnt_ns->root->mnt;
+       root.dentry = mnt_ns->root->mnt.mnt_root;
+       path_get(&root);
+       while(d_mountpoint(root.dentry) && follow_down_one(&root))
+               ;
+
+       /* Update the pwd and root */
+       set_fs_pwd(fs, &root);
+       set_fs_root(fs, &root);
+
+       path_put(&root);
+       return 0;
+}
+
+const struct proc_ns_operations mntns_operations = {
+       .name           = "mnt",
+       .type           = CLONE_NEWNS,
+       .get            = mntns_get,
+       .put            = mntns_put,
+       .install        = mntns_install,
+};