|
1 | 1 | # herkos |
2 | 2 |
|
3 | | -> ⚠️ **This project is work in progress! Not all wasm features nor corner cases were tested! Do not use in production!** |
| 3 | +[](https://github.com/arnoox/herkos/actions/workflows/ci.yml) |
| 4 | +[](https://crates.io/crates/herkos) |
| 5 | +[](LICENSE) |
| 6 | +[]() |
4 | 7 |
|
5 | | -A compilation pipeline that transpiles WebAssembly modules into memory-safe Rust code with compile-time isolation guarantees (memory+capabilities), replacing runtime hardware-based memory protection (MMU/MPU) with type-system-enforced safety. |
| 8 | +**Compile-time memory isolation via WebAssembly-to-Rust transpilation.** |
6 | 9 |
|
7 | | -herkos approach: if the Rust compiler accepts the transpiled code, isolation is guaranteed; no MMU, no context switches, no runtime overhead for proven accesses. |
| 10 | +herkos transpiles WebAssembly modules into safe Rust code, offering type-system-enforced isolation as a lightweight alternative to hardware-based memory protection (MMU/MPU). When hardware isolation is overkill or simply unavailable, herkos lets the Rust compiler enforce the guarantees instead: no MMU, no context switches, no runtime overhead for proven accesses. |
8 | 11 |
|
9 | | -**WebAssembly → Rust source → Safe binary** |
| 12 | +> **Status:** Work in progress. Not all Wasm features or corner cases are covered. Do not use in production. |
10 | 13 |
|
11 | | -## Motivation |
| 14 | +## The idea |
12 | 15 |
|
13 | | -Running untrusted or unsafe-language components alongside safe code usually requires hardware isolation (MMU/MPU, hypervisors) or process boundaries, all of which add runtime overhead and complexity. What if the compiler itself could enforce "spatial" isolation? |
| 16 | +Running untrusted or unsafe-language code usually requires hardware isolation (processes, hypervisors, MMU/MPU). That's the right tool for many jobs — but sometimes it's overkill: a small plugin, a single C library, a microcontroller with no MMU. For those cases, herkos offers a lighter-weight approach: transpile Wasm to safe Rust, so the compiler itself enforces spatial isolation. |
14 | 17 |
|
15 | | -herkos explores this idea: transpile WebAssembly modules into safe Rust, so that memory isolation and capability restrictions are checked at compile time rather than at runtime. This opens up several use cases: |
| 18 | +``` |
| 19 | + Compile-time isolation |
| 20 | + ┌──────────────────────┐ |
| 21 | + C / C++ / Rust ──────▶ │ .wasm ──herkos──▶│ Safe Rust ──rustc──▶ Binary |
| 22 | + (unsafe code) clang/ │ │ (no unsafe) (isolated) |
| 23 | + rustc └──────────────────────┘ |
| 24 | +``` |
16 | 25 |
|
17 | | -- **Isolating untrusted components** — sandbox C/C++ libraries without hardware protection |
18 | | -- **Porting unsafe-language code to Rust** — use Wasm as an intermediate representation to get a safe Rust version of existing C/C++ code |
19 | | -- **Efficient cross-partition communication** — how do "compile-time-MMU" partitions talk to each other efficiently? |
| 26 | +This enables: |
| 27 | +- **Sandboxing C/C++ libraries** without hardware protection: isolate a JPEG decoder, a crypto library, or a plugin system |
| 28 | +- **Porting unsafe code to Rust**: use Wasm as an IR to get a safe Rust version of existing C/C++ code |
| 29 | +- **Bare-metal isolation**: `#![no_std]`, no heap, no OS required. Run isolated components on microcontrollers |
| 30 | + |
| 31 | +## Example: C to safe Rust in 3 steps |
| 32 | + |
| 33 | +**1. Write C code** (or any language that compiles to Wasm): |
| 34 | +```c |
| 35 | +int fibonacci(int n) { |
| 36 | + if (n <= 0) return 0; |
| 37 | + if (n == 1) return 1; |
| 38 | + int a = 0, b = 1, i = 2; |
| 39 | + while (i <= n) { int tmp = a + b; a = b; b = tmp; i++; } |
| 40 | + return b; |
| 41 | +} |
| 42 | +``` |
20 | 43 |
|
21 | | -## Architecture |
| 44 | +**2. Compile to Wasm, then transpile:** |
| 45 | +```bash |
| 46 | +clang --target=wasm32 -O2 -nostdlib -Wl,--no-entry -Wl,--export-all -o fibonacci.wasm fibonacci.c |
| 47 | +herkos fibonacci.wasm --output fibonacci.rs |
| 48 | +``` |
22 | 49 |
|
23 | | -The project is a Rust workspace with three core crates: |
| 50 | +**3. Use from Rust — every call returns `Result`, traps become errors:** |
| 51 | +```rust |
| 52 | +mod fibonacci; |
24 | 53 |
|
25 | | -| Crate | Purpose | |
26 | | -|---|---| |
27 | | -| `herkos` | CLI transpiler: parses `.wasm` binaries, emits Rust source code | |
28 | | -| `herkos-runtime` | `#![no_std]` runtime library shipped with transpiled output (isolated memory, capability types, wasm operations) | |
29 | | -| `herkos-tests` | collection of wat/Rust/C sources that are compiled to .wasm, transpiled and tested. | |
| 54 | +fn main() { |
| 55 | + let mut module = fibonacci::new().expect("init failed"); |
| 56 | + let result = module.fibonacci(10).unwrap(); |
| 57 | + assert_eq!(result, 55); |
| 58 | +} |
| 59 | +``` |
30 | 60 |
|
31 | | -## Build and test |
| 61 | +The generated `fibonacci.rs` contains **no `unsafe`**, **no function pointers**, **no heap allocations**. Memory access is bounds-checked, division by zero returns `Err(WasmTrap::DivisionByZero)`, and the module's memory is structurally isolated from the rest of your program. |
| 62 | + |
| 63 | +See the full [C-to-Wasm-to-Rust example](examples/c-to-wasm-to-rust/). |
| 64 | + |
| 65 | +## How it works |
| 66 | + |
| 67 | +``` |
| 68 | +.wasm ─▶ Parser ─▶ IR Builder ─▶ Optimizer ─▶ Backend ─▶ Codegen ─▶ rustfmt ─▶ output.rs |
| 69 | + wasmparser SSA-form dead block safe Rust |
| 70 | + IR elimination backend source |
| 71 | +``` |
| 72 | + |
| 73 | +The transpiler parses a `.wasm` binary, builds an SSA-form intermediate representation, optimizes it, and emits Rust source code through a safe backend. The output depends only on the `herkos-runtime` crate. |
| 74 | + |
| 75 | +### Generated code structure |
| 76 | + |
| 77 | +Each Wasm module becomes a Rust struct that owns its isolated memory: |
| 78 | + |
| 79 | +```rust |
| 80 | +pub struct WasmModule(pub Module<Globals, MAX_PAGES, TABLE_SIZE>); |
| 81 | + |
| 82 | +impl WasmModule { |
| 83 | + pub fn fibonacci(&mut self, n: i32) -> WasmResult<i32> { ... } |
| 84 | + pub fn add(&mut self, a: i32, b: i32) -> WasmResult<i32> { ... } |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +- **Memory** — `IsolatedMemory<MAX_PAGES>`: a fixed-size 2D array, fully stack/BSS allocated, no heap. Every load/store is bounds-checked |
| 89 | +- **Imports** — become trait bounds: `fn send<H: SocketOps>(host: &mut H, ...)`. No imports = no host parameter |
| 90 | +- **Exports** — become methods on the module struct |
| 91 | +- **Indirect calls** — safe `match` dispatch over function indices. No function pointers, no vtables |
| 92 | +- **Errors** — `WasmResult<T> = Result<T, WasmTrap>`. No panics, no unwinding |
| 93 | + |
| 94 | +### Security properties |
| 95 | + |
| 96 | +| Property | How | |
| 97 | +|----------|-----| |
| 98 | +| No buffer overflows | Every memory access is bounds-checked against `active_pages * PAGE_SIZE` | |
| 99 | +| No cross-module access | Each module owns a distinct `IsolatedMemory`; the type system prevents cross-access | |
| 100 | +| No unauthorized syscalls | Imports are trait bounds — you can't call `socket_open` unless the host implements `SocketOps` | |
| 101 | +| No ROP gadgets | No function pointers in generated code; indirect calls use static match dispatch | |
| 102 | +| No panics | All errors are `Result<T, WasmTrap>`; no unwinding | |
| 103 | + |
| 104 | +## Quick start |
| 105 | + |
| 106 | +### Install |
32 | 107 |
|
33 | 108 | ```bash |
34 | | -cargo build # build all crates |
35 | | -cargo test # run all tests |
36 | | -cargo clippy --all-targets # lint |
37 | | -cargo fmt --check # format check |
| 109 | +cargo install herkos |
38 | 110 | ``` |
39 | 111 |
|
40 | | -Run a single crate's tests: |
| 112 | +Or from source: |
41 | 113 |
|
42 | 114 | ```bash |
43 | | -cargo test -p herkos |
44 | | -cargo test -p herkos-runtime |
45 | | -cargo test -p herkos-tests |
| 115 | +git clone https://github.com/arnoox/herkos.git |
| 116 | +cd herkos |
| 117 | +cargo install --path crates/herkos |
46 | 118 | ``` |
47 | 119 |
|
48 | | -## Usage |
| 120 | +### Transpile |
49 | 121 |
|
50 | 122 | ```bash |
51 | | -cargo run -p herkos -- input.wasm --output output.rs |
| 123 | +herkos input.wasm --output output.rs |
52 | 124 | ``` |
53 | 125 |
|
| 126 | +### Use from `build.rs` (compile-time pipeline) |
| 127 | + |
| 128 | +```rust |
| 129 | +let wasm_bytes = std::fs::read("module.wasm").unwrap(); |
| 130 | +let rust_code = herkos::transpile(&wasm_bytes, &herkos::Options::default()).unwrap(); |
| 131 | +std::fs::write(out_dir.join("module.rs"), rust_code).unwrap(); |
| 132 | +``` |
| 133 | + |
| 134 | +## Project structure |
| 135 | + |
| 136 | +| Crate | Purpose | `no_std` | |
| 137 | +|-------|---------|----------| |
| 138 | +| [`herkos`](crates/herkos/) | CLI transpiler: `.wasm` binary in, Rust source out | No | |
| 139 | +| [`herkos-runtime`](crates/herkos-runtime/) | Runtime library shipped with transpiled output | Yes | |
| 140 | +| [`herkos-tests`](crates/herkos-tests/) | Integration tests + benchmarks | No | |
| 141 | + |
| 142 | +## Build and test |
| 143 | + |
| 144 | +```bash |
| 145 | +cargo build # build all crates |
| 146 | +cargo test # run all tests |
| 147 | +cargo clippy --all-targets # lint |
| 148 | +cargo fmt --check # format check |
| 149 | +cargo bench -p herkos-tests # benchmarks |
| 150 | +``` |
| 151 | + |
| 152 | +## Documentation |
| 153 | + |
| 154 | +- [Requirements](docs/REQUIREMENTS.md) — formal requirements (REQ_* IDs) |
| 155 | +- [Specification](docs/SPECIFICATION.md) — architecture, transpilation rules, memory model, security analysis |
| 156 | +- [Future work](docs/FUTURE.md) — verified backend, hybrid backend, temporal isolation |
| 157 | + |
54 | 158 | ## License |
55 | 159 |
|
56 | | -Licensed under the Apache License, Version 2.0 ([LICENSE](LICENSE)) |
| 160 | +Licensed under the Apache License, Version 2.0 ([LICENSE](LICENSE)). |
0 commit comments