Skip to content

Commit

Permalink
vfs: Test for and handle paths that are unreachable from their mnt_root
Browse files Browse the repository at this point in the history
[ Upstream commit 397d425 ]

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: Sasha Levin <sasha.levin@oracle.com>
  • Loading branch information
Eric W. Biederman authored and Sasha Levin committed Oct 28, 2015
1 parent 3e6fba2 commit 7f61fd9
Showing 1 changed file with 28 additions and 3 deletions.
31 changes: 28 additions & 3 deletions fs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,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
Expand Down Expand Up @@ -1164,6 +1182,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
goto failed;
nd->path.dentry = parent;
nd->seq = seq;
if (unlikely(!path_connected(&nd->path)))
goto failed;
break;
}
if (!follow_up_rcu(&nd->path))
Expand Down Expand Up @@ -1260,7 +1280,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);
Expand All @@ -1276,13 +1296,18 @@ 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))) {
path_put(&nd->path);
return -ENOENT;
}
break;
}
if (!follow_up(&nd->path))
break;
}
follow_mount(&nd->path);
nd->inode = nd->path.dentry->d_inode;
return 0;
}

/*
Expand Down Expand Up @@ -1503,7 +1528,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;
}
Expand Down Expand Up @@ -2239,7 +2264,7 @@ mountpoint_last(struct nameidata *nd, struct path *path)
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;
}
Expand Down

0 comments on commit 7f61fd9

Please sign in to comment.