Skip to content

bytecodealliance/WASI-Virt

Repository files navigation

WASI Virt

Virtualization Component Generator for WASI Preview 2

A Bytecode Alliance project

build status

The virtualized component can be composed into a WASI Preview2 component with wasm-tools compose, providing fully-configurable WASI virtualization with host pass through or full encapsulation as needed.

Supports all of the current WASI subsystems:

  • Clocks: Allow / Deny
  • Environment: Set environment variables, configure host environment variable permissions
  • Config: Set configuration, configure host property permissions
  • Exit: Allow / Deny
  • Filesystem: Mount a read-only filesystem, configure host filesystem preopen remappings or pass-through.
  • HTTP: Allow / Deny
  • Random: Allow / Deny
  • Sockets: Allow / Deny
  • Stdio: Allow / Deny / Ignore

While current virtualization support is limited, the goal for this project is to support a wide range of WASI virtualization configuration use cases.

Have an unhandled use case? Post a virtualization suggestion.

Explainer

When wanting to run WebAssembly Components depending on WASI APIs in other environments it can provide a point of friction having to port WASI interop to every target platform.

In addition having full unrestricted access to core operating system APIs is a security concern.

WASI Virt allows taking a component that depends on WASI APIs and using a virtualized adapter to convert it into a component that no longer depends on those WASI APIs, or conditionally only depends on them in a configurable way.

For example, consider converting an application to a WebAssembly Component that assumes it can load read some files from the filesystem, but never needs to write.

Using WASI Virt, those specific file paths can be mounted and virtualized into the component itself as a post-compile operation, while banning the final component from being able to access the host's filesystem at all. The inner program still imports a wasi filesystem, but the filesystem implementation is provided by another component, rather than in the host environment. The composition of these two components no longer has a filesystem import, so it can be run in hosts (or other components) which do not provide a filesystem API.

Basic Usage

cargo install --git https://github.com/bytecodealliance/wasi-virt
# Encapsulating a component
wasi-virt component.wasm -o virt.wasm

By default the virtualization will deny all subsystems, and will panic on any attempt to use any subsystem.

In all of the examples, the component.wasm is the component being virtualized and virt.wasm the output component with its virtualization. The component.wasm argument is optional. If omitted, then the virtualized adapter is output into virt.wasm, which can be composed into any arbitrary component with:

wasm-tools compose component.wasm -d virt.wasm -o component.virt.wasm

Configuring a subsystem virtualization will enable it, or subsystems can be fully enabled via --allow-fs, --allow-env etc by subsystem. Allowing all subsystems can be achieved with --allow-all.

Clocks

# Create a component which just allows clocks, but no other interfaces
wasi-virt component.wasm --allow-clocks -o virt.wasm

Env

# Setting specific env vars (while disallowing all host env var access):
wasi-virt component.wasm -e CUSTOM=VAR -o virt.wasm

# Setting env vars with all host env vars allowed:
wasi-virt component.wasm -e CUSTOM=VAR --allow-env -o virt.wasm

# Setting env vars with restricted host env var access:
wasi-virt component.wasm -e CUSTOM=VAR --allow-env=SOME,ENV_VARS -o virt.wasm

Config

experimental

# Setting specific config properties (while disallowing all host config property access):
wasi-virt component.wasm -c custom=prop -o virt.wasm

# Setting config properties with all host config properties allowed:
wasi-virt component.wasm -c custom=prop --allow-config -o virt.wasm

# Setting config properties with restricted host config property access:
wasi-virt component.wasm -c custom=prop --allow-config=some,property -o virt.wasm

Exit

# Create a component which is allowed to exit (terminate execution without a panic and unwind)
wasi-virt component.wasm --allow-exit -o virt.wasm

Filesystem

# Mounting a virtual directory
# (Globs all files in local-dir and virtualizes them)
wasi-virt component.wasm --mount /=./local-dir -o virt.wasm

# Providing a host preopen mapping
wasi-virt component.wasm --preopen /=/restricted/path -o virt.wasm

# Providing both host and virtual preopens
wasi-virt component.wasm --mount /virt-dir=./local --preopen /host-dir=/host/path -o virt.wasm

HTTP

