feat: add wasmtime

This commit is contained in:
Kristofers Solo 2025-09-28 19:19:52 +03:00
parent 51f07beb4d
commit d28b182422
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
3 changed files with 1366 additions and 92 deletions

1371
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,17 +10,18 @@ keywords = ["wasm", "sandbox", "capabilities", "plugins", "traces"]
categories = ["cryptography::randomness", "encoding"]
[dependencies]
anyhow = "1.0"
base64 = "0.22"
color-eyre = "0.6"
ed25519-dalek = { version = "2.2", features = ["rand_core"] }
glob = "0.3"
rand = "0.8" # ed25519-dalek depends on rand_core v0.6
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.10.9"
sha2 = "0.10"
thiserror = "2.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
wasmtime = "37.0"
[dev-dependencies]
claims = "0.8"

View File

@ -9,9 +9,10 @@ use ed25519_dalek::{PUBLIC_KEY_LENGTH, SigningKey, ed25519::signature::SignerMut
use glob::Pattern;
use rand::{Rng, SeedableRng, rngs::StdRng};
use sha2::{Digest, Sha256};
use std::path::Path;
use std::{path::Path, str::from_utf8};
use thiserror::Error;
use tracing::Level;
use wasmtime::{Caller, Extern, Linker, Trap};
#[derive(Debug)]
pub struct HostState {
@ -40,6 +41,15 @@ pub enum CapError {
InvalidPath,
}
/// Host-visible status codes returned from host functions.
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HostStatus {
Allowed = 0,
Denied = 1,
Error = -1,
}
impl HostState {
#[inline]
#[must_use]
@ -234,3 +244,73 @@ impl HostState {
pub fn init_tracing() {
tracing_subscriber::fmt().with_max_level(Level::INFO).init();
}
/// Register host functions for Wasmtime on the provided linker.
///
/// Exposes:
/// - `host::read_file(ptr: i32, len: i32) -> Result<i32, Trap>`
/// - `host::status_allowed() -> i32`
/// - `host::status_denied() -> i32`
/// - `host::status_error() -> i32`
///
/// # Errors
///
/// `read_file` returns `Ok(HostStatus::Allowed/Denied)` for normal outcomes,
/// and Err(Trap) for exceptional errors (OOB, invalid pointer, invalid UTF-8).
pub fn add_wasm_linker_funcs(linker: &mut Linker<HostState>) -> anyhow::Result<()> {
linker.func_wrap(
"host",
"read_file",
|mut caller: Caller<'_, HostState>, ptr: i32, len: i32| -> anyhow::Result<i32> {
let memory = caller
.get_export("memory")
.and_then(Extern::into_memory)
.ok_or(Trap::MemoryOutOfBounds)?;
let start = usize::try_from(ptr).map_err(|_| Trap::BadConversionToInteger)?;
let read_len = usize::try_from(len).map_err(|_| Trap::BadConversionToInteger)?;
let data = memory.data(&caller);
if start
.checked_add(read_len)
.is_none_or(|end| end > data.len())
{
return Err(Trap::MemoryOutOfBounds.into());
}
let bytes = &data[start..start + read_len];
let path_str = from_utf8(bytes)
.map_err(|_| Trap::BadConversionToInteger)?
.to_string();
match caller.data_mut().execute_plugin(path_str) {
Ok(true) => Ok(HostStatus::Allowed.into()),
Ok(false) => Ok(HostStatus::Denied.into()),
Err(err) => match err {
CapError::GlobMismatch => Ok(HostStatus::Denied.into()),
CapError::NoFsCapability | CapError::NoReadPatterns => {
Ok(HostStatus::Denied.into())
}
CapError::InvalidPath => Err(Trap::MemoryOutOfBounds.into()),
},
}
},
)?;
linker.func_wrap("host", "status_allowed", || -> i32 {
HostStatus::Allowed.into()
})?;
linker.func_wrap("host", "status_denied", || -> i32 {
HostStatus::Denied.into()
})?;
linker.func_wrap("host", "status_error", || -> i32 {
HostStatus::Error.into()
})?;
Ok(())
}
impl From<HostStatus> for i32 {
fn from(value: HostStatus) -> Self {
value as Self
}
}