Skip to content

Commit 397d425

Browse files
ebiedermAl Viro
authored and
Al Viro
committed
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>
1 parent a03e283 commit 397d425

File tree

1 file changed

+25
-2
lines changed

1 file changed

+25
-2
lines changed

Diff for: fs/namei.c

+25-2
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,24 @@ static int __nd_alloc_stack(struct nameidata *nd)
560560
return 0;
561561
}
562562

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+
*/
570+
static 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+
563581
static inline int nd_alloc_stack(struct nameidata *nd)
564582
{
565583
if (likely(nd->depth != EMBEDDED_LEVELS))
@@ -1296,6 +1314,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
12961314
return -ECHILD;
12971315
nd->path.dentry = parent;
12981316
nd->seq = seq;
1317+
if (unlikely(!path_connected(&nd->path)))
1318+
return -ENOENT;
12991319
break;
13001320
} else {
13011321
struct mount *mnt = real_mount(nd->path.mnt);
@@ -1396,7 +1416,7 @@ static void follow_mount(struct path *path)
13961416
}
13971417
}
13981418

1399-
static void follow_dotdot(struct nameidata *nd)
1419+
static int follow_dotdot(struct nameidata *nd)
14001420
{
14011421
if (!nd->root.mnt)
14021422
set_root(nd);
@@ -1412,13 +1432,16 @@ static void follow_dotdot(struct nameidata *nd)
14121432
/* rare case of legitimate dget_parent()... */
14131433
nd->path.dentry = dget_parent(nd->path.dentry);
14141434
dput(old);
1435+
if (unlikely(!path_connected(&nd->path)))
1436+
return -ENOENT;
14151437
break;
14161438
}
14171439
if (!follow_up(&nd->path))
14181440
break;
14191441
}
14201442
follow_mount(&nd->path);
14211443
nd->inode = nd->path.dentry->d_inode;
1444+
return 0;
14221445
}
14231446

14241447
/*
@@ -1634,7 +1657,7 @@ static inline int handle_dots(struct nameidata *nd, int type)
16341657
if (nd->flags & LOOKUP_RCU) {
16351658
return follow_dotdot_rcu(nd);
16361659
} else
1637-
follow_dotdot(nd);
1660+
return follow_dotdot(nd);
16381661
}
16391662
return 0;
16401663
}

0 commit comments

Comments
 (0)