diff --git a/README.md b/README.md index f040ed1..c5d6e6b 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,8 @@ Unlike many filesystem APIs, WASI filesystem is capability-oriented. Instead of having functions that implicitly reference a filesystem namespace, WASI filesystems' APIs are passed a directory handle along with a path, and the path is looked up relative to the given handle, and sandboxed to be -resolved within that directory. +resolved within that directory. For more information about sandbox, see +[WASI filesystem path resolution](path-resolution.md). WASI filesystem hides some of the surface differences between Windows and Unix-style filesystems, however much of its behavior, indluding the diff --git a/example-world.md b/example-world.md index 8a7fe8d..7805cf8 100644 --- a/example-world.md +++ b/example-world.md @@ -366,6 +366,8 @@ function starts with /, or if any step of resolving a path.. and symbolic link steps, reaches a directory outside of the base directory, or reaches a symlink to an absolute or rooted path in the underlying filesystem, the function fails with error-code::not-permitted.

+

For more information about WASI path resolution and sandboxing, see +WASI filesystem path resolution.


Types

type input-stream

diff --git a/path-resolution.md b/path-resolution.md new file mode 100644 index 0000000..45f20b1 --- /dev/null +++ b/path-resolution.md @@ -0,0 +1,91 @@ +# WASI filesystem path resolution + +wasi-filesystem uses a filesystem path sandboxing scheme modeled after the +system used in [CloudABI], which is also similar to the system used in +[Capsicum]. + +On Linux, it corresponds to the `RESOLVE_BENEATH` behavior in +[Linux's `openat2`]. In FreeBSD, it corresponds to the `O_RESOLVE_BENEATH` +behavior in [FreeBSD's `open`]. However, path resolution can also be +implemented manually using `openat` and `readlinkat` or similar primitives. + +## Sandboxing overview + +All functions in wasi-filesystem which operate on filesystem paths take +a pair of values: a base directory handle, and a relative path. Absolute +paths are not permitted, and there is no global namespace. All path +accesses are relative to a base directory handle. + +Path resolution is constrained to occur within the sub-filesystem referenced +by the base handle. Information about the filesystem outside of the base +directory handles is not visible. In particular, it's not permitted to use +paths that temporarily step outside the sandbox with something like +"../../../stuff/here", even if the final resolved path is back inside the +sandbox, because that would leak information about the existence of +directories outside the sandbox. + +Importantly, the sandboxing is designed to be implementable even in the presence +of outside processes accessing the same filesystem, including renaming, +unlinking, and creating new files and directories. + +## Symlinks + +Creating a symlink with an absolute path string fails with a "not permitted" +error. + +Other than that, symlinks may be created with any string, provided the +underlying filesystem implementation supports it. + +Sandboxing for symlink strings is performed at the time of an access, when a +path is being resolved, and not at the time that the symlink is created or +moved. This ensures that the sandbox is respected even if there are symlinks +created or renamed by other entities with access to the filesystem. + +## Host Implementation + +### Implementing path resolution manually + +Plain `openat` doesn't perform any sandboxing; it will readily open paths +containing ".." or starting with "/", or symlinks to paths containing ".." +or starting with "/". It has an `O_NOFOLLOW` flag, however this flag only +applies to the last component of the path (eg. the "c" in "a/b/c"). So +the strategy for using `openat` to implement sandboxing is to split paths +into components (eg. "a", "b", "c") and open them one component at a time, +so that each component can be opened with `O_NOFOLLOW`. + +If the `openat` call fails, and the OS error code indicates that it *was* +a symlink (eg. `ELOOP`), then call `readlinkat` to read the link contents, +split the contents into components, and prepend these new components to the +component list. If it starts with an absolute path, that's an attempt to +jump outside the sandbox, so path resolution should fail with an +"access denied" error message. + +If a path component is "..", instead of opening it, pop an item off of the +component list. If the list was empty, that represents an attempt to use +".." to step outside the sandbox, so path resolution should fail with an +"access denied" error message. + +### Implementation notes + +On Linux, `openat2` with `RESOLVE_BENEATH` may be used as an optimization to +implement many system calls other than just "open" by utilizing Linux's +`O_PATH` and "/proc/self/fd" features. + +On Windows, the [`NtCreateFile`] function can accept a directory handle and +can behave like an `openat` function, which can be used in the +[manual algorithm](implementing-path-resolution-manually). + +The Rust library [cap-std] implements WASI's filesystem sandboxing semantics, +but is otherwise independent of WASI or Wasm, so it can be reused in other +settings. It uses `openat2` and `NtCreateFile` and other optimizations. + +cloudabi-utils has an [implementation of the manual technique in C], though +that repository is no longer maintained. + +[implementation of the manual technique in C]: https://github.com/NuxiNL/cloudabi-utils/blob/master/src/libemulator/posix.c#L1205 +[cap-std]: https://github.com/bytecodealliance/cap-std +[Linux's `openat2`]: https://man7.org/linux/man-pages/man2/openat2.2.html +[CloudABI]: https://github.com/NuxiNL/cloudabi +[Capsicum]: https://wiki.freebsd.org/Capsicum +[FreeBSD's `open`]: https://man.freebsd.org/cgi/man.cgi?sektion=2&query=open +[`NtCreateFile`]: https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntcreatefile diff --git a/wit/types.wit b/wit/types.wit index e72a742..9b1412d 100644 --- a/wit/types.wit +++ b/wit/types.wit @@ -17,6 +17,11 @@ /// `..` and symbolic link steps, reaches a directory outside of the base /// directory, or reaches a symlink to an absolute or rooted path in the /// underlying filesystem, the function fails with `error-code::not-permitted`. +/// +/// For more information about WASI path resolution and sandboxing, see +/// [WASI filesystem path resolution]. +/// +/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md interface types { use wasi:io/streams.{input-stream, output-stream} use wasi:clocks/wall-clock.{datetime}