# Allow HTTP
wasi-virt component.wasm --allow-http -o virt.wasm

Random

# Allow random number generation
wasi-virt component.wasm --allow-random -o virt.wasm

Sockets

# Allow socket APIs
wasi-virt component.wasm --allow-sockets -o virt.wasm

Stdio

# Ignore all stdio entirely
wasi-virt component.wasm --stdio=ignore -o virt.wasm

# Throw an error if attempting any stdio
# (this is the default)
wasi-virt component.wasm --stdio=deny -o virt.wasm

# Configure stderr / stdout / stdin individually
wasi-virt component.wasm --stderr=allow -o virt.wasm

Debugging

The --debug flag can be used to get tracing output of system calls.

API

When using the virtualization API, subsystems are passthrough by default instead of deny by default.

use std::fs;
use wasi_virt::{WasiVirt, FsEntry};

fn main() {
    let mut virt = WasiVirt::new();

    // allow all subsystems initially
    virt.allow_all();

    // ignore stdio
    virt.stdio().ignore();

    virt.env()
      // provide an allow list of host env vars
      .allow(&["PUBLIC_ENV_VAR"])
      // provide custom env overrides
      .overrides(&[("SOME", "ENV"), ("VAR", "OVERRIDES")]);
        
    virt.fs()
        // deny arbitrary host preopens
        .deny_host_preopens()
        // mount and virtualize a local directory recursively
        .virtual_preopen("/dir", "/local/dir")
        // create a virtual directory containing some virtual files
        .preopen("/another-dir", FsEntry::Dir(BTreeMap::from([
          // create a virtual file from the given UTF8 source
          ("file.txt", FsEntry::Source("Hello world")),
          // create a virtual file read from a local file at
          // virtualization time
          ("another.wasm", FsEntry::Virtualize("/local/another.wasm"))
          // create a virtual file which reads from a given file
          // path at runtime using the runtime host filesystem API
          ("host.txt", FsEntry::RuntimeFile("/runtime/host/path.txt"))
        ])));

    // compose the virtualization with a component
    virt.compose("path/to/component.wasm");
    // filter enabled subsystems by imports on the composed component
    virt.filter_imports().unwrap();

    let virt_component_bytes = virt.finish().unwrap();
    fs::write("virt.component.wasm", virt_component_bytes).unwrap();
}

When calling a subsystem for the first time, its virtualization will be enabled. Subsystems not used or configured at all will be omitted from the virtualization entirely.

Selective Subsystem Virtualization

By default, when using the wasi-virt CLI command, all virtualizations are enabled. This way, not only is encapsulation the default, but composition with arbitrary components will always work out as all interfaces for WASI should always be available.

Selective subsystem virtualization can be performed directly with the WASI Virt library as above (which does not virtualize all subsystems by default). This allows virtualizing just a single subsystem like env, where it is possible to virtualize only that subsystem and skip other virtualizations and end up creating a smaller virtualization component.

When directly composing another component, individual subsystems are disabled if the composed component does not import that subsystem. This behavior reduces imports in the output component that are never actually used.

There is an important caveat to this: as soon as any subsystem uses IO, all subsystems using IO need to be virtualized in order to fully subclass streams and polls in the virtualization layer. In future this caveat requirement should weaken as these features lower into the core ABI in subsequent WASI versions.

wasm-tools compose will error in these scenarios, and better error messaging may be provided in future when performing invalid compositions like the above. Missing subsystems can be usually detected from the composition warnings list.

Contributing

Note: You may need to install the wasm32-wasi target if not already added (rustup target add wasm32-wasi).

To build, run ./build-adapter.sh which builds the master virtual-adapter component, followed by cargo build to build the virtualization tooling (located in src).

Tests can be run with cargo test, which runs the tests in tests/virt.rs.

Test components are built from the tests/components directory, and run against the configurations provided in tests/cases.

To update WASI, lib/wasi_snapshot_preview1.reactor.wasm needs to be updated to the latest Wasmtime build, and the wit/deps folder needs to be updated with the latest WASI definitions.

License

This project is licensed under the Apache 2.0 license with the LLVM exception. See LICENSE for more details.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.

About

Virtual implementations of WASI APIs

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published