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:
This happened to me too. You need to also add command to the linker.
wasmtime_wasi::command::sync::add_to_linker(&mut linker)?;
@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...
@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();
...
}
}
The wasi imports do seem to get pruned in the example https://github.com/bytecodealliance/cargo-component/tree/main/example
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.
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.
@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.
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
Did you create your project with cargo component new
?
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.
Ohhh I see; I misunderstood your question. You are seeing wasi imports because the Rust stdlib uses wasi via wasi-libc.
most likely because of the panic machinery
which the generated bindings make use of between your code and the raw imports / exports
Ah, so, if I want to remove wasi, I need to remove any dependency on stdlib?
Or link it like Sekhar suggested
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.
you can technically target wasm32-unknown-unknown
(--target wasm32-unknown-unknown
), but it's not a properly supported target
Although the standard library is available, most of it returns an error immediately
there is that; if you're absolutely not using the stdlib, i'd go no_std
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
Yeah, by default (?) wasmtime-wasi should still be isolated.
I see. Thank you. Does linking wasi provide an escape route from the guest?
for my understanding, what do you mean by "escape route"?
Take a look at the documented defaults:
https://docs.rs/wasmtime-wasi/19.0.0/wasmtime_wasi/struct.WasiCtxBuilder.html#method.new
i.e. host clocks may count as an "escape" depending on your requirements
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?
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.
Alen, it definitely should not be able to; we would consider it a critical security issue if so
Everything works now! My misunderstanding was that wasi was permissive by default and that it had to be avoided. The issue is resolved.
Alen has marked this topic as resolved.
Last updated: Nov 25 2024 at 19:03 UTC