Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Prerelease] - Unreleased

### Added
* Add `LibcRngReseedPolicy` and
`SandboxConfiguration::set_libc_rng_reseed_policy` for configuring the guest
libc PRNG.

### Changed
* **Breaking:** Guest MSR state is now saved and restored across snapshots.
Expand Down
13 changes: 9 additions & 4 deletions docs/snapshot-oci-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,20 @@ Three blob kinds per tag:
* **manifest** (`application/vnd.oci.image.manifest.v1+json`). Tiny JSON
pointer record selected via `index.json`. References one config and
one layer by digest.
* **config** (`application/vnd.hyperlight.snapshot.config.v1+json`). The
* **config** (`application/vnd.hyperlight.snapshot.config.v2+json`). The
snapshot descriptor: arch, hypervisor, CPU vendor, ABI version,
resume address and captured registers, memory layout, registered
host functions, snapshot generation counter. Loaded eagerly and
fully parsed.
resume address and captured registers, libc PRNG reseed policy,
memory layout, registered host functions, snapshot generation counter.
Loaded eagerly and fully parsed.
* **layer / memory** (`application/vnd.hyperlight.snapshot.memory.v1`).
The raw guest memory image, exactly `memory_size` bytes. mmap'd on
restore.

The libc PRNG setting applies only to `rand()` and `random()` from
Hyperlight's bundled libc. Rust and custom RNG implementations must manage
their own state across snapshot restores. This setting is not a source of
cryptographic entropy.

Blob filenames are the sha256 of the blob bytes, so identical blobs
across tags are stored once.

