Re: Safety of resolving untrusted paths with detached mount dirfd
From: Aleksa Sarai <hidden>
Date: 2025-11-20 09:25:22
Also in:
linux-fsdevel, lkml
On 2025-11-19, Demi Marie Obenour [off-list ref] wrote:
On 11/19/25 21:18, Aleksa Sarai wrote:quoted
On 2025-11-19, Alyssa Ross [off-list ref] wrote:quoted
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?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.
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?
https://github.com/QubesOS/qubes-secpack/blob/main/QSBs/qsb-014-2015.txt was the chroot breakout.quoted
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 -- Aleksa Sarai https://www.cyphar.com/
Attachments
- signature.asc [application/pgp-signature] 265 bytes