Stream: cargo-component

Topic: ✔ Test host and guest not working


view this post on Zulip Alen (Apr 01 2024 at 12:32):

I've created a test wasmtime host and a test guest with cargo component. The world definition is as follows:

package local:test-guest;

interface string-modifier {
    edit-string: func(input-string: string) -> string;
}

interface host {
    name: func() -> string;
}

/// An example world for the component to target.
world test-world {
    import host;
    export string-modifier;
}

The host code:

use wasmtime::component;
use wasmtime::{Config, Engine, Store};
use local::test_guest::host::Host;

component::bindgen!();

struct MyState {
    name: String,
}

impl Host for MyState {
    fn name(&mut self) -> wasmtime::Result<String> {
        Ok(self.name.clone())
    }
}

fn main() -> wasmtime::Result<()> {
    let mut config = Config::new();
    config.wasm_component_model(true);
    let engine = Engine::new(&config)?;
    let component = component::Component::from_file(&engine, "./test_guest.wasm")?;

    let mut linker = component::Linker::new(&engine);
    TestWorld::add_to_linker(&mut linker, |state: &mut MyState| state)?;

    let mut store = Store::new(
        &engine,
        MyState{
            name: "TestHost".to_string(),
        },
    );
    let (bindings, _) = TestWorld::instantiate(&mut store, &component, &linker)?;

    let output = bindings.local_test_guest_string_modifier().call_edit_string(&mut store,"Hello test")?;
    println!("{}",output);
    Ok(())
}

The guest code:

#[allow(warnings)]
mod bindings;

use bindings::exports::local::test_guest::string_modifier::Guest;

struct Component;

impl Guest for Component {
    fn edit_string(s: String) -> String {
        s.replace("test", "World")
    }
}

bindings::export!(Component with_types_in bindings);

When I generate the .wasm file with cargo component build -r, wasm-tools component wit target/wasm32-wasi/release/test_guest.wasm reveals the following world:

package root:component;

world root {
  import wasi:cli/environment@0.2.0;
  import wasi:cli/exit@0.2.0;
  import wasi:io/error@0.2.0;
  import wasi:io/streams@0.2.0;
  import wasi:cli/stdin@0.2.0;
  import wasi:cli/stdout@0.2.0;
  import wasi:cli/stderr@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 local:test-guest/string-modifier;
}

For some reason, the wasi imports don't seem to be pruned despite not being used. From what I understand, cargo component is supposed to remove them?
When I try to run the resulting component with the test host, it throws the following error:

Error: import `wasi:cli/environment@0.2.0` has the wrong type

Caused by:
    0: instance export `get-environment` has the wrong type
    1: expected func found nothing

So, it seems that the component wants to import wasi interfaces that the host doesn't provide, even though the wasi imports are unused. I've tried manually specifying the wasi_snapshot_preview1.reactor.wasm adapter, but that didn't change anything. Is this a bug or am I missing something?
Versions:

view this post on Zulip Sekhar Ravinutala (Apr 01 2024 at 13:53):

This happened to me too. You need to also add command to the linker.

wasmtime_wasi::command::sync::add_to_linker(&mut linker)?;

view this post on Zulip Alen (Apr 01 2024 at 14:05):

@Sekhar Ravinutala Wouldn't that effectively break guest isolation? Also, in the project I want to use the component model in, I'll be using a custom state and implementing multiple function imports. That seems to clash with wasi because the add_to_linker you refer to expects state to implement WasiView trait and I'm guessing it's not meant to be implemented manually...

view this post on Zulip Sekhar Ravinutala (Apr 01 2024 at 14:32):

@Alen Yeah, that's the other thing that threw me off, and I ended up implementing WasiView for it. Fortunately, it's simple if you just use the defaults, as below. The extra command link need is coming from the wrapper preview1 code that is componentizing the module as I understand, check out the tool at at wa2.dev to examine your component (or use wasm-tools CLI).

impl WasiView for HostComponent {
    fn ctx(&mut self) -> &mut WasiCtx {
        &mut self.ctx
    }

    fn table(&mut self) -> &mut ResourceTable {
        &mut self.table
    }
}

impl HostComponent {
    fn new() -> Self {
        let ctx = WasiCtxBuilder::new().build();
        let table = ResourceTable::new();
        ...
    }
}

view this post on Zulip Alen (Apr 01 2024 at 15:02):

The wasi imports do seem to get pruned in the example https://github.com/bytecodealliance/cargo-component/tree/main/example

A Cargo subcommand for creating WebAssembly components based on the component model proposal. - bytecodealliance/cargo-component

view this post on Zulip Alen (Apr 01 2024 at 15:38):

Not sure what significant differences there are between the example code and the code I'm testing that makes cargo component prune wasi from the example, but not from the test.

view this post on Zulip Lann Martin (Apr 01 2024 at 16:30):

