bytecodealliance / WASI-Virt

Virtual implementations of WASI APIs
Apache License 2.0
144 stars 17 forks source link

Fs access from virtualized component in Rust host fails #80

Closed Finfalter closed 3 months ago

Finfalter commented 3 months ago

I do not succeed to access the filesystem from a virtualized component which is executed in a Rust host.

The virtualized component is made from a very simple snippet of Go code. It tries to list all files in given directory ("/"). I try to run it with a simple Rust host.

My observations should be easy to comprehend by:

  1. cloning the Rust host
  2. cloning the virtualized component
  3. Executing "third-component/scripts/build.sh" of virtualized component.

The essential build steps are

# generate bindings
wit-bindgen tiny-go ./read.wit --world discovery --out-dir=gen

# build for target 'wasi'
tinygo build -o reader.wasm -target=wasi read.go

# embed WIT
wasm-tools component embed --world discovery ./read.wit reader.wasm -o reader.embed.wasm

# create component
export COMPONENT_ADAPTER_REACTOR=binaries/wasi_snapshot_preview1.reactor.wasm
wasm-tools component new -o reader.component.wasm --adapt wasi_snapshot_preview1="$COMPONENT_ADAPTER_REACTOR" reader.embed.wasm

# virtualize component
wasi-virt reader.component.wasm --mount /=./ -o reader.wasm

NOTE: This topic has been discussed in Zulip.

kateinoigakukun commented 3 months ago

The --mount option in wasi-virt does not allow access to the host, but it means it just does not deny access. Thus even if you pass the --mount option to wasi-virt, you still need to allow directory access from the host runtime. If we would allow a guest Wasm program to specify host directories to be preopened, an untrusted Wasm program could see arbitrary host directories, so we need explicit control by host side.

Finfalter commented 3 months ago

I agree with you. Based on your explanation and some further research regarding the runtime, wasmtime, I found preopened_dir() and implemented it accordingly. Helas, I still cannot access the filesystem from the component. What I have is

Go: dir, err := os.Open("/data") wasi-virt: wasi-virt reader.component.wasm --mount /data=./ -o reader.wasm Rust host: let wasi = WasiCtxBuilder::new().preopened_dir("./", "/data", DirPerms::all(), FilePerms::all())?.build();

What is said about the virtualization by wasi-virt is

Virtualized files from local filesystem:

  - /data/README.md : ./README.md
  - /data/binaries : ./binaries
  - [..]
  - /data/scripts : ./scripts
  - /data/scripts/build.sh : ./scripts/build.sh

Is this setup supposed to match?

kateinoigakukun commented 3 months ago

Ah, sorry, I was confused with --preopen. With --preopen, you still need to allow explicit access by host runtime, but with --mount all files are encoded into .wasm file, and don't need access to host file system at runtime.

So I guess the missing option here is --allow-all to the wasi-virt CLI invocation.

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

wasi-virt by default denies all subsystems, so you need to enable subsystems you need explicitly. Usually --allow-all would be what you want.

Finfalter commented 3 months ago

I agree that there is the sweetspot somewhere. Let's go through the options:

  1. --allow-all throws errors during instantiation of the kind component imports instance 'wasi:cli/exit@0.2.0', but a matching implementation was not found in the linker. That is probably that I would have to implement all import interfaces - which I wanted to avoid because they are numerous.
  2. --mount: in case the files are encoded into the .wasm file I shouldn't need any permission from the host, right? Perhaps, I do a mistake in using it then. I tried many path variants, with and without explicit host authorization, and it never worked for me.
  3. --preopen: That sounds for me like the mechanism I really want. However, it leaves me with a structure as shown below which, again, results in errors like component imports instancewasi:io/error@0.2.0, but a matching implementation was not found in the linker. My hope from reading the chapter about the filesystem was that I could "just" use it without implementing any additional interfaces. Maybe that assumption is wrong.

After virtualization I get displayed something like this:

  [..]
  world root {
    import wasi:io/error@0.2.0;
    import wasi:io/poll@0.2.0;
    import wasi:io/streams@0.2.0;
    import wasi:clocks/wall-clock@0.2.0;
    import wasi:filesystem/types@0.2.0;
    import wasi:filesystem/preopens@0.2.0;

    export finfalter:reader/discover;
  }
}