vfs: Don't copy mount bind mounts of /proc/<pid>/ns/mnt between namespaces
authorEric W. Biederman <ebiederm@xmission.com>
Sat, 30 Mar 2013 08:35:18 +0000 (01:35 -0700)
committerEric W. Biederman <ebiederm@xmission.com>
Tue, 27 Aug 2013 01:42:15 +0000 (18:42 -0700)
Don't copy bind mounts of /proc/<pid>/ns/mnt between namespaces.
These files hold references to a mount namespace and copying them
between namespaces could result in a reference counting loop.

The current mnt_ns_loop test prevents loops on the assumption that
mounts don't cross between namespaces.  Unfortunately unsharing a
mount namespace and shared substrees can both cause mounts to
propogate between mount namespaces.

Add two flags CL_COPY_UNBINDABLE and CL_COPY_MNT_NS_FILE are added to
control this behavior, and CL_COPY_ALL is redefined as both of them.

Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
fs/namespace.c
fs/pnode.h

index 7e16a730559ca9b0e4ac016f9ff142d5e023317a..64627f883bf2005c835eaa0fad791ebe566ec4a2 100644 (file)
@@ -1355,14 +1355,11 @@ SYSCALL_DEFINE1(oldumount, char __user *, name)
 
 #endif
 
-static bool mnt_ns_loop(struct path *path)
+static bool is_mnt_ns_file(struct dentry *dentry)
 {
-       /* Could bind mounting the mount namespace inode cause a
-        * mount namespace loop?
-        */
-       struct inode *inode = path->dentry->d_inode;
+       /* Is this a proxy for a mount namespace? */
+       struct inode *inode = dentry->d_inode;
        struct proc_ns *ei;
-       struct mnt_namespace *mnt_ns;
 
        if (!proc_ns_inode(inode))
                return false;
@@ -1371,7 +1368,19 @@ static bool mnt_ns_loop(struct path *path)
        if (ei->ns_ops != &mntns_operations)
                return false;
 
-       mnt_ns = ei->ns;
+       return true;
+}
+
+static bool mnt_ns_loop(struct dentry *dentry)
+{
+       /* Could bind mounting the mount namespace inode cause a
+        * mount namespace loop?
+        */
+       struct mnt_namespace *mnt_ns;
+       if (!is_mnt_ns_file(dentry))
+               return false;
+
+       mnt_ns = get_proc_ns(dentry->d_inode)->ns;
        return current->nsproxy->mnt_ns->seq >= mnt_ns->seq;
 }
 
@@ -1380,7 +1389,10 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
 {
        struct mount *res, *p, *q, *r, *parent;
 
-       if (!(flag & CL_COPY_ALL) && IS_MNT_UNBINDABLE(mnt))
+       if (!(flag & CL_COPY_UNBINDABLE) && IS_MNT_UNBINDABLE(mnt))
+               return ERR_PTR(-EINVAL);
+
+       if (!(flag & CL_COPY_MNT_NS_FILE) && is_mnt_ns_file(dentry))
                return ERR_PTR(-EINVAL);
 
        res = q = clone_mnt(mnt, dentry, flag);
@@ -1397,7 +1409,13 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
                        continue;
 
                for (s = r; s; s = next_mnt(s, r)) {
-                       if (!(flag & CL_COPY_ALL) && IS_MNT_UNBINDABLE(s)) {
+                       if (!(flag & CL_COPY_UNBINDABLE) &&
+                           IS_MNT_UNBINDABLE(s)) {
+                               s = skip_mnt_tree(s);
+                               continue;
+                       }
+                       if (!(flag & CL_COPY_MNT_NS_FILE) &&
+                           is_mnt_ns_file(s->mnt.mnt_root)) {
                                s = skip_mnt_tree(s);
                                continue;
                        }
@@ -1733,7 +1751,7 @@ static int do_loopback(struct path *path, const char *old_name,
                return err;
 
        err = -EINVAL;
-       if (mnt_ns_loop(&old_path))
+       if (mnt_ns_loop(old_path.dentry))
                goto out; 
 
        mp = lock_mount(path);
@@ -1755,7 +1773,7 @@ static int do_loopback(struct path *path, const char *old_name,
                goto out2;
 
        if (recurse)
-               mnt = copy_tree(old, old_path.dentry, 0);
+               mnt = copy_tree(old, old_path.dentry, CL_COPY_MNT_NS_FILE);
        else
                mnt = clone_mnt(old, old_path.dentry, 0);
 
@@ -2417,7 +2435,7 @@ static struct mnt_namespace *dup_mnt_ns(struct mnt_namespace *mnt_ns,
 
        namespace_lock();
        /* First pass: copy the tree topology */
-       copy_flags = CL_COPY_ALL | CL_EXPIRE;
+       copy_flags = CL_COPY_UNBINDABLE | CL_EXPIRE;
        if (user_ns != mnt_ns->user_ns)
                copy_flags |= CL_SHARED_TO_SLAVE | CL_UNPRIVILEGED;
        new = copy_tree(old, old->mnt.mnt_root, copy_flags);
@@ -2452,6 +2470,10 @@ static struct mnt_namespace *dup_mnt_ns(struct mnt_namespace *mnt_ns,
                }
                p = next_mnt(p, old);
                q = next_mnt(q, new);
+               if (!q)
+                       break;
+               while (p->mnt.mnt_root != q->mnt.mnt_root)
+                       p = next_mnt(p, old);
        }
        namespace_unlock();
 
index b091445c1c4ac4472c09adb54496f7af98060a42..59e7eda1851ec447d705a02866c19a07d91d2c99 100644 (file)
 
 #define CL_EXPIRE              0x01
 #define CL_SLAVE               0x02
-#define CL_COPY_ALL            0x04
+#define CL_COPY_UNBINDABLE     0x04
 #define CL_MAKE_SHARED                 0x08
 #define CL_PRIVATE             0x10
 #define CL_SHARED_TO_SLAVE     0x20
 #define CL_UNPRIVILEGED                0x40
+#define CL_COPY_MNT_NS_FILE    0x80
+
+#define CL_COPY_ALL            (CL_COPY_UNBINDABLE | CL_COPY_MNT_NS_FILE)
 
 static inline void set_mnt_shared(struct mount *mnt)
 {