diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index 84a112a05..5ef2a0581 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -133,6 +133,7 @@ trace_guest = ["dep:opentelemetry", "dep:tracing-opentelemetry", "dep:hyperlight mem_profile = [ "trace_guest", "dep:framehop", "dep:fallible-iterator", "hyperlight-common/mem_profile" ] kvm = ["dep:kvm-bindings", "dep:kvm-ioctls"] mshv3 = ["dep:mshv-bindings", "dep:mshv-ioctls"] +guest-semaphore = [] # This enables easy debug in the guest gdb = ["dep:gdbstub", "dep:gdbstub_arch"] fuzzing = ["hyperlight-common/fuzzing"] diff --git a/src/hyperlight_host/src/lib.rs b/src/hyperlight_host/src/lib.rs index 2b3e7168f..55415ecd5 100644 --- a/src/hyperlight_host/src/lib.rs +++ b/src/hyperlight_host/src/lib.rs @@ -94,6 +94,9 @@ pub use sandbox::MultiUseSandbox; pub use sandbox::UninitializedSandbox; /// The re-export for the `GuestBinary` type pub use sandbox::uninitialized::GuestBinary; +/// The re-export for the `GuestSemaphore` type +#[cfg(feature = "guest-semaphore")] +pub use sandbox::uninitialized::GuestSemaphore; /// The universal `Result` type used throughout the Hyperlight codebase. pub type Result = core::result::Result; diff --git a/src/hyperlight_host/src/sandbox/uninitialized.rs b/src/hyperlight_host/src/sandbox/uninitialized.rs index 2167dca43..1d3a41f02 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -29,9 +29,13 @@ use crate::func::host_functions::{HostFunction, register_host_function}; use crate::func::{ParameterTuple, SupportedReturnType}; #[cfg(feature = "build-metadata")] use crate::log_build_details; +#[cfg(feature = "guest-semaphore")] +use crate::mem::layout::SandboxMemoryLayout; use crate::mem::memory_region::{DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegionFlags}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::ExclusiveSharedMemory; +#[cfg(feature = "guest-semaphore")] +use crate::mem::shared_mem::SharedMemory; use crate::sandbox::SandboxConfiguration; use crate::{MultiUseSandbox, Result, new_error}; @@ -54,6 +58,53 @@ pub(crate) struct SandboxRuntimeConfig { pub(crate) entry_point: Option, } +/// A counting semaphore backed by a u64 in guest shared memory. +/// +/// Created via [`UninitializedSandbox::guest_semaphore()`]. The host +/// manipulates the counter with [`increment()`](Self::increment) and +/// [`decrement()`](Self::decrement); the guest reads the same location +/// with a volatile load at the agreed-upon GPA. +#[cfg(feature = "guest-semaphore")] +pub struct GuestSemaphore { + ptr: *mut u64, + value: u64, +} + +// SAFETY: The pointer targets mmap'd shared memory owned by the sandbox. +// Access must be externally synchronised (e.g. via Mutex). +#[cfg(feature = "guest-semaphore")] +unsafe impl Send for GuestSemaphore {} + +#[cfg(feature = "guest-semaphore")] +impl GuestSemaphore { + /// Increments the counter by one and writes it to guest memory with + /// a volatile store. + pub fn increment(&mut self) -> Result<()> { + self.value = self + .value + .checked_add(1) + .ok_or_else(|| new_error!("GuestSemaphore overflow"))?; + unsafe { core::ptr::write_volatile(self.ptr, self.value) }; + Ok(()) + } + + /// Decrements the counter by one and writes it to guest memory with + /// a volatile store. + pub fn decrement(&mut self) -> Result<()> { + self.value = self + .value + .checked_sub(1) + .ok_or_else(|| new_error!("GuestSemaphore underflow"))?; + unsafe { core::ptr::write_volatile(self.ptr, self.value) }; + Ok(()) + } + + /// Returns the current host-side value of the counter. + pub fn value(&self) -> u64 { + self.value + } +} + /// A preliminary sandbox that represents allocated memory and registered host functions, /// but has not yet created the underlying virtual machine. /// @@ -169,6 +220,48 @@ impl<'a> From> for GuestEnvironment<'a, '_> { } impl UninitializedSandbox { + /// Creates a [`GuestSemaphore`] backed by a u64 counter at the given + /// guest physical address (GPA). + /// + /// The GPA must fall within the sandbox's allocated memory region. + /// The returned semaphore owns a host-side pointer into the shared + /// memory mapping and exposes safe `increment()` / `decrement()` + /// operations that use volatile writes so the guest can observe + /// changes. + /// + /// # Safety contract + /// + /// The caller must ensure the `GuestSemaphore` does not outlive the + /// sandbox's underlying shared memory mapping (which stays alive + /// through `evolve()` into `MultiUseSandbox`). + #[cfg(feature = "guest-semaphore")] + pub fn guest_semaphore(&mut self, gpa: usize) -> Result { + let base = SandboxMemoryLayout::BASE_ADDRESS; + let mem_size = self.mgr.shared_mem.mem_size(); + + if gpa < base { + return Err(new_error!( + "GPA {:#x} is below the sandbox base address ({:#x})", + gpa, + base + )); + } + + let offset = gpa - base; + + if offset >= mem_size { + return Err(new_error!( + "GPA {:#x} (offset {:#x}) is outside the sandbox memory region (size {:#x})", + gpa, + offset, + mem_size + )); + } + + let ptr = unsafe { self.mgr.shared_mem.base_ptr().add(offset) as *mut u64 }; + Ok(GuestSemaphore { ptr, value: 0 }) + } + // Creates a new uninitialized sandbox from a pre-built snapshot. // Note that since memory configuration is part of the snapshot the only configuration // that can be changed (from the original snapshot) is the configuration defines the behaviour of