Expand Down
4 changes: 2 additions & 2 deletions docs/snapshot-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ A snapshot carries three independently evolvable version markers:
`MT_SNAPSHOT_CURRENT`. This is the on-wire format of the snapshot
blob: framing, section ordering, alignment, dirty/zero-page elision,
anything about how the bytes are packed inside the OCI layer.
* **Config schema**, `MT_CONFIG_V1`
(`application/vnd.hyperlight.snapshot.config.v1+json`), aliased as
* **Config schema**, `MT_CONFIG_V2`
(`application/vnd.hyperlight.snapshot.config.v2+json`), aliased as
`MT_CONFIG_CURRENT`. This is the JSON shape of the config blob:
field names, types, required vs optional, the descriptors the loader
needs in order to reconstruct the sandbox (memory sizes, buffer
Expand Down
1 change: 1 addition & 0 deletions src/hyperlight_common/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub const SCRATCH_TOP_SIZE_OFFSET: u64 = 0x08;
pub const SCRATCH_TOP_ALLOCATOR_OFFSET: u64 = 0x10;
pub const SCRATCH_TOP_SNAPSHOT_PT_GPA_BASE_OFFSET: u64 = 0x18;
pub const SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET: u64 = 0x20;
pub const SCRATCH_TOP_LIBC_RNG_SEED_OFFSET: u64 = 0x28;
pub const SCRATCH_TOP_EXN_STACK_OFFSET: u64 = 0x30;

pub fn scratch_base_gpa(size: usize) -> u64 {
Expand Down
4 changes: 4 additions & 0 deletions src/hyperlight_guest/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ pub fn snapshot_generation_gva() -> *mut u64 {
use hyperlight_common::layout::{SCRATCH_TOP_GVA, SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET};
(SCRATCH_TOP_GVA as u64 - SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET + 1) as *mut u64
}
pub fn libc_rng_seed_gva() -> *mut u64 {
use hyperlight_common::layout::{SCRATCH_TOP_GVA, SCRATCH_TOP_LIBC_RNG_SEED_OFFSET};
(SCRATCH_TOP_GVA as u64 - SCRATCH_TOP_LIBC_RNG_SEED_OFFSET + 1) as *mut u64
}
pub use arch::{scratch_base_gpa, scratch_base_gva};
4 changes: 4 additions & 0 deletions src/hyperlight_guest_bin/src/guest_function/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result<Vec<u8>
}

pub(crate) fn internal_dispatch_function() {
// Reseed the libc PRNG if requested by the host.
#[cfg(feature = "libc")]
crate::refresh_libc_rng();

// Read the current TSC to report it to the host with the spans/events
// This helps calculating the timestamps relative to the guest call
#[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
Expand Down
15 changes: 13 additions & 2 deletions src/hyperlight_guest_bin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,18 @@ unsafe extern "C" {
fn srand(seed: u32);
}

#[cfg(feature = "libc")]
pub(crate) fn refresh_libc_rng() {
let seed_ptr = hyperlight_guest::layout::libc_rng_seed_gva();
let request = unsafe { seed_ptr.read_volatile() };
if request >> 32 != 0 {
unsafe {
seed_ptr.write_volatile(0);
srand(request as u32);
}
}
}

#[tracing::instrument(skip_all, parent = tracing::Span::current(), level= "Trace")]
extern "C" fn hyperlight_main_default() {
// no-op
Expand Down Expand Up @@ -254,8 +266,7 @@ pub(crate) extern "C" fn generic_init(

#[cfg(feature = "libc")]
unsafe {
let srand_seed = (((peb_address << 8) ^ (_seed >> 4)) >> 32) as u32;
srand(srand_seed);
srand(_seed as u32);
}

unsafe {
Expand Down
12 changes: 12 additions & 0 deletions src/hyperlight_host/src/mem/mgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ where
mapped_regions: Vec<MemoryRegion>,
root_pt_gpas: &[u64],
rsp_gva: u64,
libc_rng_reseed_policy: crate::sandbox::LibcRngReseedPolicy,
sregs: CommonSpecialRegisters,
#[cfg(target_arch = "x86_64")] msrs: Vec<crate::hypervisor::regs::MsrEntry>,
next_action: NextAction,
Expand All @@ -318,6 +319,7 @@ where
mapped_regions,
root_pt_gpas,
rsp_gva,
libc_rng_reseed_policy,
sregs,
#[cfg(target_arch = "x86_64")]
msrs,
Expand Down Expand Up @@ -530,6 +532,15 @@ impl SandboxMemoryManager<HostSharedMemory> {
self.scratch_mem.write::<u64>(base_offset, value)
}

pub(crate) fn request_libc_rng_reseed(&mut self, seed: u32) -> Result<()> {
// Zero means no request. The upper half marks a pending request, and
// the lower half contains the complete u32 seed.
self.update_scratch_bookkeeping_item(
hyperlight_common::layout::SCRATCH_TOP_LIBC_RNG_SEED_OFFSET,
(1_u64 << 32) | u64::from(seed),
)
}

fn update_scratch_bookkeeping(&mut self) -> Result<()> {
use hyperlight_common::layout::*;
let scratch_size = self.scratch_mem.mem_size();
Expand All @@ -554,6 +565,7 @@ impl SandboxMemoryManager<HostSharedMemory> {
SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET,
self.snapshot_count,
)?;
self.update_scratch_bookkeeping_item(SCRATCH_TOP_LIBC_RNG_SEED_OFFSET, 0)?;

// Initialise the guest input and output data buffers in
// scratch memory. TODO: remove the need for this.
Expand Down
61 changes: 60 additions & 1 deletion src/hyperlight_host/src/sandbox/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,34 @@ pub enum GuestMsrError {
},
}

/// Controls how Hyperlight's guest libc PRNG is seeded during initialization
/// and reseeded after every snapshot restore.
///
/// This controls `rand()` and `random()` when the guest uses Hyperlight's
/// bundled libc and guest runtime. It has no effect on Rust `rand`,
/// `getrandom`, custom PRNGs, or hardware RNG instructions. It does not
/// provide cryptographic entropy. Other runtimes must manage their own RNG
/// state across snapshot restores.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u8)]
pub enum LibcRngReseedPolicy {
/// Generate a new seed on the host during initialization and after every
/// snapshot restore.
#[default]
Random,
/// Use the supplied seed.
Fixed(u32),
}

impl LibcRngReseedPolicy {
pub(crate) fn resolve(self) -> u32 {
match self {
Self::Random => rand::random(),
Self::Fixed(seed) => seed,
}
}
}

/// The complete set of configuration needed to create a Sandbox
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(C)]
Expand Down Expand Up @@ -92,6 +120,8 @@ pub struct SandboxConfiguration {
/// Number of valid entries in `guest_msrs`.
#[cfg(target_arch = "x86_64")]
guest_msrs_count: usize,
/// Guest libc PRNG reseeding behavior.
libc_rng_reseed_policy: LibcRngReseedPolicy,
}

impl SandboxConfiguration {
Expand Down Expand Up @@ -137,6 +167,7 @@ impl SandboxConfiguration {
scratch_size,
interrupt_retry_delay,
interrupt_vcpu_sigrtmin_offset,
libc_rng_reseed_policy: LibcRngReseedPolicy::default(),
#[cfg(gdb)]
guest_debug_info,
#[cfg(crashdump)]
Expand Down Expand Up @@ -299,6 +330,19 @@ impl SandboxConfiguration {
self.scratch_size = scratch_size;
}

/// Set the guest libc PRNG reseed policy.
///
/// Snapshots retain this setting. Restoring a snapshot uses its retained
/// setting, not the destination sandbox configuration. The default is
/// [`LibcRngReseedPolicy::Random`]. See [`LibcRngReseedPolicy`] for its scope.
pub fn set_libc_rng_reseed_policy(&mut self, policy: LibcRngReseedPolicy) {
self.libc_rng_reseed_policy = policy;
}

pub(crate) fn get_libc_rng_reseed_policy(&self) -> LibcRngReseedPolicy {
self.libc_rng_reseed_policy
}

#[cfg(crashdump)]
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn get_guest_core_dump(&self) -> bool {
Expand Down Expand Up @@ -347,7 +391,22 @@ impl Default for SandboxConfiguration {
mod tests {
#[cfg(target_arch = "x86_64")]
use super::GuestMsrError;
use super::SandboxConfiguration;
use super::{LibcRngReseedPolicy, SandboxConfiguration};

#[test]
fn libc_rng_configuration_defaults_and_setters() {
let mut config = SandboxConfiguration::default();
assert_eq!(
config.get_libc_rng_reseed_policy(),
LibcRngReseedPolicy::Random
);

config.set_libc_rng_reseed_policy(LibcRngReseedPolicy::Fixed(0));
assert_eq!(
config.get_libc_rng_reseed_policy(),
LibcRngReseedPolicy::Fixed(0)
);
}

#[test]
#[cfg(target_arch = "x86_64")]
Expand Down
27 changes: 19 additions & 8 deletions src/hyperlight_host/src/sandbox/initialized_multi_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ pub struct MultiUseSandbox {
/// Given (snapshot_mem, scratch_mem, cr3), returns a list of root GPAs.
/// If not set, only CR3 is used as the single root.
pt_root_finder: Option<PtRootFinder>,
libc_rng_reseed_policy: crate::sandbox::LibcRngReseedPolicy,
}

/// Callback for discovering page table roots from guest memory.
Expand All @@ -118,6 +119,7 @@ impl MultiUseSandbox {
host_funcs: Arc<Mutex<FunctionRegistry>>,
mgr: SandboxMemoryManager<HostSharedMemory>,
vm: HyperlightVm,
libc_rng_reseed_policy: crate::sandbox::LibcRngReseedPolicy,
) -> MultiUseSandbox {
Self {
poisoned: false,
Expand All @@ -126,6 +128,7 @@ impl MultiUseSandbox {
vm,
snapshot: None,
pt_root_finder: None,
libc_rng_reseed_policy,
}
}

Expand Down Expand Up @@ -205,8 +208,6 @@ impl MultiUseSandbox {
host_funcs: crate::HostFunctions,
config: Option<crate::sandbox::SandboxConfiguration>,
) -> Result<Self> {
use rand::RngExt;

use crate::mem::ptr::RawPtr;
use crate::sandbox::uninitialized_evolve::set_up_hypervisor_partition;

Expand Down Expand Up @@ -270,16 +271,18 @@ impl MultiUseSandbox {
load_info,
)?;

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

// noop for NextAction::Call
vm.initialise(peb_addr, seed, &mut hshm, &host_funcs, None)
vm.initialise(peb_addr, u64::from(seed), &mut hshm, &host_funcs, None)
.map_err(crate::hypervisor::hyperlight_vm::HyperlightVmError::Initialize)?;

if matches!(snapshot.next_action(), super::snapshot::NextAction::Call(_)) {
hshm.request_libc_rng_reseed(seed)?;
}

// If the snapshot was taken from an already-initialized guest
// (NextAction::Call), apply the captured special registers so
// the guest resumes in the correct CPU state.
Expand All @@ -303,7 +306,7 @@ impl MultiUseSandbox {
})?;
}

let sbox = MultiUseSandbox::from_uninit(host_funcs, hshm, vm);
let sbox = MultiUseSandbox::from_uninit(host_funcs, hshm, vm, libc_rng_reseed_policy);
Ok(sbox)
}

Expand Down Expand Up @@ -392,6 +395,7 @@ impl MultiUseSandbox {
mapped_regions_vec,
&root_pt_gpas,
stack_top_gpa,
self.libc_rng_reseed_policy,
sregs,
#[cfg(target_arch = "x86_64")]
msrs,
Expand Down Expand Up @@ -425,6 +429,10 @@ impl MultiUseSandbox {
/// declare every MSR the snapshot saved, or the restore poisons with an MSR
/// mismatch.
///
/// Restore replaces the sandbox's configured
/// [`LibcRngReseedPolicy`](crate::sandbox::LibcRngReseedPolicy) with the
/// snapshot's policy.
///
/// ## Poison State Recovery
///
/// This method automatically clears any poison state when successful. This is safe because:
Expand Down Expand Up @@ -528,6 +536,8 @@ impl MultiUseSandbox {
}

let (gsnapshot, gscratch) = self.mem_mgr.restore_snapshot(&snapshot)?;
self.mem_mgr
.request_libc_rng_reseed(snapshot.libc_rng_reseed_policy().resolve())?;
if let Some(gsnapshot) = gsnapshot {
self.vm
.update_snapshot_mapping(gsnapshot)
Expand Down Expand Up @@ -574,6 +584,7 @@ impl MultiUseSandbox {
}

// The restored snapshot is now our most current snapshot
self.libc_rng_reseed_policy = snapshot.libc_rng_reseed_policy();
self.snapshot = Some(snapshot.clone());

// Clear poison state when successfully restoring from snapshot.
Expand Down
4 changes: 2 additions & 2 deletions src/hyperlight_host/src/sandbox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ pub(crate) mod trace;

/// Trait used by the macros to paper over the differences between hyperlight and hyperlight-wasm
pub use callable::Callable;
/// Re-export for `SandboxConfiguration` type
pub use config::SandboxConfiguration;
/// Re-export for sandbox configuration types
pub use config::{LibcRngReseedPolicy, SandboxConfiguration};
/// Re-export for the `MultiUseSandbox` type
pub use initialized_multi_use::{MultiUseSandbox, PtRootFinder};
/// Re-export for `GuestBinary` type
Expand Down
Loading
Loading