When you run wasm-tools component wit on a module (which is what rustc itself currently emits) you get the entire input world with nothing pruned. When you run it against a component (which cargo component emits) you see the actual world after dead code elimination.

view this post on Zulip Alen (Apr 01 2024 at 16:33):

@Lann Martin I'm using exactly the same command in both the example and the test directories: cargo component build -r. Am I supposed to do something to the .wasm file cargo component produces? I thought it already does composition.

view this post on Zulip Peter Huene (Apr 01 2024 at 16:34):

it's technically wit-component that's eliding the imports as it isn't finding any matching imports in the core module (output of rustc) it's componentizing

view this post on Zulip Lann Martin (Apr 01 2024 at 16:34):

Did you create your project with cargo component new?

view this post on Zulip Alen (Apr 01 2024 at 16:36):

Lann Martin said:

Did you create your project with cargo component new?

I did. Here's the contents of Cargo.toml:

[package]
name = "test_guest"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wit-bindgen-rt = { version = "0.23.0", features = ["bitflags"] }

[package.metadata.component]
package = "local:test-guest"

[package.metadata.component.dependencies]

[workspace]

cargo component new did produce a slightly different file, but I edited it to match the example as much as possible.

view this post on Zulip Lann Martin (Apr 01 2024 at 16:38):

Ohhh I see; I misunderstood your question. You are seeing wasi imports because the Rust stdlib uses wasi via wasi-libc.

view this post on Zulip Peter Huene (Apr 01 2024 at 16:39):

most likely because of the panic machinery

view this post on Zulip Peter Huene (Apr 01 2024 at 16:40):

which the generated bindings make use of between your code and the raw imports / exports

view this post on Zulip Alen (Apr 01 2024 at 16:41):

Ah, so, if I want to remove wasi, I need to remove any dependency on stdlib?

view this post on Zulip Alen (Apr 01 2024 at 16:41):

Or link it like Sekhar suggested

view this post on Zulip Lann Martin (Apr 01 2024 at 16:42):

Not necessarily any dependency on stdlib but it can be tricky to avoid e.g. wasi:clocks/wall-clock@0.2.0;. And you might need to change the panic behavior as @Peter Huene mentions.

view this post on Zulip Peter Huene (Apr 01 2024 at 16:42):

you can technically target wasm32-unknown-unknown (--target wasm32-unknown-unknown), but it's not a properly supported target

view this post on Zulip Lann Martin (Apr 01 2024 at 16:44):

https://doc.rust-lang.org/stable/nightly-rustc/rustc_target/spec/targets/wasm32_unknown_unknown/index.html

Although the standard library is available, most of it returns an error immediately

view this post on Zulip Peter Huene (Apr 01 2024 at 16:45):

there is that; if you're absolutely not using the stdlib, i'd go no_std

view this post on Zulip Peter Huene (Apr 01 2024 at 16:45):

but generally i'd advocate for your host to support WASI, even if you give it a WASI context that gives the program access to nothing at all

view this post on Zulip Lann Martin (Apr 01 2024 at 16:45):

Yeah, by default (?) wasmtime-wasi should still be isolated.

view this post on Zulip Alen (Apr 01 2024 at 16:46):

I see. Thank you. Does linking wasi provide an escape route from the guest?

view this post on Zulip Peter Huene (Apr 01 2024 at 16:46):

for my understanding, what do you mean by "escape route"?

view this post on Zulip Lann Martin (Apr 01 2024 at 16:46):

Take a look at the documented defaults:
https://docs.rs/wasmtime-wasi/19.0.0/wasmtime_wasi/struct.WasiCtxBuilder.html#method.new

view this post on Zulip Lann Martin (Apr 01 2024 at 16:47):

i.e. host clocks may count as an "escape" depending on your requirements

view this post on Zulip Alen (Apr 01 2024 at 16:47):

Peter Huene said:

for my understanding, what do you mean by "escape route"?

If the wasm code is untrusted, would it be able to use wasi to break out of the isolated memory?

view this post on Zulip Alen (Apr 01 2024 at 16:48):

Lann Martin said:

Take a look at the documented defaults:
https://docs.rs/wasmtime-wasi/19.0.0/wasmtime_wasi/struct.WasiCtxBuilder.html#method.new

Ah, got it. Thank you.

view this post on Zulip Peter Huene (Apr 01 2024 at 16:48):

Alen, it definitely should not be able to; we would consider it a critical security issue if so

view this post on Zulip Alen (Apr 01 2024 at 17:10):

Everything works now! My misunderstanding was that wasi was permissive by default and that it had to be avoided. The issue is resolved.

view this post on Zulip Notification Bot (Apr 01 2024 at 17:10):

Alen has marked this topic as resolved.


Last updated: Nov 25 2024 at 19:03 UTC