hyperlight-dev / hyperlight

Hyperlight is a lightweight Virtual Machine Manager (VMM) designed to be embedded within applications. It enables safe execution of untrusted code within micro virtual machines with very low latency and minimal overhead.
Apache License 2.0
2.15k stars 68 forks source link

Hyperlight

hyperlight logo

Hyperlight is a lightweight Virtual Machine Manager (VMM) designed to be embedded within applications. It enables safe execution of untrusted code within micro virtual machines with very low latency and minimal overhead.

Note: Hyperlight is a nascent project with an evolving API and no guaranteed support. Assistance is provided on a best-effort basis by the developers.


Overview

Hyperlight is a library for creating micro virtual machines — or sandboxes — specifically optimized for securely running untrusted code with minimal impact. It supports both Windows and Linux, utilizing Windows Hypervisor Platform on Windows, and either Microsoft Hypervisor (mshv) or KVM on Linux.

These micro VMs operate without a kernel or operating system, keeping overhead low. Instead, guests are built specifically for Hyperlight using the Hyperlight Guest library, which provides a controlled set of APIs that facilitate interaction between host and guest:

By default, Hyperlight restricts guest access to a minimal API. The only host function available by default allows the guest to print messages, which are displayed on the host console or redirected to stdout, as configured. Hosts can choose to expose additional host functions, expanding the guest’s capabilities as needed.

Below is an example demonstrating the use of the Hyperlight host library in Rust to execute a simple guest application and an example of a simple guest application using the Hyperlight guest library in also written in Rust.

Host

use std::{thread, sync::{Arc, Mutex}};

use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType};
use hyperlight_host::{UninitializedSandbox, MultiUseSandbox, func::HostFunction0, sandbox_state::transition::Noop, sandbox_state::sandbox::EvolvableSandbox};

fn main() -> hyperlight_host::Result<()> {
    // Create an uninitialized sandbox with a guest binary
    let mut uninitialized_sandbox = UninitializedSandbox::new(
        hyperlight_host::GuestBinary::FilePath(hyperlight_testing::simple_guest_as_string().unwrap()),
        None, // default configuration
        None, // default run options
        None, // default host print function
    )?;

    // Register a host function
    fn sleep_5_secs() -> hyperlight_host::Result<()> {
        thread::sleep(std::time::Duration::from_secs(5));
        Ok(())
    }

    let host_function = Arc::new(Mutex::new(sleep_5_secs));

    // Registering a host function makes it available to be called by the guest
    host_function.register(&mut uninitialized_sandbox, "Sleep5Secs")?;
    // Note: This function is unused by the guest code below, it's just here for demonstration purposes

    // Initialize sandbox to be able to call host functions
    let mut multi_use_sandbox: MultiUseSandbox = uninitialized_sandbox.evolve(Noop::default())?;

    // Call a function in the guest
    let message = "Hello, World! I am executing inside of a VM :)\n".to_string();
    // in order to call a function it first must be defined in the guest and exposed so that 
    // the host can call it
    let result = multi_use_sandbox.call_guest_function_by_name(
        "PrintOutput",
        ReturnType::Int,
        Some(vec![ParameterValue::String(message.clone())]),
    );

    assert!(result.is_ok());

    Ok(())
}

Guest

#![no_std]
#![no_main]
extern crate alloc;

use alloc::string::ToString;
use alloc::vec::Vec;
use hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall;
use hyperlight_common::flatbuffer_wrappers::function_types::{
    ParameterType, ParameterValue, ReturnType,
};
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result_from_int;

use hyperlight_guest::error::{HyperlightGuestError, Result};
use hyperlight_guest::guest_function_definition::GuestFunctionDefinition;
use hyperlight_guest::guest_function_register::register_function;
use hyperlight_guest::host_function_call::{
    call_host_function, get_host_value_return_as_int,
};

fn print_output(function_call: &FunctionCall) -> Result<Vec<u8>> {
    if let ParameterValue::String(message) = function_call.parameters.clone().unwrap()[0].clone() {
        call_host_function(
            "HostPrint",
            Some(Vec::from(&[ParameterValue::String(message.to_string())])),
            ReturnType::Int,
        )?;
        let result = get_host_value_return_as_int()?;
        Ok(get_flatbuffer_result_from_int(result))
    } else {
        Err(HyperlightGuestError::new(
            ErrorCode::GuestFunctionParameterTypeMismatch,
            "Invalid parameters passed to simple_print_output".to_string(),
        ))
    }
}

