The behaviour of `O_BENEATH` in code like this: ```c int dirfd = open("/foo", O_RDONLY); int bar = openat(dirfd, "/foo/bar", O_RDONLY|O_BENEATH); ``` on FreeBSD-CURRENT, if I read the man page [0] correctly, is to attempt to open the absolute path "/foo/bar", since "/foo" is a prefix that "ends up at the topping directory". `openat` with `O_BENEATH` also resolves ".." paths which temporarily escape the topping directory if the final path is within the topping directory. These means that information about the path above the topping directory leaks through the result of the `openat`. This is undesirable for sandboxing applications which wish to avoid leaking information about the host filesystem outside the sandbox. One way to avoid this is to enable Capsicum capability mode, however since that affects process-wide state, it isn't suitable for lightweight sandboxing use cases which just want to sandbox paths in selected `openat` calls. And, it appears to have the side effect of disallowing ".." entirely. Another way to avoid this is for these use cases to check for absolute paths and ".." manually, however this wouldn't protect against symlinks to absolute paths or symlinks containing "..", which can't always be prevented. It may also have the side effect of disallowing ".." entirely--there are techniques for emulating sandboxed ".." resolution [4], however they incur significant overhead. FreeBSD's `O_BENEATH` was modified to have its current behaviour in D17714 [3]. It's unclear from the discussion what the motivating use cases are. For the use case of sandboxing untrusted paths, it would be desirable to have a way to avoid leaking information about the host filesystem above the topping directory. FreeBSD's current `O_BENEATH` also appears to differ from Linux's new `openat2`'s `RESOLVE_BENEATH` flag, or any of its other flags [1]. Linux's `openat2` with `RESOLVE_BENEATH` fails with `EACCES` on any absolute path, or any path that escapes even temporarily in an intermediate component. As an additional data point, this is the behaviour of CloudABI's `libemulator` as well [2]. I'm not very familiar with FreeBSD, so corrections if I've misunderstood or missed anything are most welcome! [0] https://svnweb.freebsd.org/base/head/lib/libc/sys/open.2?revision=340347&view=markup&pathrev=340347 [1] https://man7.org/linux/man-pages/man2/openat2.2.html [2] https://github.com/NuxiNL/cloudabi-utils/blob/master/src/libemulator/posix.c#L1271 [3] https://reviews.freebsd.org/D17714 [4] https://github.com/NuxiNL/cloudabi-utils/blob/master/src/libemulator/posix.c#L1205
I am not convinced that the discovered oracle is useful for anything. It can only answer the question 'is given dirfd has such upper full path'. So what ? But even that, I do not see how it prevents code from opening and listing arbitrary directories by not using O_BENEATH, since you are not in the cap mode. I would prefer to see some (potential) real-world application of O_BENEATH that is damaged by the behavior, before making any decision.
As an example, paths may contain usernames. Usernames may not be sensitive in a username/password sense, but they are in a personally-identifying-information sense. In my application, we have untrusted WebAssembly bytecode running inside an interpreter. The bytecode may make calls to an API called WASI, which includes POSIX-like filesystem access. We have an implementation of WASI which implements sandboxing that works on FreeBSD, however it's slow (about one syscall per path component). A feature like `O_BENEATH` but which instead blocks access to paths outside the topping directory could run much faster.
But user could only guess-check only his own username, no ? O_BENEATH usage was designed to confine existing non-capsicumized apps, which only need access to the known subset of the whole filesystem namespace. Typical example is compiler which only needs to access source file, hierarchies of headers, and write output file. There, we can pre-allocate dirfds for /usr/include and /usr/local/include. On the other hand, build systems often use relative paths with dotdots to express target directory as relative to source, so dotdot support was needed for intended application of our O_BENEATH. Anyway, if you can provide somewhat more precise explanation of the desired behavior, and perhaps give the name for the new O_ flag, I will implement it as well.
Combined with O_BENEATH allowing `..` to temporarily leave the topping directory as long as it ends back within the topping directory, one could probe for any username under /home. The behaviour which would be best for the present WASI use case would be the behaviour of Linux's RESOLVE_BENEATH: Do not permit the path resolution to succeed if any component of the resolution is not a descendant of [the topping directory]. This causes absolute symbolic links (and absolute values of pathname) to be rejected.
(In reply to Dan Gohman from comment #4) So it would be like capability mode with dotdot enabled ? I prototyped O_RBENEATH/AT_RBENEATH flags in the following diff https://reviews.freebsd.org/D25886 It will take some time to test, get reviews and write documentation.
A commit references this bug: Author: kib Date: Tue Sep 22 22:48:13 UTC 2020 New revision: 366022 URL: https://svnweb.freebsd.org/changeset/base/366022 Log: Add O_RESOLVE_BENEATH and AT_RESOLVE_BENEATH to mimic Linux' RESOLVE_BENEATH. It is like O_BENEATH, but disables to walk out of the subtree rooted in the starting directory. O_BENEATH does not care if path walks out if it returned. Requested by: Dan Gohman <dev@sunfishcode.online> PR: 248335 Reviewed by: markj Tested by: pho Sponsored by: The FreeBSD Foundation MFC after: 1 week Differential revision: https://reviews.freebsd.org/D25886 Changes: head/sys/kern/vfs_lookup.c head/sys/kern/vfs_syscalls.c head/sys/kern/vfs_vnops.c head/sys/sys/fcntl.h head/sys/sys/namei.h
A commit references this bug: Author: kib Date: Tue Sep 22 22:54:56 UTC 2020 New revision: 366023 URL: https://svnweb.freebsd.org/changeset/base/366023 Log: Document {O,AT}_RESOLVE_BENEATH and new O_BENEATH behavior for relative paths. PR: 248335 Reviewed by: markj Tested by: pho Sponsored by: The FreeBSD Foundation MFC after: 1 week Differential revision: https://reviews.freebsd.org/D25886 Changes: head/lib/libc/sys/access.2 head/lib/libc/sys/chflags.2 head/lib/libc/sys/chmod.2 head/lib/libc/sys/chown.2 head/lib/libc/sys/fhlink.2 head/lib/libc/sys/getfh.2 head/lib/libc/sys/link.2 head/lib/libc/sys/open.2 head/lib/libc/sys/stat.2 head/lib/libc/sys/unlink.2 head/lib/libc/sys/utimensat.2
I suspect this ticket can now be closed with the addition of *_RESOLVE_BENEATH? (Perhaps after also confirming that the man page adequately describes the situation.)