summaryrefslogtreecommitdiffstats
path: root/fs/namei.c
diff options
context:
space:
mode:
authorEric W. Biederman <ebiederm@xmission.com>2015-08-15 21:27:13 -0400
committerAl Viro <viro@zeniv.linux.org.uk>2015-08-21 03:20:10 -0400
commit397d425dc26da728396e66d392d5dcb8dac30c37 (patch)
tree14a2ec6d000fa7b5b45d91bb62d2f89fc11d28b5 /fs/namei.c
parenta03e283bf5c3d4851b4998122196ce9f849e6dfb (diff)
vfs: Test for and handle paths that are unreachable from their mnt_root
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>
Diffstat (limited to 'fs/namei.c')
-rw-r--r--fs/namei.c27
1 files changed, 25 insertions, 2 deletions
diff --git a/fs/namei.c b/fs/namei.c
index 1c2105ed20c5..29b927938b8c 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -560,6 +560,24 @@ static int __nd_alloc_stack(struct nameidata *nd)
560 return 0; 560 return 0;
561} 561}
562 562
563/**
564 * path_connected - Verify that a path->dentry is below path->mnt.mnt_root
565 * @path: nameidate to verify
566 *
567 * Rename can sometimes move a file or directory outside of a bind
568 * mount, path_connected allows those cases to be detected.
569 */
570static bool path_connected(const struct path *path)
571{
572 struct vfsmount *mnt = path->mnt;
573
574 /* Only bind mounts can have disconnected paths */
575 if (mnt->mnt_root == mnt->mnt_sb->s_root)
576 return true;
577
578 return is_subdir(path->dentry, mnt->mnt_root);
579}
580
563static inline int nd_alloc_stack(struct nameidata *nd) 581static inline int nd_alloc_stack(struct nameidata *nd)
564{ 582{
565 if (likely(nd->depth != EMBEDDED_LEVELS)) 583 if (likely(nd->depth != EMBEDDED_LEVELS))
@@ -1296,6 +1314,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
1296 return -ECHILD; 1314 return -ECHILD;
1297 nd->path.dentry = parent; 1315 nd->path.dentry = parent;
1298 nd->seq = seq; 1316 nd->seq = seq;
1317 if (unlikely(!path_connected(&nd->path)))
1318 return -ENOENT;
1299 break; 1319 break;
1300 } else { 1320 } else {
1301 struct mount *mnt = real_mount(nd->path.mnt); 1321 struct mount *mnt = real_mount(nd->path.mnt);
@@ -1396,7 +1416,7 @@ static void follow_mount(struct path *path)
1396 } 1416 }
1397} 1417}
1398 1418
1399static void follow_dotdot(struct nameidata *nd) 1419static int follow_dotdot(struct nameidata *nd)
1400{ 1420{
1401 if (!nd->root.mnt) 1421 if (!nd->root.mnt)
1402 set_root(nd); 1422 set_root(nd);
@@ -1412,6 +1432,8 @@ static void follow_dotdot(struct nameidata *nd)
1412 /* rare case of legitimate dget_parent()... */ 1432 /* rare case of legitimate dget_parent()... */
1413 nd->path.dentry = dget_parent(nd->path.dentry); 1433 nd->path.dentry = dget_parent(nd->path.dentry);
1414 dput(old); 1434 dput(old);
1435 if (unlikely(!path_connected(&nd->path)))
1436 return -ENOENT;
1415 break; 1437 break;
1416 } 1438 }
1417 if (!follow_up(&nd->path)) 1439 if (!follow_up(&nd->path))
@@ -1419,6 +1441,7 @@ static void follow_dotdot(struct nameidata *nd)
1419 } 1441 }
1420 follow_mount(&nd->path); 1442 follow_mount(&nd->path);
1421 nd->inode = nd->path.dentry->d_inode; 1443 nd->inode = nd->path.dentry->d_inode;
1444 return 0;
1422} 1445}
1423 1446
1424/* 1447/*
@@ -1634,7 +1657,7 @@ static inline int handle_dots(struct nameidata *nd, int type)
1634 if (nd->flags & LOOKUP_RCU) { 1657 if (nd->flags & LOOKUP_RCU) {
1635 return follow_dotdot_rcu(nd); 1658 return follow_dotdot_rcu(nd);
1636 } else 1659 } else
1637 follow_dotdot(nd); 1660 return follow_dotdot(nd);
1638 } 1661 }
1639 return 0; 1662 return 0;
1640} 1663}