Safety of resolving untrusted paths with detached mount dirfd
Hello,
As we know, it's not safe to use chroot() for resolving untrusted paths within some root, as a subdirectory could be moved outside of the process root while walking the path[1]. On the other hand, LOOKUP_BENEATH is supposed to be robust against this, and going by [2], it sounds like resolving with the mount namespace root as dirfd should also be.
My question is: would resolving an untrusted path against a detached mount root dirfd opened with OPEN_TREE_CLONE (not necessarily a filesystem root) also be expected to be robust against traversal issues? i.e. can I rely on an untrusted path never resolving to a path that isn't under the mount root?
[1]: https://lore.kernel.org/lkml/CAG48ez30WJhbsro2HOc_DR7V91M+hNFzBP5ogRMZaxbAORvqzg@mail.gmail.com/
[2]: https://lore.kernel.org/lkml/[email protected]/
Quoted 9 lines · On Wed, 19 Nov 2025 14:46:35 +0100 Alyssa Ross <[email protected]> wrote:
Hello,
As we know, it's not safe to use
chroot()for resolving untrusted paths within some root, as a subdirectory could be moved outside of the process root while walking the path[1]. On the other hand,LOOKUP_BENEATHis supposed to be robust against this, and going by [2], it sounds like resolving with the mount namespace root as dirfd should also be.
Quoted 8 lines
My question is: would resolving an untrusted path against a detached mount root dirfd opened with
OPEN_TREE_CLONE(not necessarily a filesystem root) also be expected to be robust against traversal issues? i.e. can I rely on an untrusted path never resolving to a path that isn't under the mount root?[1]: https://lore.kernel.org/lkml/CAG48ez30WJhbsro2HOc_DR7V91M+hNFzBP5ogRMZaxbAORvqzg@mail.gmail.com/
[2]: https://lore.kernel.org/lkml/[email protected]/
May not be directly relevant, but I found 'pwd' giving the wrong answer when done inside a chroot (that isn't a filesytem mount point) after 'faffing' [1] with network namespaces.
The basic problem was that two kernel 'inode' structures end up referencing the base of the chroot - so the pointer equality test fails.
So you could find the path of the chroot without any help from outside.
[1] Brain thinks it might have been an 'unshare' to leave a network namespace that cause the problem.
David
Quoted 9 lines · Alyssa Ross
Hello,
As we know, it's not safe to use
chroot()for resolving untrusted paths within some root, as a subdirectory could be moved outside of the process root while walking the path[1]. On the other hand,LOOKUP_BENEATHis supposed to be robust against this, and going by [2], it sounds like resolving with the mount namespace root as dirfd should also be.
Quoted 5 lines
My question is: would resolving an untrusted path against a detached mount root dirfd opened with
OPEN_TREE_CLONE(not necessarily a filesystem root) also be expected to be robust against traversal issues? i.e. can I rely on an untrusted path never resolving to a path that isn't under the mount root?
No, if you hit an absolute symlink or use an absolute path it will resolve to your current->fs->root (mount namespace root or chroot). However, OPEN_TREE_CLONE will stop ".." from naively stepping out of the detached bind-mount. If you are dealing with procfs then magic-links can also jump out.
You can always use RESOLVE_BENEATH or RESOLVE_IN_ROOT in combination with OPEN_TREE_CLONE.
Signature · 2 lines
Aleksa Sarai https://www.cyphar.com/
Quoted 16 lines · Aleksa Sarai
Quoted 9 lines · Alyssa Ross
Hello,
As we know, it's not safe to use
chroot()for resolving untrusted paths within some root, as a subdirectory could be moved outside of the process root while walking the path[1]. On the other hand,LOOKUP_BENEATHis supposed to be robust against this, and going by [2], it sounds like resolving with the mount namespace root as dirfd should also be.Quoted 5 lines
My question is: would resolving an untrusted path against a detached mount root dirfd opened with
OPEN_TREE_CLONE(not necessarily a filesystem root) also be expected to be robust against traversal issues? i.e. can I rely on an untrusted path never resolving to a path that isn't under the mount root?
Quoted 5 lines
No, if you hit an absolute symlink or use an absolute path it will resolve to your
current->fs->root(mount namespace root or chroot). However,OPEN_TREE_CLONEwill stop ".." from naively stepping out of the detached bind-mount. If you are dealing with procfs then magic-links can also jump out.
Is using open_tree_attr() with MOUNT_ATTR_NOSYMFOLLOW enough to prevent these? Will it still provide protection even if someone concurrently renames one of the files out from under the root? I know that can escape a chroot, but I wonder if this provides more guarantees.
https://github.com/QubesOS/qubes-secpack/blob/main/QSBs/qsb-014-2015.txt
was the chroot breakout.
Quoted 2 lines
You can always use
RESOLVE_BENEATHorRESOLVE_IN_ROOTin combination withOPEN_TREE_CLONE.
Unfortunately not everything supports that. For instance, mkdirat() doesn't.
Signature · 2 lines
Sincerely, Demi Marie Obenour (she/her/hers)
Quoted 23 lines · Demi Marie Obenour
Quoted 16 lines · Aleksa Sarai
Quoted 9 lines · Alyssa Ross
Hello,
As we know, it's not safe to use
chroot()for resolving untrusted paths within some root, as a subdirectory could be moved outside of the process root while walking the path[1]. On the other hand,LOOKUP_BENEATHis supposed to be robust against this, and going by [2], it sounds like resolving with the mount namespace root as dirfd should also be.Quoted 5 lines
My question is: would resolving an untrusted path against a detached mount root dirfd opened with
OPEN_TREE_CLONE(not necessarily a filesystem root) also be expected to be robust against traversal issues? i.e. can I rely on an untrusted path never resolving to a path that isn't under the mount root?Quoted 5 lines
No, if you hit an absolute symlink or use an absolute path it will resolve to your
current->fs->root(mount namespace root or chroot). However,OPEN_TREE_CLONEwill stop ".." from naively stepping out of the detached bind-mount. If you are dealing with procfs then magic-links can also jump out.
Quoted 4 lines
Is using
open_tree_attr()withMOUNT_ATTR_NOSYMFOLLOWenough to prevent these? Will it still provide protection even if someone concurrently renames one of the files out from under the root? I know that can escape a chroot, but I wonder if this provides more guarantees.
That will block symlinks (in a similar manner to RESOLVE_NO_SYMLINKS), so those particular problems would not be an issue. Of course, a lot of symlink usages are valid and so this will block those as well (back when I wrote openat2 I did a cursory scan and something like 15% of system paths contained symlinks on my system).
I think that ".." will not be a problem even with renames because the detached mount is associated with the directory (just like how moving a bind-mount source doesn't suddenly expose more information).
It also goes without saying that you need to make sure an absolute path *never* gets passed to any of the helper functions you write to do this -- in my view this is usually going to be quite a fragile setup. Who is providing the paths to your program?
Quoted 8 lines
https://github.com/QubesOS/qubes-secpack/blob/main/QSBs/qsb-014-2015.txt
was the chroot breakout.Quoted 2 lines
You can always use
RESOLVE_BENEATHorRESOLVE_IN_ROOTin combination withOPEN_TREE_CLONE.Unfortunately not everything supports that. For instance,
mkdirat()doesn't.
You can openat2(RESOLVE_BENEATH) the parent directory and then mkdirat() the final component (because mkdirat does not follow trailing symlinks). This is what libpathrs[1] does, and it works for most *at() syscalls (those that support AT_EMPTY_PATH are even easier).
[1]: https://github.com/cyphar/libpathrs
Signature · 2 lines
Aleksa Sarai https://www.cyphar.com/