Skip to content
Merged
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
43 changes: 43 additions & 0 deletions src/hyperlight_common/src/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,54 @@ pub struct GuestMemoryRegion {
pub ptr: u64,
}

/// Maximum length of a file mapping label (excluding null terminator).
pub const FILE_MAPPING_LABEL_MAX_LEN: usize = 63;

/// Maximum number of file mappings that can be registered in the PEB.
///
/// Space for this many [`FileMappingInfo`] entries is statically
/// reserved immediately after the [`HyperlightPEB`] struct within the
/// same memory region. The reservation happens at layout time
/// (see `SandboxMemoryLayout::new`) so the guest heap never overlaps
/// the array, regardless of how many entries are actually used.
pub const MAX_FILE_MAPPINGS: usize = 32;

/// Describes a single file mapping in the guest address space.
///
/// Stored in the PEB's file mappings array so the guest can discover
/// which files have been mapped, at what address, and with what label.
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct FileMappingInfo {
/// The guest address where the file is mapped.
pub guest_addr: u64,
/// The page-aligned size of the mapping in bytes.
pub size: u64,
/// Null-terminated C-style label (max 63 chars + null).
pub label: [u8; FILE_MAPPING_LABEL_MAX_LEN + 1],
}

impl Default for FileMappingInfo {
fn default() -> Self {
Self {
guest_addr: 0,
size: 0,
label: [0u8; FILE_MAPPING_LABEL_MAX_LEN + 1],
}
}
}

