Skip to content

Commit eed13ce

Browse files
ebiedermgregkh
authored andcommitted
vfs: Test for and handle paths that are unreachable from their mnt_root
commit 397d425 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>
1 parent 6f4e45e commit eed13ce

File tree

1 file changed

+28
-3
lines changed

1 file changed

+28
-3
lines changed

fs/namei.c

+28-3
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,24 @@ struct nameidata {
505505
char *saved_names[MAX_NESTED_LINKS + 1];
506506
};
507507

508+
/**
509+
* path_connected - Verify that a path->dentry is below path->mnt.mnt_root
510+
* @path: nameidate to verify
511+
*
512+
* Rename can sometimes move a file or directory outside of a bind
513+
* mount, path_connected allows those cases to be detected.
514+
*/
515+
static bool path_connected(const struct path *path)
516+
{
517+
struct vfsmount *mnt = path->mnt;
518+
519+
/* Only bind mounts can have disconnected paths */
520+
if (mnt->mnt_root == mnt->mnt_sb->s_root)
521+
return true;
522+
523+
return is_subdir(path->dentry, mnt->mnt_root);
524+
}
525+
508526
/*
509527
* Path walking has 2 modes, rcu-walk and ref-walk (see
510528
* Documentation/filesystems/path-lookup.txt). In situations when we can't
@@ -1194,6 +1212,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
11941212
goto failed;
11951213
nd->path.dentry = parent;
11961214
nd->seq = seq;
1215+
if (unlikely(!path_connected(&nd->path)))
1216+
goto failed;
11971217
break;
11981218
}
11991219
if (!follow_up_rcu(&nd->path))
@@ -1290,7 +1310,7 @@ static void follow_mount(struct path *path)
12901310
}
12911311
}
12921312

1293-
static void follow_dotdot(struct nameidata *nd)
1313+
static int follow_dotdot(struct nameidata *nd)
12941314
{
12951315
if (!nd->root.mnt)
12961316
set_root(nd);
@@ -1306,13 +1326,18 @@ static void follow_dotdot(struct nameidata *nd)
13061326
/* rare case of legitimate dget_parent()... */
13071327
nd->path.dentry = dget_parent(nd->path.dentry);
13081328
dput(old);
1329+
if (unlikely(!path_connected(&nd->path))) {
1330+
path_put(&nd->path);
1331+
return -ENOENT;
1332+
}
13091333
break;
13101334
}
13111335
if (!follow_up(&nd->path))
13121336
break;
13131337
}
13141338
follow_mount(&nd->path);
13151339
nd->inode = nd->path.dentry->d_inode;
1340+
return 0;
13161341
}
13171342

13181343
/*
@@ -1541,7 +1566,7 @@ static inline int handle_dots(struct nameidata *nd, int type)
15411566
if (follow_dotdot_rcu(nd))
15421567
return -ECHILD;
15431568
} else
1544-
follow_dotdot(nd);
1569+
return follow_dotdot(nd);
15451570
}
15461571
return 0;
15471572
}
@@ -2290,7 +2315,7 @@ mountpoint_last(struct nameidata *nd, struct path *path)
22902315
if (unlikely(nd->last_type != LAST_NORM)) {
22912316
error = handle_dots(nd, nd->last_type);
22922317
if (error)
2293-
goto out;
2318+
return error;
22942319
dentry = dget(nd->path.dentry);
22952320
goto done;
22962321
}

0 commit comments

Comments
 (0)