48f65f6
From 14588dfe2e411056df5ba85ef88ad51730a2fa0a Mon Sep 17 00:00:00 2001
48f65f6
From: "Eric W. Biederman" <ebiederm@xmission.com>
48f65f6
Date: Sat, 15 Aug 2015 20:27:13 -0500
48f65f6
Subject: [PATCH 2/2] vfs: Test for and handle paths that are unreachable from
48f65f6
 their mnt_root
48f65f6
48f65f6
commit 397d425dc26da728396e66d392d5dcb8dac30c37 upstream.
48f65f6
48f65f6
In rare cases a directory can be renamed out from under a bind mount.
48f65f6
In those cases without special handling it becomes possible to walk up
48f65f6
the directory tree to the root dentry of the filesystem and down
48f65f6
from the root dentry to every other file or directory on the filesystem.
48f65f6
48f65f6
Like division by zero .. from an unconnected path can not be given
48f65f6
a useful semantic as there is no predicting at which path component
48f65f6
the code will realize it is unconnected.  We certainly can not match
48f65f6
the current behavior as the current behavior is a security hole.
48f65f6
48f65f6
Therefore when encounting .. when following an unconnected path
48f65f6
return -ENOENT.
48f65f6
48f65f6
- Add a function path_connected to verify path->dentry is reachable
48f65f6
  from path->mnt.mnt_root.  AKA to validate that rename did not do
48f65f6
  something nasty to the bind mount.
48f65f6
48f65f6
  To avoid races path_connected must be called after following a path
48f65f6
  component to it's next path component.
48f65f6
48f65f6
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
48f65f6
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
48f65f6
---
48f65f6
 fs/namei.c | 27 +++++++++++++++++++++++++--
48f65f6
 1 file changed, 25 insertions(+), 2 deletions(-)
48f65f6
48f65f6
diff --git a/fs/namei.c b/fs/namei.c
48f65f6
index 1c2105ed20c5..29b927938b8c 100644
48f65f6
--- a/fs/namei.c
48f65f6
+++ b/fs/namei.c
48f65f6
@@ -560,6 +560,24 @@ static int __nd_alloc_stack(struct nameidata *nd)
48f65f6
 	return 0;
48f65f6
 }
48f65f6
 
48f65f6
+/**
48f65f6
+ * path_connected - Verify that a path->dentry is below path->mnt.mnt_root
48f65f6
+ * @path: nameidate to verify
48f65f6
+ *
48f65f6
+ * Rename can sometimes move a file or directory outside of a bind
48f65f6
+ * mount, path_connected allows those cases to be detected.
48f65f6
+ */
48f65f6
+static bool path_connected(const struct path *path)
48f65f6
+{
48f65f6
+	struct vfsmount *mnt = path->mnt;
48f65f6
+
48f65f6
+	/* Only bind mounts can have disconnected paths */
48f65f6
+	if (mnt->mnt_root == mnt->mnt_sb->s_root)
48f65f6
+		return true;
48f65f6
+
48f65f6
+	return is_subdir(path->dentry, mnt->mnt_root);
48f65f6
+}
48f65f6
+
48f65f6
 static inline int nd_alloc_stack(struct nameidata *nd)
48f65f6
 {
48f65f6
 	if (likely(nd->depth != EMBEDDED_LEVELS))
48f65f6
@@ -1296,6 +1314,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
48f65f6
 				return -ECHILD;
48f65f6
 			nd->path.dentry = parent;
48f65f6
 			nd->seq = seq;
48f65f6
+			if (unlikely(!path_connected(&nd->path)))
48f65f6
+				return -ENOENT;
48f65f6
 			break;
48f65f6
 		} else {
48f65f6
 			struct mount *mnt = real_mount(nd->path.mnt);
48f65f6
@@ -1396,7 +1416,7 @@ static void follow_mount(struct path *path)
48f65f6
 	}
48f65f6
 }
48f65f6
 
48f65f6
-static void follow_dotdot(struct nameidata *nd)
48f65f6
+static int follow_dotdot(struct nameidata *nd)
48f65f6
 {
48f65f6
 	if (!nd->root.mnt)
48f65f6
 		set_root(nd);
48f65f6
@@ -1412,6 +1432,8 @@ static void follow_dotdot(struct nameidata *nd)
48f65f6
 			/* rare case of legitimate dget_parent()... */
48f65f6
 			nd->path.dentry = dget_parent(nd->path.dentry);
48f65f6
 			dput(old);
48f65f6
+			if (unlikely(!path_connected(&nd->path)))
48f65f6
+				return -ENOENT;
48f65f6
 			break;
48f65f6
 		}
48f65f6
 		if (!follow_up(&nd->path))
48f65f6
@@ -1419,6 +1441,7 @@ static void follow_dotdot(struct nameidata *nd)
48f65f6
 	}
48f65f6
 	follow_mount(&nd->path);
48f65f6
 	nd->inode = nd->path.dentry->d_inode;
48f65f6
+	return 0;
48f65f6
 }
48f65f6
 
48f65f6
 /*
48f65f6
@@ -1634,7 +1657,7 @@ static inline int handle_dots(struct nameidata *nd, int type)
48f65f6
 		if (nd->flags & LOOKUP_RCU) {
48f65f6
 			return follow_dotdot_rcu(nd);
48f65f6
 		} else
48f65f6
-			follow_dotdot(nd);
48f65f6
+			return follow_dotdot(nd);
48f65f6
 	}
48f65f6
 	return 0;
48f65f6
 }
48f65f6
-- 
48f65f6
2.4.3
48f65f6