| From 397d425dc26da728396e66d392d5dcb8dac30c37 Mon Sep 17 00:00:00 2001 |
| From: "Eric W. Biederman" <ebiederm@xmission.com> |
| Date: Sat, 15 Aug 2015 20:27:13 -0500 |
| Subject: vfs: Test for and handle paths that are unreachable from their mnt_root |
| |
| From: "Eric W. Biederman" <ebiederm@xmission.com> |
| |
| 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. |
| |
| Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com> |
| Signed-off-by: Al Viro <viro@zeniv.linux.org.uk> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| |
| --- |
| fs/namei.c | 31 ++++++++++++++++++++++++++++--- |
| 1 file changed, 28 insertions(+), 3 deletions(-) |
| |
| --- a/fs/namei.c |
| +++ b/fs/namei.c |
| @@ -484,6 +484,24 @@ void path_put(const struct path *path) |
| } |
| EXPORT_SYMBOL(path_put); |
| |
| +/** |
| + * 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); |
| +} |
| + |
| /* |
| * Path walking has 2 modes, rcu-walk and ref-walk (see |
| * Documentation/filesystems/path-lookup.txt). In situations when we can't |
| @@ -1149,6 +1167,8 @@ static int follow_dotdot_rcu(struct name |
| goto failed; |
| nd->path.dentry = parent; |
| nd->seq = seq; |
| + if (unlikely(!path_connected(&nd->path))) |
| + goto failed; |
| break; |
| } |
| if (!follow_up_rcu(&nd->path)) |
| @@ -1242,7 +1262,7 @@ static void follow_mount(struct path *pa |
| } |
| } |
| |
| -static void follow_dotdot(struct nameidata *nd) |
| +static int follow_dotdot(struct nameidata *nd) |
| { |
| if (!nd->root.mnt) |
| set_root(nd); |
| @@ -1258,6 +1278,10 @@ static void follow_dotdot(struct nameida |
| /* rare case of legitimate dget_parent()... */ |
| nd->path.dentry = dget_parent(nd->path.dentry); |
| dput(old); |
| + if (unlikely(!path_connected(&nd->path))) { |
| + path_put(&nd->path); |
| + return -ENOENT; |
| + } |
| break; |
| } |
| if (!follow_up(&nd->path)) |
| @@ -1265,6 +1289,7 @@ static void follow_dotdot(struct nameida |
| } |
| follow_mount(&nd->path); |
| nd->inode = nd->path.dentry->d_inode; |
| + return 0; |
| } |
| |
| /* |
| @@ -1488,7 +1513,7 @@ static inline int handle_dots(struct nam |
| if (follow_dotdot_rcu(nd)) |
| return -ECHILD; |
| } else |
| - follow_dotdot(nd); |
| + return follow_dotdot(nd); |
| } |
| return 0; |
| } |
| @@ -2214,7 +2239,7 @@ mountpoint_last(struct nameidata *nd, st |
| if (unlikely(nd->last_type != LAST_NORM)) { |
| error = handle_dots(nd, nd->last_type); |
| if (error) |
| - goto out; |
| + return error; |
| dentry = dget(nd->path.dentry); |
| goto done; |
| } |