Skip to contentSkip to conversation

Safety of resolving untrusted paths with detached mount dirfd

DISCUSSION Alyssa Ross 4 participants 5 messages Nov 19, 2025 – Nov 20, 2025

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

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]/

#1 lore raw
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_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.

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

#2 lore raw
In reply to Alyssa Ross
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_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.

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/
#3 lore raw
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_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.

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_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.

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_BENEATH or RESOLVE_IN_ROOT in combination with OPEN_TREE_CLONE.

Unfortunately not everything supports that. For instance, mkdirat() doesn't.

Signature · 2 lines
Sincerely,
Demi Marie Obenour (she/her/hers)
#4 lore raw
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_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.

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_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.

Quoted 4 lines

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.

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_BENEATH or RESOLVE_IN_ROOT in combination with OPEN_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/