Skip to content

Commit

Permalink
fix: add test for and verify full encapsulation (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford authored Sep 1, 2023
1 parent e5cac31 commit bb44033
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 24 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ heck = { version = "0.4" }
tokio = { version = "1.30.0", features = ["macros"] }
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", features = ["component-model"] }
wasmtime-wasi = { git = "https://github.com/bytecodealliance/wasmtime" }
wasmparser = "0.112.0"

[workspace.dependencies]
anyhow = "1"
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Supports all of the current WASI subsystems:
- [Environment](#env): Set environment variables, configure host environment variable permissions
- [Exit](#exit): Allow / Deny
- [Filesystem](#filesystem): Mount a read-only filesystem, configure host filesystem preopen remappings or pass-through.
- [HTTP](#http): Allow / Deny
- [Random](#random): Allow / Deny
- [Sockets](#sockets): Allow / Deny
- [Stdio](#stdio): Allow / Deny / Ignore
Expand Down Expand Up @@ -106,6 +107,13 @@ wasi-virt component.wasm --preopen /=/restricted/path -o virt.wasm
wasi-virt component.wasm --mount /virt-dir=./local --preopen /host-dir=/host/path -o virt.wasm
```

### HTTP

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

### Random

```sh
Expand Down
Binary file modified lib/virtual_adapter.wasm
Binary file not shown.
Binary file modified lib/wasi_snapshot_preview1.reactor.wasm
Binary file not shown.
8 changes: 7 additions & 1 deletion src/virt_deny.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use anyhow::Result;
use walrus::{Module, ValType};

use crate::walrus_ops::add_stub_exported_func;
use crate::{
virt_io::{stub_clocks_virt, stub_http_virt, stub_sockets_virt},
walrus_ops::add_stub_exported_func,
};

// set exports to deny clock access
pub(crate) fn deny_clocks_virt(module: &mut Module) -> Result<()> {
stub_clocks_virt(module)?;
add_stub_exported_func(
module,
"wasi:clocks/monotonic-clock#now",
Expand Down Expand Up @@ -72,6 +76,7 @@ pub(crate) fn deny_clocks_virt(module: &mut Module) -> Result<()> {
}

pub(crate) fn deny_http_virt(module: &mut Module) -> Result<()> {
stub_http_virt(module)?;
add_stub_exported_func(
module,
"wasi:http/incoming-handler#handle",
Expand Down Expand Up @@ -373,6 +378,7 @@ pub(crate) fn deny_exit_virt(module: &mut Module) -> Result<()> {
}

pub(crate) fn deny_sockets_virt(module: &mut Module) -> Result<()> {
stub_sockets_virt(module)?;
add_stub_exported_func(
module,
"wasi:sockets/instance-network#instance-network",
Expand Down
61 changes: 41 additions & 20 deletions src/virt_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,23 +325,38 @@ pub(crate) fn create_io_virt<'a>(
flags |= FLAGS_HOST_PREOPENS;
}
}
let mut disable_stdio = true;
if let Some(stdio) = stdio {
match stdio.stdin {
StdioCfg::Allow => flags |= FLAGS_ENABLE_STDIN,
StdioCfg::Allow => {
flags |= FLAGS_ENABLE_STDIN;
disable_stdio = false;
}
StdioCfg::Ignore => flags |= FLAGS_IGNORE_STDIN,
// deny is the default
StdioCfg::Deny => {}
}
match stdio.stdout {
StdioCfg::Allow => flags |= FLAGS_ENABLE_STDOUT,
StdioCfg::Allow => {
flags |= FLAGS_ENABLE_STDOUT;
disable_stdio = false;
}
StdioCfg::Ignore => flags |= FLAGS_IGNORE_STDOUT,
StdioCfg::Deny => {}
}
match stdio.stderr {
StdioCfg::Allow => flags |= FLAGS_ENABLE_STDERR,
StdioCfg::Allow => {
flags |= FLAGS_ENABLE_STDERR;
disable_stdio = false;
}
StdioCfg::Ignore => flags |= FLAGS_IGNORE_STDERR,
StdioCfg::Deny => {}
}
} else {
strip_stdio_virt(module)?;
}
if disable_stdio {
stub_stdio_virt(module)?;
}

// First we iterate the options and fill in all HostDir and HostFile entries
Expand Down Expand Up @@ -498,6 +513,7 @@ pub(crate) fn create_io_virt<'a>(
// replacing it with a stub panic
if !fs_passthrough {
stub_io_virt(module)?;
stub_fs_virt(module, true)?;
} else {
flags |= FLAGS_HOST_PASSTHROUGH;
}
Expand Down Expand Up @@ -548,18 +564,23 @@ pub(crate) fn create_io_virt<'a>(
// when stubbing functions that are not part of the virtual adapter exports, we therefore
// have to create this functions fresh.
// Ideally, we should generate these stubs automatically from WASI definitions.
pub(crate) fn stub_fs_virt(module: &mut Module) -> Result<()> {
stub_imported_func(module, "wasi:filesystem/preopens", "get-directories", true)?;
stub_imported_func(module, "wasi:filesystem/types", "read-via-stream", true)?;
pub(crate) fn stub_fs_virt(module: &mut Module, uses_fs: bool) -> Result<()> {
stub_imported_func(
module,
"wasi:filesystem/preopens",
"get-directories",
uses_fs,
)?;
stub_imported_func(module, "wasi:filesystem/types", "read-via-stream", uses_fs)?;
stub_imported_func(module, "wasi:filesystem/types", "write-via-stream", false)?;
stub_imported_func(module, "wasi:filesystem/types", "append-via-stream", false)?;
stub_imported_func(module, "wasi:filesystem/types", "advise", false)?;
stub_imported_func(module, "wasi:filesystem/types", "sync-data", false)?;
stub_imported_func(module, "wasi:filesystem/types", "get-flags", false)?;
stub_imported_func(module, "wasi:filesystem/types", "get-type", true)?;
stub_imported_func(module, "wasi:filesystem/types", "get-type", uses_fs)?;
stub_imported_func(module, "wasi:filesystem/types", "set-size", false)?;
stub_imported_func(module, "wasi:filesystem/types", "set-times", false)?;
stub_imported_func(module, "wasi:filesystem/types", "read", true)?;
stub_imported_func(module, "wasi:filesystem/types", "read", uses_fs)?;
stub_imported_func(module, "wasi:filesystem/types", "write", false)?;
stub_imported_func(module, "wasi:filesystem/types", "read-directory", false)?;
stub_imported_func(module, "wasi:filesystem/types", "sync", false)?;
Expand All @@ -569,11 +590,11 @@ pub(crate) fn stub_fs_virt(module: &mut Module) -> Result<()> {
"create-directory-at",
false,
)?;
stub_imported_func(module, "wasi:filesystem/types", "stat", true)?;
stub_imported_func(module, "wasi:filesystem/types", "stat-at", true)?;
stub_imported_func(module, "wasi:filesystem/types", "stat", uses_fs)?;
stub_imported_func(module, "wasi:filesystem/types", "stat-at", uses_fs)?;
stub_imported_func(module, "wasi:filesystem/types", "set-times-at", false)?;
stub_imported_func(module, "wasi:filesystem/types", "link-at", false)?;
stub_imported_func(module, "wasi:filesystem/types", "open-at", true)?;
stub_imported_func(module, "wasi:filesystem/types", "open-at", uses_fs)?;
stub_imported_func(module, "wasi:filesystem/types", "readlink-at", false)?;
stub_imported_func(
module,
Expand Down Expand Up @@ -602,26 +623,26 @@ pub(crate) fn stub_fs_virt(module: &mut Module) -> Result<()> {
stub_imported_func(module, "wasi:filesystem/types", "try-lock-shared", false)?;
stub_imported_func(module, "wasi:filesystem/types", "try-lock-exclusive", false)?;
stub_imported_func(module, "wasi:filesystem/types", "unlock", false)?;
stub_imported_func(module, "wasi:filesystem/types", "drop-descriptor", true)?;
stub_imported_func(module, "wasi:filesystem/types", "drop-descriptor", uses_fs)?;
stub_imported_func(
module,
"wasi:filesystem/types",
"read-directory-entry",
true,
uses_fs,
)?;
stub_imported_func(
module,
"wasi:filesystem/types",
"drop-directory-entry-stream",
true,
uses_fs,
)?;
stub_imported_func(module, "wasi:filesystem/types", "is-same-object", true)?;
stub_imported_func(module, "wasi:filesystem/types", "is-same-object", uses_fs)?;
stub_imported_func(module, "wasi:filesystem/types", "metadata-hash", false)?;
stub_imported_func(module, "wasi:filesystem/types", "metadata-hash-at", false)?;
Ok(())
}

pub(crate) fn stub_io_virt(module: &mut Module) -> Result<()> {
fn stub_io_virt(module: &mut Module) -> Result<()> {
stub_imported_func(module, "wasi:poll/poll", "drop-pollable", true)?;
stub_imported_func(module, "wasi:poll/poll", "poll-oneoff", true)?;
stub_imported_func(module, "wasi:io/streams", "read", false)?;
Expand Down Expand Up @@ -655,9 +676,9 @@ pub(crate) fn stub_clocks_virt(module: &mut Module) -> Result<()> {
}

pub(crate) fn stub_stdio_virt(module: &mut Module) -> Result<()> {
stub_imported_func(module, "wasi:cli/stdin", "get-stdin", true)?;
stub_imported_func(module, "wasi:cli/stdout", "get-stdout", true)?;
stub_imported_func(module, "wasi:cli/stderr", "get-stderr", true)?;
stub_imported_func(module, "wasi:cli/stdin", "get-stdin", false)?;
stub_imported_func(module, "wasi:cli/stdout", "get-stdout", false)?;
stub_imported_func(module, "wasi:cli/stderr", "get-stderr", false)?;
stub_imported_func(
module,
"wasi:cli/terminal-stdin",
Expand Down Expand Up @@ -764,7 +785,7 @@ pub(crate) fn stub_sockets_virt(module: &mut Module) -> Result<()> {

// strip functions only have to dce the virtual adapter
pub(crate) fn strip_fs_virt(module: &mut Module) -> Result<()> {
stub_fs_virt(module)?;
stub_fs_virt(module, false)?;
remove_exported_func(module, "wasi:filesystem/preopens#get-directories")?;

remove_exported_func(module, "wasi:filesystem/types#read-via-stream")?;
Expand Down
15 changes: 15 additions & 0 deletions tests/cases/encapsulate-none.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
component = "do-everything"

[virt-opts]
exit = false
fs.host-preopens = false
stdio.stdin = "ignore"
stdio.stdout = "ignore"
stdio.stderr = "ignore"
clocks = false
http = false
random = false
sockets = false

[expect]
encapsulation = true
58 changes: 55 additions & 3 deletions tests/virt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::process::Command;
use std::{fs, path::PathBuf};
use wasi_virt::WasiVirt;
use wasm_compose::composer::ComponentComposer;
use wasmparser::{Chunk, Parser, Payload};
use wasmtime::{
component::{Component, Linker},
Config, Engine, Store, WasmBacktraceDetails,
Expand Down Expand Up @@ -48,6 +49,7 @@ fn cmd(arg: &str) -> Result<()> {
struct TestExpectation {
env: Option<Vec<(String, String)>>,
file_read: Option<String>,
encapsulation: Option<bool>,
}

#[derive(Deserialize, Debug)]
Expand All @@ -60,7 +62,7 @@ struct TestCase {
expect: TestExpectation,
}

const DEBUG: bool = true;
const DEBUG: bool = false;

#[tokio::test]
async fn virt_test() -> Result<()> {
Expand All @@ -73,7 +75,7 @@ async fn virt_test() -> Result<()> {
let test_case_name = test_case_file_name.strip_suffix(".toml").unwrap();

// Filtering...
// if test_case_name != "encapsulate" {
// if test_case_name != "encapsulate-none" {
// continue;
// }

Expand Down Expand Up @@ -136,7 +138,17 @@ async fn virt_test() -> Result<()> {
.finish()
.with_context(|| format!("Error creating virtual adapter for {:?}", test_case_path))?;

fs::write(&virt_component_path, virt_component.adapter)?;
fs::write(&virt_component_path, &virt_component.adapter)?;

// verify the encapsulation
if test.expect.encapsulation.unwrap_or(false) {
if let Some(impt) = has_component_import(virt_component.adapter.as_slice())? {
panic!(
"Unexpected import \"{impt}\" in virtualization {:?}",
virt_component_path
);
}
}

// compose the test component with the defined test virtualization
if DEBUG {
Expand Down Expand Up @@ -262,3 +274,43 @@ async fn virt_test() -> Result<()> {
}
Ok(())
}

fn has_component_import(bytes: &[u8]) -> Result<Option<String>> {
let mut parser = Parser::new(0);
let mut offset = 0;
loop {
let payload = match parser.parse(&bytes[offset..], true)? {
Chunk::NeedMoreData(_) => unreachable!(),
Chunk::Parsed { payload, consumed } => {
offset += consumed;
payload
}
};
match payload {
Payload::ModuleSection { mut parser, range } => {
let mut ioffset = range.start;
loop {
let payload = match parser.parse(&bytes[ioffset..], true)? {
Chunk::NeedMoreData(_) => unreachable!(),
Chunk::Parsed { payload, consumed } => {
ioffset += consumed;
payload
}
};
match payload {
Payload::ImportSection(impt_section_reader) => {
for impt in impt_section_reader {
let impt = impt?;
return Ok(Some(format!("{}#{}", impt.module, impt.name)));
}
}
Payload::End(_) => return Ok(None),
_ => {}
}
}
}
Payload::End(_) => return Ok(None),
_ => {}
}
}
}
1 change: 1 addition & 0 deletions update-wasi.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
git clone https://github.com/bytecodealliance/wasmtime --depth 1
cd wasmtime
git checkout 134dddc
git submodule init
git submodule update
cargo build -p wasi-preview1-component-adapter --target wasm32-unknown-unknown --release
Expand Down

0 comments on commit bb44033

Please sign in to comment.