dcache: Handle escaped paths in prepend_path
PATCH 1/2 dcache: Handle escaped paths in prepend_path
From: "Eric W. Biederman" <[email protected]>
commit cde93be45a8a90d8c264c776fab63487b5038a65 upstream.
A rename can result in a dentry that by walking up d_parent will never reach it's mnt_root. For lack of a better term I call this an escaped path.
prepend_path is called by four different functions __d_path, d_absolute_path, d_path, and getcwd.
__d_path only wants to see paths are connected to the root it passes in. So __d_path needs prepend_path to return an error.
d_absolute_path similarly wants to see paths that are connected to some root. Escaped paths are not connected to any mnt_root so d_absolute_path needs prepend_path to return an error greater than 1. So escaped paths will be treated like paths on lazily unmounted mounts.
getcwd needs to prepend "(unreachable)" so getcwd also needs prepend_path to return an error.
d_path is the interesting hold out. d_path just wants to print something, and does not care about the weird cases. Which raises the question what should be printed?
Given that <escaped_path>/<anything> should result in -ENOENT I believe it is desirable for escaped paths to be printed as empty paths. As there are not really any meaninful path components when considered from the perspective of a mount tree.
So tweak prepend_path to return an empty path with an new error code of 3 when it encounters an escaped path.
--- fs/dcache.c | 7 +++++++ 1 file changed, 7 insertions(+)
fs/dcache.c +7 -0
@@ -2923,6 +2923,13 @@ restart:if (dentry == vfsmnt->mnt_root ||IS_ROOT (dentry)) {struct mount *parent =ACCESS_ONCE (mnt->mnt_parent); +/* Escaped? */ +if (dentry != vfsmnt->mnt_root) { + bptr = *buffer; + blen = *buflen; + error =3 ; +break ; + }/* Global root? */ if (mnt != parent) { dentry =ACCESS_ONCE (mnt->mnt_mountpoint);
Signature · 1 line
2.1.0
PATCH 2/2 vfs: Test for and handle paths that are unreachable from their mnt_root
From: "Eric W. Biederman" <[email protected]>
commit 397d425dc26da728396e66d392d5dcb8dac30c37 upstream.
In rare cases a directory can be renamed out from under a bind mount. In those cases without special handling it becomes possible to walk up the directory tree to the root dentry of the filesystem and down from the root dentry to every other file or directory on the filesystem.
Like division by zero .. from an unconnected path can not be given a useful semantic as there is no predicting at which path component the code will realize it is unconnected. We certainly can not match the current behavior as the current behavior is a security hole.
Therefore when encounting .. when following an unconnected path return -ENOENT.
- Add a function path_connected to verify path->dentry is reachable from path->mnt.mnt_root. AKA to validate that rename did not do something nasty to the bind mount.
To avoid races path_connected must be called after following a path component to it's next path component.
--- fs/namei.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-)
fs/namei.c +25 -2
@@ -1172,6 +1172,24 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path, !(path->dentry->d_flags &DCACHE_NEED_AUTOMOUNT ); } +/** +* path_connected - Verify that a path->dentry is below path->mnt.mnt_root +* @path: nameidate to verify +* +* Rename can sometimes move a file or directory outside of a bind +* mount, path_connected allows those cases to be detected. +*/ +static bool path_connected (const struct path *path) +{ +struct vfsmount *mnt = path->mnt; + +/* Only bind mounts can have disconnected paths */ +if (mnt->mnt_root == mnt->mnt_sb->s_root) +return true; + +return is_subdir (path->dentry, mnt->mnt_root); +} +static int follow_dotdot_rcu (struct nameidata *nd) {struct inode *inode = nd->inode;
@@ -1194,6 +1212,8 @@ static int follow_dotdot_rcu(struct nameidata *nd) goto failed; nd->path.dentry = parent; nd->seq = seq; +if (unlikely (!path_connected (&nd->path))) +return -ENOENT ;break ; }if (!follow_up_rcu (&nd->path))
@@ -1290,7 +1310,7 @@ static void follow_mount(struct path *path) } } -static void follow_dotdot (struct nameidata *nd) +static int follow_dotdot (struct nameidata *nd) {if (!nd->root.mnt)set_root (nd);
@@ -1306,6 +1326,8 @@ static void follow_dotdot(struct nameidata *nd)/* rare case of legitimate dget_parent()... */ nd->path.dentry =dget_parent (nd->path.dentry);dput (old); +if (unlikely (!path_connected (&nd->path))) +return -ENOENT ;break ; }if (!follow_up (&nd->path))
@@ -1313,6 +1335,7 @@ static void follow_dotdot(struct nameidata *nd) }follow_mount (&nd->path); nd->inode = nd->path.dentry->d_inode; +return 0 ; }/*
@@ -1541,7 +1564,7 @@ static inline int handle_dots(struct nameidata *nd, int type)if (follow_dotdot_rcu (nd))return -ECHILD ; }else -follow_dotdot (nd); +return follow_dotdot (nd); }return 0 ; }
Signature · 1 line
2.1.0
Quoted 3 lines · Chas Williams
From: "Eric W. Biederman" <[email protected]>
commit
cde93be45a8a90d8c264c776fab63487b5038a65upstream.
Eric already sent patches for all actively maintained stable trees for these commits last week. Was there something additional that you needed to do or some other reason you sent this and your other patch out?
josh
Quoted 11 lines · Josh Boyer
Quoted 3 lines · Chas Williams
From: "Eric W. Biederman" <[email protected]>
commit
cde93be45a8a90d8c264c776fab63487b5038a65upstream.Eric already sent patches for all actively maintained stable trees for these commits last week. Was there something additional that you needed to do or some other reason you sent this and your other patch out?
josh
Sorry, I didn't see those messages. I don't have anything to add then. Please ignore these patches.