Skip to content

Commit 72ba292

Browse files
cypharAl Viro
authored andcommitted
namei: LOOKUP_NO_XDEV: block mountpoint crossing
/* Background. */ The need to contain path operations within a mountpoint has been a long-standing usecase that userspace has historically implemented manually with liberal usage of stat(). find, rsync, tar and many other programs implement these semantics -- but it'd be much simpler to have a fool-proof way of refusing to open a path if it crosses a mountpoint. This is part of a refresh of Al's AT_NO_JUMPS patchset[1] (which was a variation on David Drysdale's O_BENEATH patchset[2], which in turn was based on the Capsicum project[3]). /* Userspace API. */ LOOKUP_NO_XDEV will be exposed to userspace through openat2(2). /* Semantics. */ Unlike most other LOOKUP flags (most notably LOOKUP_FOLLOW), LOOKUP_NO_XDEV applies to all components of the path. With LOOKUP_NO_XDEV, any path component which crosses a mount-point during path resolution (including "..") will yield an -EXDEV. Absolute paths, absolute symlinks, and magic-links will only yield an -EXDEV if the jump involved changing mount-points. /* Testing. */ LOOKUP_NO_XDEV is tested as part of the openat2(2) selftests. [1]: https://lore.kernel.org/lkml/[email protected]/ [2]: https://lore.kernel.org/lkml/[email protected]/ [3]: https://lore.kernel.org/lkml/[email protected]/ Cc: Christian Brauner <[email protected]> Suggested-by: David Drysdale <[email protected]> Suggested-by: Al Viro <[email protected]> Suggested-by: Andy Lutomirski <[email protected]> Suggested-by: Linus Torvalds <[email protected]> Signed-off-by: Aleksa Sarai <[email protected]> Signed-off-by: Al Viro <[email protected]>
1 parent 4b99d49 commit 72ba292

File tree

2 files changed

+28
-4
lines changed

2 files changed

+28
-4
lines changed

fs/namei.c

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,11 @@ static inline void path_to_nameidata(const struct path *path,
838838

839839
static int nd_jump_root(struct nameidata *nd)
840840
{
841+
if (unlikely(nd->flags & LOOKUP_NO_XDEV)) {
842+
/* Absolute path arguments to path_init() are allowed. */
843+
if (nd->path.mnt != NULL && nd->path.mnt != nd->root.mnt)
844+
return -EXDEV;
845+
}
841846
if (!nd->root.mnt) {
842847
int error = set_root(nd);
843848
if (error)
@@ -873,6 +878,12 @@ int nd_jump_link(struct path *path)
873878
if (unlikely(nd->flags & LOOKUP_NO_MAGICLINKS))
874879
goto err;
875880

881+
error = -EXDEV;
882+
if (unlikely(nd->flags & LOOKUP_NO_XDEV)) {
883+
if (nd->path.mnt != path->mnt)
884+
goto err;
885+
}
886+
876887
path_put(&nd->path);
877888
nd->path = *path;
878889
nd->inode = nd->path.dentry->d_inode;
@@ -1284,10 +1295,14 @@ static int follow_managed(struct path *path, struct nameidata *nd)
12841295
break;
12851296
}
12861297

1287-
if (need_mntput && path->mnt == mnt)
1288-
mntput(path->mnt);
1289-
if (need_mntput)
1290-
nd->flags |= LOOKUP_JUMPED;
1298+
if (need_mntput) {
1299+
if (path->mnt == mnt)
1300+
mntput(path->mnt);
1301+
if (unlikely(nd->flags & LOOKUP_NO_XDEV))
1302+
ret = -EXDEV;
1303+
else
1304+
nd->flags |= LOOKUP_JUMPED;
1305+
}
12911306
if (ret == -EISDIR || !ret)
12921307
ret = 1;
12931308
if (ret > 0 && unlikely(d_flags_negative(flags)))
@@ -1348,6 +1363,8 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path,
13481363
mounted = __lookup_mnt(path->mnt, path->dentry);
13491364
if (!mounted)
13501365
break;
1366+
if (unlikely(nd->flags & LOOKUP_NO_XDEV))
1367+
return false;
13511368
path->mnt = &mounted->mnt;
13521369
path->dentry = mounted->mnt.mnt_root;
13531370
nd->flags |= LOOKUP_JUMPED;
@@ -1394,6 +1411,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
13941411
return -ECHILD;
13951412
if (&mparent->mnt == nd->path.mnt)
13961413
break;
1414+
if (unlikely(nd->flags & LOOKUP_NO_XDEV))
1415+
return -ECHILD;
13971416
/* we know that mountpoint was pinned */
13981417
nd->path.dentry = mountpoint;
13991418
nd->path.mnt = &mparent->mnt;
@@ -1408,6 +1427,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
14081427
return -ECHILD;
14091428
if (!mounted)
14101429
break;
1430+
if (unlikely(nd->flags & LOOKUP_NO_XDEV))
1431+
return -ECHILD;
14111432
nd->path.mnt = &mounted->mnt;
14121433
nd->path.dentry = mounted->mnt.mnt_root;
14131434
inode = nd->path.dentry->d_inode;
@@ -1506,6 +1527,8 @@ static int follow_dotdot(struct nameidata *nd)
15061527
}
15071528
if (!follow_up(&nd->path))
15081529
break;
1530+
if (unlikely(nd->flags & LOOKUP_NO_XDEV))
1531+
return -EXDEV;
15091532
}
15101533
follow_mount(&nd->path);
15111534
nd->inode = nd->path.dentry->d_inode;

include/linux/namei.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND};
4242
/* Scoping flags for lookup. */
4343
#define LOOKUP_NO_SYMLINKS 0x010000 /* No symlink crossing. */
4444
#define LOOKUP_NO_MAGICLINKS 0x020000 /* No nd_jump_link() crossing. */
45+
#define LOOKUP_NO_XDEV 0x040000 /* No mountpoint crossing. */
4546

4647
extern int path_pts(struct path *path);
4748

0 commit comments

Comments
 (0)