#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct HyperlightPEB {
pub input_stack: GuestMemoryRegion,
pub output_stack: GuestMemoryRegion,
pub init_data: GuestMemoryRegion,
pub guest_heap: GuestMemoryRegion,
/// File mappings array descriptor.
/// **Note:** `size` holds the **entry count** (number of valid
/// [`FileMappingInfo`] entries), NOT a byte size. `ptr` holds the
/// guest address of the preallocated array (immediately after the
/// PEB struct).
pub file_mappings: GuestMemoryRegion,
}
6 changes: 3 additions & 3 deletions src/hyperlight_host/examples/crashdump/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ fn guest_crash_auto_dump(guest_path: &str) -> hyperlight_host::Result<()> {
// Map a file as read-only into the guest at a known address.
let mapping_file = create_mapping_file();
let guest_base: u64 = 0x200000000;
let len = sandbox.map_file_cow(mapping_file.as_path(), guest_base)?;
let len = sandbox.map_file_cow(mapping_file.as_path(), guest_base, None)?;
println!("Mapped {len} bytes at guest address {guest_base:#x} (read-only).");

// Call WriteMappedBuffer — the guest maps the address in its page tables
Expand Down Expand Up @@ -259,7 +259,7 @@ fn guest_crash_with_dump_disabled(guest_path: &str) -> hyperlight_host::Result<(

let mapping_file = create_mapping_file();
let guest_base: u64 = 0x200000000;
let len = sandbox.map_file_cow(mapping_file.as_path(), guest_base)?;
let len = sandbox.map_file_cow(mapping_file.as_path(), guest_base, None)?;

println!("Calling guest function 'WriteMappedBuffer' on read-only region...");
let result = sandbox.call::<bool>("WriteMappedBuffer", (guest_base, len));
Expand Down Expand Up @@ -401,7 +401,7 @@ mod tests {
// automatically. This mapping lets us verify that GDB can read
// a specific sentinel string from a known address.
let len = sbox
.map_file_cow(&data_file, MAP_GUEST_BASE)
.map_file_cow(&data_file, MAP_GUEST_BASE, None)
.expect("map_file_cow");

// Read the mapped region back through the guest and verify it
Expand Down
73 changes: 66 additions & 7 deletions src/hyperlight_host/src/mem/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ limitations under the License.
use std::fmt::Debug;
use std::mem::{offset_of, size_of};

use hyperlight_common::mem::{GuestMemoryRegion, HyperlightPEB, PAGE_SIZE_USIZE};
use hyperlight_common::mem::{HyperlightPEB, PAGE_SIZE_USIZE};
use tracing::{Span, instrument};

use super::memory_region::MemoryRegionType::{Code, Heap, InitData, Peb};
Expand Down Expand Up @@ -92,6 +92,7 @@ pub(crate) struct SandboxMemoryLayout {
peb_output_data_offset: usize,
peb_init_data_offset: usize,
peb_heap_data_offset: usize,
peb_file_mappings_offset: usize,

guest_heap_buffer_offset: usize,
init_data_offset: usize,
Expand Down Expand Up @@ -141,6 +142,10 @@ impl Debug for SandboxMemoryLayout {
"Guest Heap Offset",
&format_args!("{:#x}", self.peb_heap_data_offset),
)
.field(
"File Mappings Offset",
&format_args!("{:#x}", self.peb_file_mappings_offset),
)
.field(
"Guest Heap Buffer Offset",
&format_args!("{:#x}", self.guest_heap_buffer_offset),
Expand Down Expand Up @@ -211,13 +216,25 @@ impl SandboxMemoryLayout {
let peb_output_data_offset = peb_offset + offset_of!(HyperlightPEB, output_stack);
let peb_init_data_offset = peb_offset + offset_of!(HyperlightPEB, init_data);
let peb_heap_data_offset = peb_offset + offset_of!(HyperlightPEB, guest_heap);
let peb_file_mappings_offset = peb_offset + offset_of!(HyperlightPEB, file_mappings);

// The following offsets are the actual values that relate to memory layout,
// which are written to PEB struct
let peb_address = Self::BASE_ADDRESS + peb_offset;
// make sure heap buffer starts at 4K boundary
let guest_heap_buffer_offset = (peb_heap_data_offset + size_of::<GuestMemoryRegion>())
.next_multiple_of(PAGE_SIZE_USIZE);
// make sure heap buffer starts at 4K boundary.
// The FileMappingInfo array is stored immediately after the PEB struct.
// We statically reserve space for MAX_FILE_MAPPINGS entries so that
// the heap never overlaps the array, even when all slots are used.
// The host writes file mapping metadata here via write_file_mapping_entry;
// the guest only reads the entries. We don't know at layout time how
// many file mappings the host will register, so we reserve space for
// the maximum number.
// The heap starts at the next page boundary after this reserved area.
let file_mappings_array_end = peb_offset
+ size_of::<HyperlightPEB>()
+ hyperlight_common::mem::MAX_FILE_MAPPINGS
* size_of::<hyperlight_common::mem::FileMappingInfo>();
let guest_heap_buffer_offset = file_mappings_array_end.next_multiple_of(PAGE_SIZE_USIZE);

// make sure init data starts at 4K boundary
let init_data_offset =
Expand All @@ -230,6 +247,7 @@ impl SandboxMemoryLayout {
peb_output_data_offset,
peb_init_data_offset,
peb_heap_data_offset,
peb_file_mappings_offset,
sandbox_memory_config: cfg,
code_size,
guest_heap_buffer_offset,
Expand Down Expand Up @@ -350,6 +368,28 @@ impl SandboxMemoryLayout {
self.peb_heap_data_offset
}

/// Get the offset in guest memory to the file_mappings count field
/// (the `size` field of the `GuestMemoryRegion` in the PEB).
pub(crate) fn get_file_mappings_size_offset(&self) -> usize {
self.peb_file_mappings_offset
}

/// Get the offset in guest memory to the file_mappings pointer field.
fn get_file_mappings_pointer_offset(&self) -> usize {
self.get_file_mappings_size_offset() + size_of::<u64>()
}

/// Get the offset in snapshot memory where the FileMappingInfo array starts
/// (immediately after the PEB struct, within the same page).
pub(crate) fn get_file_mappings_array_offset(&self) -> usize {
self.peb_offset + size_of::<HyperlightPEB>()
}

/// Get the guest address of the FileMappingInfo array.
fn get_file_mappings_array_gva(&self) -> u64 {
(Self::BASE_ADDRESS + self.get_file_mappings_array_offset()) as u64
}

/// Get the offset of the heap pointer in guest memory,
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
fn get_heap_pointer_offset(&self) -> usize {
Expand Down Expand Up @@ -446,9 +486,12 @@ impl SandboxMemoryLayout {
));
}

// PEB
// PEB + preallocated FileMappingInfo array
let peb_and_array_size = size_of::<HyperlightPEB>()
+ hyperlight_common::mem::MAX_FILE_MAPPINGS
* size_of::<hyperlight_common::mem::FileMappingInfo>();
let heap_offset = builder.push_page_aligned(
size_of::<HyperlightPEB>(),
peb_and_array_size,
MemoryRegionFlags::READ | MemoryRegionFlags::WRITE,
Peb,
);
Expand Down Expand Up @@ -588,6 +631,18 @@ impl SandboxMemoryLayout {
shared_mem.write_u64(self.get_heap_size_offset(), self.heap_size.try_into()?)?;
shared_mem.write_u64(self.get_heap_pointer_offset(), addr)?;

// Set up the file_mappings descriptor in the PEB.
// - The `size` field holds the number of valid FileMappingInfo
// entries currently written (initially 0 — entries are added
// later by map_file_cow / evolve).
// - The `ptr` field holds the guest address of the preallocated
// FileMappingInfo array
shared_mem.write_u64(self.get_file_mappings_size_offset(), 0)?;
shared_mem.write_u64(
self.get_file_mappings_pointer_offset(),
self.get_file_mappings_array_gva(),
)?;

// End of setting up the PEB

// The input and output data regions do not have their layout
Expand All @@ -611,7 +666,11 @@ mod tests {
// in order of layout
expected_size += layout.code_size;

expected_size += size_of::<HyperlightPEB>().next_multiple_of(PAGE_SIZE_USIZE);
// PEB + preallocated FileMappingInfo array
let peb_and_array = size_of::<HyperlightPEB>()
+ hyperlight_common::mem::MAX_FILE_MAPPINGS
* size_of::<hyperlight_common::mem::FileMappingInfo>();
expected_size += peb_and_array.next_multiple_of(PAGE_SIZE_USIZE);

expected_size += layout.heap_size.next_multiple_of(PAGE_SIZE_USIZE);

Expand Down
62 changes: 62 additions & 0 deletions src/hyperlight_host/src/mem/mgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
use std::mem::offset_of;

use flatbuffers::FlatBufferBuilder;
use hyperlight_common::flatbuffer_wrappers::function_call::{
FunctionCall, validate_guest_function_call_buffer,
Expand Down Expand Up @@ -351,6 +353,66 @@ impl SandboxMemoryManager<ExclusiveSharedMemory> {
}

impl SandboxMemoryManager<HostSharedMemory> {
/// Write a [`FileMappingInfo`] entry into the PEB's preallocated array.
///
/// Reads the current entry count from the PEB, validates that the
/// array isn't full ([`MAX_FILE_MAPPINGS`]), writes the entry at the
/// next available slot, and increments the count.
///
/// This is the **only** place that writes to the PEB file mappings
/// array — both `MultiUseSandbox::map_file_cow` and the evolve loop
/// call through here so the logic is not duplicated.
///
/// # Errors
///
/// Returns an error if [`MAX_FILE_MAPPINGS`] has been reached.
///
/// [`FileMappingInfo`]: hyperlight_common::mem::FileMappingInfo
/// [`MAX_FILE_MAPPINGS`]: hyperlight_common::mem::MAX_FILE_MAPPINGS
pub(crate) fn write_file_mapping_entry(
&mut self,
guest_addr: u64,
size: u64,
label: &[u8; hyperlight_common::mem::FILE_MAPPING_LABEL_MAX_LEN + 1],
) -> Result<()> {
use hyperlight_common::mem::{FileMappingInfo, MAX_FILE_MAPPINGS};

// Read the current entry count from the PEB. This is the source
// of truth — it survives snapshot/restore because the PEB is
// part of shared memory that gets snapshotted.
let current_count =
self.shared_mem
.read::<u64>(self.layout.get_file_mappings_size_offset())? as usize;

if current_count >= MAX_FILE_MAPPINGS {
return Err(crate::new_error!(
"file mapping limit reached ({} of {})",
current_count,
MAX_FILE_MAPPINGS,
));
}

// Write the entry into the next available slot.
let entry_offset = self.layout.get_file_mappings_array_offset()
+ current_count * std::mem::size_of::<FileMappingInfo>();
let guest_addr_offset = offset_of!(FileMappingInfo, guest_addr);
let size_offset = offset_of!(FileMappingInfo, size);
let label_offset = offset_of!(FileMappingInfo, label);
self.shared_mem
.write::<u64>(entry_offset + guest_addr_offset, guest_addr)?;
self.shared_mem
.write::<u64>(entry_offset + size_offset, size)?;
self.shared_mem
.copy_from_slice(label, entry_offset + label_offset)?;

// Increment the entry count.
let new_count = (current_count + 1) as u64;
self.shared_mem
.write::<u64>(self.layout.get_file_mappings_size_offset(), new_count)?;

Ok(())
}

/// Reads a host function call from memory
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn get_host_function_call(&mut self) -> Result<FunctionCall> {
Expand Down
Loading
Loading