Gunnar

nordisk/heimdall

crates/heimdall-wasm/src/limits.rs

// SPDX-License-Identifier: AGPL-3.0-only
//! The limits a guest runs under (spec §6). Every one of them fails closed:
//! hitting it ends the run as `guest trapped (<which limit>)`.

use std::path::PathBuf;
use std::time::Duration;

/// Per-run limits. Each run gets a fresh store, so these may differ per job.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RunLimits {
    /// wasmtime fuel (roughly one unit per wasm instruction).
    pub fuel: u64,
    /// Wall-clock budget, enforced by epoch interruption.
    pub timeout: Duration,
    /// Linear memory over all of the guest's memories together, in bytes.
    pub memory_bytes: usize,
    /// Table elements over all of the guest's tables together.
    pub table_elements: usize,
    /// Most core instances a guest may create. A component built by
    /// wit-component with the emit-file import holds three (the guest's
    /// module plus the canonical-ABI shim and fixup modules).
    pub instances: usize,
    /// Most tables a guest may create.
    pub tables: usize,
    /// Most linear memories a guest may create.
    pub memories: usize,
    /// Most files one run may emit.
    pub max_files: usize,
    /// Largest single emitted file, in bytes.
    pub max_file_bytes: usize,
    /// Largest sum of all emitted files, in bytes.
    pub max_emit_bytes: usize,
    /// Largest `note` or `reason` string the guest may return, in bytes.
    pub max_text_bytes: usize,
}

impl Default for RunLimits {
    fn default() -> Self {
        RunLimits {
            fuel: 2_000_000_000,
            timeout: Duration::from_secs(10),
            memory_bytes: 256 << 20,
            table_elements: 100_000,
            instances: 16,
            tables: 16,
            memories: 4,
            max_files: 256,
            max_file_bytes: 64 << 20,
            max_emit_bytes: 128 << 20,
            max_text_bytes: 64 << 10,
        }
    }
}

impl From<&heimdall_core::Limits> for RunLimits {
    /// The job policy's limits, as the wasm host enforces them. `fuel: None`
    /// in the policy means "no fuel metering": the run then has `u64::MAX`
    /// fuel and only the wall clock stops it. The note/reason cap is not in
    /// the policy and keeps its default.
    fn from(l: &heimdall_core::Limits) -> Self {
        let sz = |v: u64| usize::try_from(v).unwrap_or(usize::MAX);
        RunLimits {
            fuel: l.fuel.unwrap_or(u64::MAX),
            timeout: l.wall,
            memory_bytes: sz(l.memory_bytes),
            table_elements: l.table_elements as usize,
            instances: l.instances as usize,
            tables: l.tables as usize,
            memories: l.memories as usize,
            max_files: l.max_output_files as usize,
            max_file_bytes: sz(l.max_output_file_bytes),
            max_emit_bytes: sz(l.max_output_total_bytes),
            ..RunLimits::default()
        }
    }
}

/// Host-wide settings: these shape the engine, so they hold for every guest
/// the host loads.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HostConfig {
    /// DER root certificates a guest's `.wasm.p7s` must chain to. Never empty:
    /// Heimdall has no unsigned mode.
    pub trust_roots: Vec<PathBuf>,
    /// Most native stack wasm may use, in bytes (deep recursion traps here).
    pub stack_bytes: usize,
    /// Largest module file the host reads at all, in bytes.
    pub max_module_bytes: u64,
    /// Epoch tick: the resolution of the wall-clock timeout.
    pub epoch_tick: Duration,
}

impl HostConfig {
    /// Defaults with the given trust roots.
    pub fn new(trust_roots: impl IntoIterator<Item = PathBuf>) -> Self {
        HostConfig {
            trust_roots: trust_roots.into_iter().collect(),
            stack_bytes: 512 << 10,
            max_module_bytes: 64 << 20,
            epoch_tick: Duration::from_millis(10),
        }
    }

    /// Defaults with the given trust roots, and the stack limit of a job
    /// policy (the stack limit is engine-wide, so it is set per host).
    pub fn for_policy(
        trust_roots: impl IntoIterator<Item = PathBuf>,
        limits: &heimdall_core::Limits,
    ) -> Self {
        HostConfig {
            stack_bytes: usize::try_from(limits.stack_bytes).unwrap_or(usize::MAX),
            ..Self::new(trust_roots)
        }
    }
}