#[no_mangle]
pub extern "C" fn hyperlight_main() {
    let print_output_def = GuestFunctionDefinition::new(
        "PrintOutput".to_string(),
        Vec::from(&[ParameterType::String]),
        ReturnType::Int,
        print_output as i64,
    );
    register_function(print_output_def);
}

#[no_mangle]
pub fn guest_dispatch_function(function_call: FunctionCall) -> Result<Vec<u8>> {
    let function_name = function_call.function_name.clone();
    return Err(HyperlightGuestError::new(
        ErrorCode::GuestFunctionNotFound,
        function_name,
    ));
}

For additional examples of using the Hyperlight host Rust library, see the ./src/hyperlight_host/examples directory.

For examples of guest applications, see the ./src/tests/c_guests directory for C guests and the ./src/tests/rust_guests directory for Rust guests.

Note: Hyperlight guests can be written using the Hyperlight Rust or C Guest libraries.

Repository Structure

Try it yourself!

You can run Hyperlight on:

After having an environment with a hypervisor setup, running the example has the following pre-requisites:

  1. On Linux or WSL, you'll most likely need build essential. For Ubuntu, run sudo apt install build-essential. For Azure Linux, run sudo dnf install build-essential.

  2. Rust. Install toolchain v1.78.0 or later.

    Also, install the x86_64-pc-windows-msvc and x86_64-unknown-none targets, these are needed to build the test guest binaries. (Note: install both targets on either Linux or Windows: Hyperlight can load ELF or PE files on either OS, and the tests/examples are built for both):

    rustup target add x86_64-unknown-none
    rustup target add x86_64-pc-windows-msvc
  3. just. cargo install just .

  4. clang and LLVM.

    • On Ubuntu, run:

      wget https://apt.llvm.org/llvm.sh
      chmod +x ./llvm.sh
      sudo ./llvm.sh 17 all
      sudo ln -s /usr/lib/llvm-17/bin/clang-cl /usr/bin/clang-cl
      sudo ln -s /usr/lib/llvm-17/bin/llvm-lib /usr/bin/llvm-lib
      sudo ln -s /usr/lib/llvm-17/bin/lld-link /usr/bin/lld-link
      sudo ln -s /usr/lib/llvm-17/bin/llvm-ml /usr/bin/llvm-ml
      sudo ln -s /usr/lib/llvm-17/bin/ld.lld /usr/bin/ld.lld
      sudo ln -s /usr/lib/llvm-17/bin/clang /usr/bin/clang
    • On Windows, see this.

    • On Azure Linux, run:

      sudo dnf remove clang -y || true
      sudo dnf install clang17 -y
      sudo dnf install clang17-tools-extra -y

Then, we are ready to build and run the example:

just build  # build the Hyperlight library
just rg     # build the rust test guest binaries
cargo run --example hello-world

If all worked as expected, you should see the following message in your console:

Hello, World! I am executing inside of a VM :)

If you get the error Error: NoHypervisorFound and KVM or mshv is set up then this may be a permissions issue. In bash, you can use ls -l /dev/kvm or ls -l /dev/mshv to check which group owns that device and then groups to make sure your user is a member of that group.

For more details on how to verify that KVM is correctly installed and permissions are correct, follow the guide here.

Or you can use a codespace

Open in GitHub Codespaces

Contributing to Hyperlight

If you are interested in contributing to Hyperlight, running the entire test-suite is a good way to get started. To do so, on your console, run the following commands:

just guests  # build the c and rust test guests
just build  # build the Hyperlight library
just test # runs the tests

Also , please review the CONTRIBUTING.md file for more information on how to contribute to Hyperlight.

Note: For general Hyperlight development, you may also need flatc (Flatbuffer compiler): for instructions, see here.

More Information

For more information, please refer to our compilation of documents in the docs/ directory.

Code of Conduct

See the Code of Conduct.