Skip to content

Commit ec8d75f

Browse files
committed
docs: document Wasm host tool support
Signed-off-by: akrm al-hakimi <alhakimiakrmj@gmail.com>
1 parent 9d49d16 commit ec8d75f

2 files changed

Lines changed: 71 additions & 5 deletions

File tree

README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ This project enables running Linux applications (Python, Node.js, Go, Rust, C/C+
4949

5050
### Key Features
5151

52-
- **Thin, opt-in host surface** — by default, the guest has no access to the host filesystem, network, or any host functions. When you enable features like `--mount`, `--net`, or `--enable-tools`, a single `__dispatch` JSON-RPC bridge is registered as the only guest→host channel. See [HOST_FUNCTIONS.md](HOST_FUNCTIONS.md) for the full list of dispatchable operations
52+
- **Thin, opt-in host surface** — by default, the guest has no access to the host filesystem, network, or any host functions. When you enable features like `--mount`, `--net`, `--enable-tools`, or `--tool`, a single `__dispatch` JSON-RPC bridge is registered as the only guest→host channel. See [host_functions.md](docs/host_functions.md) for the full list of dispatchable operations
5353
- **Identity-mapped memory** - Simplified memory layout (vaddr == paddr)
5454
- **Generic cmdline mechanism** - Pass arguments to any application via `-- arg1 arg2 ...`
5555
- **Fast cold start** - Hyperlight's lightweight design enables millisecond startup times
@@ -281,7 +281,19 @@ Options:
281281
-m, --memory <MEMORY> Memory allocation (e.g., 256Mi, 512Mi, 1Gi) [default: 512Mi]
282282
--stack <STACK> Stack size (e.g., 8Mi) [default: 8Mi]
283283
-q, --quiet Quiet mode — suppress host-side status messages
284-
--enable-tools Enable tool dispatch via __dispatch host function
284+
--enable-tools Register the built-in echo tool
285+
--tool <NAME=WASM> Register a WASIp1 module as a host tool (requires wasm-host-fns)
286+
--tool-wasi-dir <HOST[:GUEST]>
287+
Preopen a read-write host directory for Wasm tools
288+
--tool-wasi-dir-ro <HOST[:GUEST]>
289+
Preopen a read-only host directory for Wasm tools
290+
--tool-wasi-env <KEY=VALUE>
291+
Set an environment variable for Wasm tools
292+
--tool-wasi-env-inherit <KEY>
293+
Inherit one host environment variable into Wasm tools
294+
--tool-wasi-fuel <FUEL> Fuel units available to each Wasm tool call [default: 100000000]
295+
--tool-wasi-output-limit <SIZE>
296+
Maximum stdout or stderr captured from one Wasm tool call [default: 1Mi]
285297
--mount <HOST[:GUEST]> Preopen a host directory for the guest's sandboxed filesystem
286298
(repeatable; default guest path: /host)
287299
--net Enable guest networking (off by default)
@@ -297,6 +309,17 @@ Options:
297309
-V, --version Print version
298310
```
299311

312+
### Wasm host tools
313+
314+
Build the CLI with the optional `wasm-host-fns` feature to register host-side custom tools from WASIp1 modules:
315+
316+
```bash
317+
cargo build --manifest-path host/Cargo.toml --release --features wasm-host-fns --bin hyperlight-unikraft
318+
hyperlight-unikraft kernel --initrd app.cpio --tool greet=./greet.wasm
319+
```
320+
321+
The guest still calls the existing `__dispatch` envelope, for example `{"name":"greet","args":{"who":"Ada"}}`. The Wasm handler runs on the host in Wasmtime, receives that request JSON on stdin, and writes either a raw JSON result or `{"result": ...}` / `{"error": "..."}` to stdout. WASI filesystem and environment access are off unless granted with `--tool-wasi-dir`, `--tool-wasi-dir-ro`, `--tool-wasi-env`, or `--tool-wasi-env-inherit`.
322+
300323
## Project Structure
301324

302325
```

docs/host_functions.md

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Implementation lives in [`host/src/lib.rs`](../host/src/lib.rs). Guest-side call
1515
| `fs_*` tools | **Off** | `--mount HOST[:GUEST]` (repeatable) |
1616
| `net_*` tools | **Off** | `--net`, `--net-allow`, or `--net-block` |
1717
| Inbound listen | **Off** | `--port PORT` (requires network enabled) |
18-
| Custom tools | **Off** | `--enable-tools` + `SandboxBuilder::tool()` |
18+
| Custom tools | **Off** | `--enable-tools`, `--tool NAME=WASM` with `wasm-host-fns`, or `SandboxBuilder::tool()` |
1919

2020
With **no flags**, the guest cannot reach the host filesystem or network through dispatch. Only internal plumbing (`__hl_exit`, `__hl_sleep`) is wired.
2121

@@ -85,7 +85,14 @@ hyperlight-unikraft KERNEL [--initrd CPIO] [options] [-- APP_ARGS...]
8585
| `--net-allow HOST_OR_IP` | Allow-list outbound destinations (implies `--net`). Repeatable. |
8686
| `--net-block HOST_OR_IP` | Block-list; all other destinations allowed (implies `--net`). Mutually exclusive with `--net-allow`. |
8787
| `--port PORT` | Allow `net_bind` / listen on `PORT` (implies `--net`). Without `--port`, outbound-only: bind is rejected. |
88-
| `--enable-tools` | Enables custom tool registration. Registers a built-in `echo` tool (used by the `python-tools` example). Library users add their own tools via `SandboxBuilder::tool()`. |
88+
| `--enable-tools` | Registers a built-in `echo` tool (used by the `python-tools` example). Library users add their own tools via `SandboxBuilder::tool()`. |
89+
| `--tool NAME=WASM` | With the Cargo feature `wasm-host-fns`, registers `WASM` as a WASIp1 custom tool named `NAME`. Repeatable. |
90+
| `--tool-wasi-dir HOST[:GUEST]` | Preopens a read-write host directory for all Wasm tools. Default guest path is `/host`. Repeatable. |
91+
| `--tool-wasi-dir-ro HOST[:GUEST]` | Preopens a read-only host directory for all Wasm tools. Default guest path is `/host`. Repeatable. |
92+
| `--tool-wasi-env KEY=VALUE` | Sets an environment variable for all Wasm tools. Repeatable. |
93+
| `--tool-wasi-env-inherit KEY` | Copies one host environment variable into all Wasm tools. Repeatable. |
94+
| `--tool-wasi-fuel FUEL` | Sets the instruction-fuel budget for each Wasm tool call. Default `100000000`. |
95+
| `--tool-wasi-output-limit SIZE` | Caps captured stdout and stderr per Wasm tool call. Default `1Mi`. |
8996

9097
**Mount rules (host-enforced before boot):**
9198

@@ -189,7 +196,34 @@ Sockets are host-side (`socket2`); the guest sees opaque numeric **`fd`** handle
189196

190197
## Custom tools
191198

192-
**CLI:** `--enable-tools` registers a built-in `echo` tool (returns `args` unchanged) used by the [`python-tools` example](../examples/python-tools). The primary purpose of `--enable-tools` is to demonstrate custom host function registration via the API.
199+
**CLI built-in:** `--enable-tools` registers a built-in `echo` tool (returns `args` unchanged) used by the [`python-tools` example](../examples/python-tools).
200+
201+
**CLI Wasm tools:** build with the optional feature and pass one or more `--tool` flags:
202+
203+
```bash
204+
cargo build --manifest-path host/Cargo.toml --features wasm-host-fns --bin hyperlight-unikraft
205+
hyperlight-unikraft kernel --initrd app.cpio --tool greet=./greet.wasm
206+
```
207+
208+
Each `--tool NAME=WASM` module is compiled and linked before VM boot, then invoked as a fresh WASIp1 command for every matching guest `__dispatch` call. The handler receives the existing dispatch request on stdin:
209+
210+
```json
211+
{"name":"NAME","args":<json_value>}
212+
```
213+
214+
The handler writes JSON to stdout. It may write either a raw JSON result value or the normal dispatch envelope:
215+
216+
```json
217+
{"result":<json_value>}
218+
```
219+
220+
```json
221+
{"error":"message"}
222+
```
223+
224+
A raw value is treated as the tool result. A single-key `result` envelope is unwrapped. A single-key `error` envelope becomes the outer `__dispatch` error response. Empty stdout returns JSON null.
225+
226+
WASI capabilities are denied by default except stdio used for the protocol, clocks, and random. Use `--tool-wasi-dir`, `--tool-wasi-dir-ro`, `--tool-wasi-env`, and `--tool-wasi-env-inherit` to grant explicit filesystem and environment access to handlers. Tool names beginning with `__`, `fs_`, or `net_` are reserved.
193227

194228
**Library:**
195229

@@ -214,6 +248,8 @@ Custom handlers run with the same JSON request/response envelope as built-in too
214248
| `fs_list` entries | 100 000 |
215249
| `net_send` / `net_sendto` | 1 MiB decoded bytes |
216250
| `__hl_sleep` | 60 s |
251+
| Wasm tool fuel | 100 000 000 instructions per call by default; configurable with `--tool-wasi-fuel` |
252+
| Wasm tool stdout / stderr | 1 MiB each per call by default; configurable with `--tool-wasi-output-limit` |
217253
| Open host sockets | 1024 per sandbox |
218254
| AllowList learned DNS IPs | 256 |
219255

@@ -242,6 +278,13 @@ Custom handlers run with the same JSON request/response envelope as built-in too
242278
- A compromised guest can invoke any **registered** tool name; do not register powerful custom tools unless needed.
243279
- Payload size is capped; malformed JSON fails closed with an error response.
244280

281+
**When `--tool` is used with `wasm-host-fns`:**
282+
283+
- Handler code runs on the host inside Wasmtime, not inside the Unikraft VM.
284+
- WASI filesystem and environment access are capability-based and off unless explicitly granted with `--tool-wasi-*` flags.
285+
- Fuel limits bound Wasm instruction execution, but do not turn filesystem operations into a full wall-clock timeout.
286+
- Handlers are untrusted code from the host operator's filesystem; only load modules you intend to grant these capabilities to.
287+
245288
**Not exposed via dispatch:** Host shell, arbitrary process spawn, unrestricted host `exec`, or kernel modules — only the tools listed above.
246289

247290
**Operators should:** Use minimal flags, allow-lists over `--net` where possible, mount least-privilege directories, and run guests with the smallest initrd/runtime required.

0 commit comments

Comments
 (0)