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