Bug 248335 - O_BENEATH leaks information about parent directories
Summary: O_BENEATH leaks information about parent directories
Status: New
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: CURRENT
Hardware: Any Any
: --- Affects Only Me
Assignee: freebsd-bugs (Nobody)
Depends on:
Reported: 2020-07-28 16:50 UTC by Dan Gohman
Modified: 2020-09-22 22:55 UTC (History)
4 users (show)

See Also:


Note You need to log in before you can comment on or make changes to this bug.
Description Dan Gohman 2020-07-28 16:50:17 UTC
The behaviour of `O_BENEATH` in code like this:

  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
Comment 1 Konstantin Belousov freebsd_committer 2020-07-29 19:13:15 UTC
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.
Comment 2 Dan Gohman 2020-07-29 20:14:07 UTC
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.
Comment 3 Konstantin Belousov freebsd_committer 2020-07-29 21:20:25 UTC
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.
Comment 4 Dan Gohman 2020-07-29 21:45:22 UTC
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
Comment 5 Konstantin Belousov freebsd_committer 2020-07-29 23:32:22 UTC
(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
It will take some time to test, get reviews and write documentation.
Comment 6 commit-hook freebsd_committer 2020-09-22 22:48:41 UTC
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


  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

Comment 7 commit-hook freebsd_committer 2020-09-22 22:55:45 UTC
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

  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