hyperlight-host 0.17.0

A lightweight Virtual Machine Manager that can be hosted in an application to safely run untrusted or code within a VM partition with very low latency and overhead.
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 The Hyperlight Authors.
use rand::RngExt;
use tracing::{Span, instrument};

use super::SandboxConfiguration;
#[cfg(any(crashdump, gdb))]
use super::uninitialized::SandboxRuntimeConfig;
use crate::hypervisor::hyperlight_vm::{HyperlightVm, HyperlightVmError};
use crate::mem::exe::LoadInfo;
use crate::mem::mgr::SandboxMemoryManager;
use crate::mem::ptr::RawPtr;
use crate::mem::shared_mem::GuestSharedMemory;
#[cfg(gdb)]
use crate::sandbox::config::DebugInfo;
#[cfg(feature = "mem_profile")]
use crate::sandbox::trace::MemTraceInfo;
#[cfg(target_os = "linux")]
use crate::signal_handlers::setup_signal_handlers;
use crate::{MultiUseSandbox, Result, UninitializedSandbox};

#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
pub(super) fn evolve_impl_multi_use(u_sbox: UninitializedSandbox) -> Result<MultiUseSandbox> {
    let (mut hshm, gshm) = u_sbox.mgr.build()?;

    // Get the host page size. Narrowed to u32 because the guest ABI
    // passes it via a 32-bit register (rdx), but widened back to usize
    // for host-side alignment calculations in set_up_hypervisor_partition.
    let page_size = u32::try_from(page_size::get())?;

    let mut vm = set_up_hypervisor_partition(
        gshm,
        &u_sbox.config,
        u_sbox.stack_top_gva,
        page_size as usize,
        #[cfg(any(crashdump, gdb))]
        u_sbox.rt_cfg,
        u_sbox.load_info,
    )?;

    let seed = {
        let mut rng = rand::rng();
        rng.random::<u64>()
    };
    let peb_addr = {
        let peb_u64 = u64::try_from(hshm.layout.peb_address())?;
        RawPtr::from(peb_u64)
    };

    #[cfg(target_os = "linux")]
    setup_signal_handlers(&u_sbox.config)?;

    // Apply any file mappings that were prepared before evolve.
    // This must happen before vm.initialise() so the mapped data is
    // visible to the guest's init code.
    //
    // Each PreparedFileMapping is marked consumed immediately after
    // its map_region succeeds — on Windows, WhpVm::map_memory copies
    // the handle into its own cleanup list, so we must not let
    // PreparedFileMapping::drop also release it (double-close).
    // Unconsumed mappings (those after a failed map_region) are
    // cleaned up by Drop when `pending` goes out of scope.
    let pending = u_sbox.pending_file_mappings;
    for mut prepared in pending {
        let region = prepared.to_memory_region()?;
        unsafe { vm.map_region(&region) }.map_err(|e| {
            crate::HyperlightError::HyperlightVmError(HyperlightVmError::MapRegion(e))
        })?;

        // Mark consumed immediately after map_region succeeds.
        // On Windows, WhpVm::map_memory copies the file mapping handle
        // into its own `file_mappings` vec for cleanup on drop. If we
        // deferred mark_consumed(), both PreparedFileMapping::drop and
        // WhpVm::drop would release the same handle — a double-close.
        // For linux see https://github.com/hyperlight-dev/hyperlight/issues/1290.
        prepared.mark_consumed();
    }

    vm.initialise(
        peb_addr,
        seed,
        &mut hshm,
        &u_sbox.host_funcs,
        u_sbox.max_guest_log_level,
    )
    .map_err(HyperlightVmError::Initialize)?;

    Ok(MultiUseSandbox::from_uninit(u_sbox.host_funcs, hshm, vm))
}

pub(crate) fn set_up_hypervisor_partition(
    mgr: SandboxMemoryManager<GuestSharedMemory>,
    #[cfg_attr(target_os = "windows", allow(unused_variables))] config: &SandboxConfiguration,
    stack_top_gva: u64,
    page_size: usize,
    #[cfg(any(crashdump, gdb))] rt_cfg: SandboxRuntimeConfig,
    _load_info: LoadInfo,
) -> Result<HyperlightVm> {
    // Create gdb thread if gdb is enabled and the configuration is provided
    #[cfg(gdb)]
    let gdb_conn = if let Some(DebugInfo { port }) = rt_cfg.debug_info {
        use crate::hypervisor::gdb::create_gdb_thread;

        let gdb_conn = create_gdb_thread(port);

        // in case the gdb thread creation fails, we still want to continue
        // without gdb
        match gdb_conn {
            Ok(gdb_conn) => Some(gdb_conn),
            Err(e) => {
                tracing::error!("Could not create gdb connection: {:#}", e);

                None
            }
        }
    } else {
        None
    };

    #[cfg(feature = "mem_profile")]
    let trace_info = MemTraceInfo::new(_load_info.info)?;

    // Store the original entry point address in the runtime config for core dumps.
    // This is needed because `next_action` transitions from `Initialise(addr)` to
    // `Call(dispatch_addr)` after guest initialisation, losing the original value
    // that GDB needs to compute the PIE binary's load offset. The manager carries
    // it across that transition (and across snapshot save/restore), so it is
    // correct for both the evolve path and `MultiUseSandbox::from_snapshot`.
    #[cfg(crashdump)]
    let rt_cfg = {
        let mut rt_cfg = rt_cfg;
        if mgr.original_entrypoint != 0 {
            rt_cfg.entry_point = Some(mgr.original_entrypoint);
        }
        rt_cfg
    };

    Ok(HyperlightVm::new(
        mgr.shared_mem,
        mgr.scratch_mem,
        mgr.layout.get_pt_base_gpa(),
        mgr.next_action,
        stack_top_gva,
        page_size,
        config,
        #[cfg(gdb)]
        gdb_conn,
        #[cfg(crashdump)]
        rt_cfg,
        #[cfg(feature = "mem_profile")]
        trace_info,
    )
    .map_err(HyperlightVmError::Create)?)
}

#[cfg(test)]
mod tests {
    use hyperlight_testing::simple_guest_as_pathbuf;

    use super::evolve_impl_multi_use;
    use crate::UninitializedSandbox;
    use crate::sandbox::uninitialized::GuestBinary;

    #[test]
    fn test_evolve() {
        let guest_bin_paths = vec![simple_guest_as_pathbuf()];
        for guest_bin_path in guest_bin_paths {
            let u_sbox =
                UninitializedSandbox::new(GuestBinary::FilePath(guest_bin_path), None).unwrap();
            evolve_impl_multi_use(u_sbox).unwrap();
        }
    }
}