WASM sandbox
Two reasons to run the scanner inside a WASM sandbox:
- You don’t trust the signatures: third-party
.ndbsets, community YARA rules, anything you did not build yourself. - You don’t trust exav. It is a young parser of hostile input, and
#![forbid(unsafe_code)]is not a boundary the kernel or a runtime enforces. The sandbox needs no assumption that exav’s own code is correct.
The same exav-core engine compiles to wasm32-wasip1 and runs under any WASI
runtime (wasmtime, wasmer, wazero): you bring your own runtime, and a bug inside
exav costs a crashed sandbox rather than a compromised host.
Quick start
Section titled “Quick start”-
Download
exav-wasm-<tag>-wasm32-wasip1.wasmfrom a release and rename itexav.wasm, or build it from a clone of the repository (Rust 1.92 or newer):Terminal window git clone https://github.com/sylvinus/exav && cd exavrustup target add wasm32-wasip1cargo build --release --target wasm32-wasip1 -p exav-core --features wasi-bincp target/wasm32-wasip1/release/exav-wasm.wasm exav.wasm -
Optionally strip debug names to make it smaller:
Terminal window cargo install wasm-tools # if not installedwasm-tools strip -a exav.wasm -o exav.min.wasm && mv exav.min.wasm exav.wasm -
Scan a file under wasmtime, with the signatures mounted at
/dband the files to scan at/files:Terminal window wasmtime \--dir /path/to/sigs::/db \--dir /path/to/files::/files \exav.wasm \/db /files/malware.exe
The first argument is the database: a directory of signature files, or a
prebuilt .exavdb file. One that loads no real
signatures is refused, as the native exav refuses it. The other arguments are
files; a directory is not walked. Mount only the directories the module needs.
Output
Section titled “Output”Each file gives one JSON object on stdout, in argument order: the ScanReport
plus "file", or "file" and "error" for a file that could not be read.
Progress goes to stderr:
wasmtime --dir ./sigs::/db --dir ./in::/in exav.wasm /db /in/a.exe /in/b.zip /in/gone \ 2>/dev/null | jq -c .{"file":"/in/a.exe","findings":[{"detail":"PE","label":"type"}],"verdict":"Clean"}{"file":"/in/b.zip","findings":[{"detail":"ZIP","label":"type"}],"verdict":{"Infected":{"method":"Pattern","offset":4096,"signature":"Win.Trojan.Agent-1234"}}}{"error":"No such file or directory (os error 44)","file":"/in/gone"}verdict is "Clean" or an object named after the variant: Infected as
above, or LimitsExceeded, Unscannable or PasswordProtected with a
reason, such as {"Unscannable":{"reason":"..."}}. The exit code follows the
native exav’s precedence.
What the sandbox changes
Section titled “What the sandbox changes”Loading third-party signatures makes the signature parser part of your attack
surface: a malicious .ndb could aim for a parser bug, excessive allocation, or
an escape from the process. Natively, any of these reaches the host. In a WASM
sandbox:
Native exav |
WASM sandbox | |
|---|---|---|
| Signature trust | full host access | a memory-limited WASM instance |
| Runtime | exav itself | wasmtime or another WASI runtime |
| What a bug can reach | the host | the module’s own memory and mounted directories |
| Speed | native | slower |
- Memory is bounded: the module cannot reach memory outside its own linear memory.
- No ambient system access: WASI limits file access to the mounted paths, and the module has no network.
- Instruction limits: wasmtime can give the module a budget of executed instructions (“fuel”) and stop it when that runs out.
- Panic isolation: a panic becomes a wasmtime trap; the host is unaffected.
- No custom ABI: the module is a standard WASI command, so the host is the runtime itself, not code exav wrote.
A wasm32 module may grow to 4 GiB unless the runtime caps it. With wasmtime,
set both limits on the command line (wasmtime run -W help lists them):
wasmtime run -W max-memory-size=3221225472 -W fuel=100000000000 \ --dir ./sigs::/db --dir ./in::/in exav.wasm /db /in/a.exeLimitations
Section titled “Limitations”- No daemon mode: each run loads the database from scratch. For
high-throughput scanning use a native
exav --listen. - No stdin: files must be on a mounted filesystem.
- No HTTP(S) scanning: the module cannot make network requests.
- No flags: every scan uses the defaults. There is no
--detect(PUA only from a.exavdbbuilt with it),--all-matches,--passwords(only.pwdbpasswords are tried) or--partial-as, and YARA’s filename variables are undefined. - Memory: the database and each scanned file, read whole, share the 4 GiB
wasm32address space. Loading raw signatures costs far more than a prebuilt database built natively, so give it one.
Alternatives
Section titled “Alternatives”- Native
exav: full speed and features, with host access. - A container: similar isolation, but a full OS image rather than one
.wasmfile.