Thread (5 messages) flat view 5 messages, 4 authors, 2025-11-20

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

Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help