Skip to content

Commit fec04d7

Browse files
committed
feat: pass file mapping info via PEB with label support
Add FileMappingInfo struct (guest_addr, size, label) and file_mappings field to HyperlightPEB so file mapping metadata is communicated to the guest through the PEB. Space for MAX_FILE_MAPPINGS (32) entries is statically reserved after the PEB struct to avoid dynamic layout changes. - Add label parameter to map_file_cow (optional, defaults to filename) - Add shared memory overlap validation (full mapped range) - Add inter-mapping overlap detection - Add write_file_mapping_entry on SandboxMemoryManager - Add MAX_FILE_MAPPINGS limit enforcement at registration time - Update PEB region sizing in get_memory_regions and layout tests Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
1 parent 137e964 commit fec04d7

File tree

8 files changed

+781
-41
lines changed

8 files changed

+781
-41
lines changed

src/hyperlight_common/src/mem.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,54 @@ pub struct GuestMemoryRegion {
2828
pub ptr: u64,
2929
}
3030

31+
/// Maximum length of a file mapping label (excluding null terminator).
32+
pub const FILE_MAPPING_LABEL_MAX_LEN: usize = 63;
33+
34+
/// Maximum number of file mappings that can be registered in the PEB.
35+
///
36+
/// Space for this many [`FileMappingInfo`] entries is statically
37+
/// reserved immediately after the [`HyperlightPEB`] struct within the
38+
/// same memory region. The reservation happens at layout time
39+
/// (see `SandboxMemoryLayout::new`) so the guest heap never overlaps
40+
/// the array, regardless of how many entries are actually used.
41+
pub const MAX_FILE_MAPPINGS: usize = 32;
42+
43+
/// Describes a single file mapping in the guest address space.
44+
///
45+
/// Stored in the PEB's file mappings array so the guest can discover
46+
/// which files have been mapped, at what address, and with what label.
47+
#[derive(Debug, Clone, Copy)]
48+
#[repr(C)]
49+
pub struct FileMappingInfo {
50+
/// The guest address where the file is mapped.
51+
pub guest_addr: u64,
52+
/// The page-aligned size of the mapping in bytes.
53+
pub size: u64,
54+
/// Null-terminated C-style label (max 63 chars + null).
55+
pub label: [u8; FILE_MAPPING_LABEL_MAX_LEN + 1],
56+
}
57+
58+
impl Default for FileMappingInfo {
59+
fn default() -> Self {
60+
Self {
61+
guest_addr: 0,
62+
size: 0,
63+
label: [0u8; FILE_MAPPING_LABEL_MAX_LEN + 1],
64+
}
65+
}
66+
}
67+
3168
#[derive(Debug, Clone, Copy)]
3269
#[repr(C)]
3370
pub struct HyperlightPEB {
3471
pub input_stack: GuestMemoryRegion,
3572
pub output_stack: GuestMemoryRegion,
3673
pub init_data: GuestMemoryRegion,
3774
pub guest_heap: GuestMemoryRegion,
75+
/// File mappings array descriptor.
76+
/// **Note:** `size` holds the **entry count** (number of valid
77+
/// [`FileMappingInfo`] entries), NOT a byte size. `ptr` holds the
78+
/// guest address of the preallocated array (immediately after the
79+
/// PEB struct).
80+
pub file_mappings: GuestMemoryRegion,
3881
}

src/hyperlight_host/examples/crashdump/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ fn guest_crash_auto_dump(guest_path: &str) -> hyperlight_host::Result<()> {
152152
// Map a file as read-only into the guest at a known address.
153153
let mapping_file = create_mapping_file();
154154
let guest_base: u64 = 0x200000000;
155-
let len = sandbox.map_file_cow(mapping_file.as_path(), guest_base)?;
155+
let len = sandbox.map_file_cow(mapping_file.as_path(), guest_base, None)?;
156156
println!("Mapped {len} bytes at guest address {guest_base:#x} (read-only).");
157157

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

260260
let mapping_file = create_mapping_file();
261261
let guest_base: u64 = 0x200000000;
262-
let len = sandbox.map_file_cow(mapping_file.as_path(), guest_base)?;
262+
let len = sandbox.map_file_cow(mapping_file.as_path(), guest_base, None)?;
263263

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

407407
// Read the mapped region back through the guest and verify it

src/hyperlight_host/src/mem/layout.rs

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ limitations under the License.
6363
use std::fmt::Debug;
6464
use std::mem::{offset_of, size_of};
6565

66-
use hyperlight_common::mem::{GuestMemoryRegion, HyperlightPEB, PAGE_SIZE_USIZE};
66+
use hyperlight_common::mem::{HyperlightPEB, PAGE_SIZE_USIZE};
6767
use tracing::{Span, instrument};
6868

6969
use super::memory_region::MemoryRegionType::{Code, Heap, InitData, Peb};
@@ -92,6 +92,7 @@ pub(crate) struct SandboxMemoryLayout {
9292
peb_output_data_offset: usize,
9393
peb_init_data_offset: usize,
9494
peb_heap_data_offset: usize,
95+
peb_file_mappings_offset: usize,
9596

9697
guest_heap_buffer_offset: usize,
9798
init_data_offset: usize,
@@ -141,6 +142,10 @@ impl Debug for SandboxMemoryLayout {
141142
"Guest Heap Offset",
142143
&format_args!("{:#x}", self.peb_heap_data_offset),
143144
)
145+
.field(
146+
"File Mappings Offset",
147+
&format_args!("{:#x}", self.peb_file_mappings_offset),
148+
)
144149
.field(
145150
"Guest Heap Buffer Offset",
146151
&format_args!("{:#x}", self.guest_heap_buffer_offset),
@@ -211,13 +216,25 @@ impl SandboxMemoryLayout {
211216
let peb_output_data_offset = peb_offset + offset_of!(HyperlightPEB, output_stack);
212217
let peb_init_data_offset = peb_offset + offset_of!(HyperlightPEB, init_data);
213218
let peb_heap_data_offset = peb_offset + offset_of!(HyperlightPEB, guest_heap);
219+
let peb_file_mappings_offset = peb_offset + offset_of!(HyperlightPEB, file_mappings);
214220

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

222239
// make sure init data starts at 4K boundary
223240
let init_data_offset =
@@ -230,6 +247,7 @@ impl SandboxMemoryLayout {
230247
peb_output_data_offset,
231248
peb_init_data_offset,
232249
peb_heap_data_offset,
250+
peb_file_mappings_offset,
233251
sandbox_memory_config: cfg,
234252
code_size,
235253
guest_heap_buffer_offset,
@@ -350,6 +368,28 @@ impl SandboxMemoryLayout {
350368
self.peb_heap_data_offset
351369
}
352370

371+
/// Get the offset in guest memory to the file_mappings count field
372+
/// (the `size` field of the `GuestMemoryRegion` in the PEB).
373+
pub(crate) fn get_file_mappings_size_offset(&self) -> usize {
374+
self.peb_file_mappings_offset
375+
}
376+
377+
/// Get the offset in guest memory to the file_mappings pointer field.
378+
fn get_file_mappings_pointer_offset(&self) -> usize {
379+
self.get_file_mappings_size_offset() + size_of::<u64>()
380+
}
381+
382+
/// Get the offset in snapshot memory where the FileMappingInfo array starts
383+
/// (immediately after the PEB struct, within the same page).
384+
pub(crate) fn get_file_mappings_array_offset(&self) -> usize {
385+
self.peb_offset + size_of::<HyperlightPEB>()
386+
}
387+
388+
/// Get the guest address of the FileMappingInfo array.
389+
fn get_file_mappings_array_gva(&self) -> u64 {
390+
(Self::BASE_ADDRESS + self.get_file_mappings_array_offset()) as u64
391+
}
392+
353393
/// Get the offset of the heap pointer in guest memory,
354394
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
355395
fn get_heap_pointer_offset(&self) -> usize {
@@ -446,9 +486,12 @@ impl SandboxMemoryLayout {
446486
));
447487
}
448488

449-
// PEB
489+
// PEB + preallocated FileMappingInfo array
490+
let peb_and_array_size = size_of::<HyperlightPEB>()
491+
+ hyperlight_common::mem::MAX_FILE_MAPPINGS
492+
* size_of::<hyperlight_common::mem::FileMappingInfo>();
450493
let heap_offset = builder.push_page_aligned(
451-
size_of::<HyperlightPEB>(),
494+
peb_and_array_size,
452495
MemoryRegionFlags::READ | MemoryRegionFlags::WRITE,
453496
Peb,
454497
);
@@ -588,6 +631,18 @@ impl SandboxMemoryLayout {
588631
shared_mem.write_u64(self.get_heap_size_offset(), self.heap_size.try_into()?)?;
589632
shared_mem.write_u64(self.get_heap_pointer_offset(), addr)?;
590633

634+
// Set up the file_mappings descriptor in the PEB.
635+
// - The `size` field holds the number of valid FileMappingInfo
636+
// entries currently written (initially 0 — entries are added
637+
// later by map_file_cow / evolve).
638+
// - The `ptr` field holds the guest address of the preallocated
639+
// FileMappingInfo array
640+
shared_mem.write_u64(self.get_file_mappings_size_offset(), 0)?;
641+
shared_mem.write_u64(
642+
self.get_file_mappings_pointer_offset(),
643+
self.get_file_mappings_array_gva(),
644+
)?;
645+
591646
// End of setting up the PEB
592647

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

614-
expected_size += size_of::<HyperlightPEB>().next_multiple_of(PAGE_SIZE_USIZE);
669+
// PEB + preallocated FileMappingInfo array
670+
let peb_and_array = size_of::<HyperlightPEB>()
671+
+ hyperlight_common::mem::MAX_FILE_MAPPINGS
672+
* size_of::<hyperlight_common::mem::FileMappingInfo>();
673+
expected_size += peb_and_array.next_multiple_of(PAGE_SIZE_USIZE);
615674

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

src/hyperlight_host/src/mem/mgr.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
16+
use std::mem::offset_of;
17+
1618
use flatbuffers::FlatBufferBuilder;
1719
use hyperlight_common::flatbuffer_wrappers::function_call::{
1820
FunctionCall, validate_guest_function_call_buffer,
@@ -351,6 +353,66 @@ impl SandboxMemoryManager<ExclusiveSharedMemory> {
351353
}
352354

353355
impl SandboxMemoryManager<HostSharedMemory> {
356+
/// Write a [`FileMappingInfo`] entry into the PEB's preallocated array.
357+
///
358+
/// Reads the current entry count from the PEB, validates that the
359+
/// array isn't full ([`MAX_FILE_MAPPINGS`]), writes the entry at the
360+
/// next available slot, and increments the count.
361+
///
362+
/// This is the **only** place that writes to the PEB file mappings
363+
/// array — both `MultiUseSandbox::map_file_cow` and the evolve loop
364+
/// call through here so the logic is not duplicated.
365+
///
366+
/// # Errors
367+
///
368+
/// Returns an error if [`MAX_FILE_MAPPINGS`] has been reached.
369+
///
370+
/// [`FileMappingInfo`]: hyperlight_common::mem::FileMappingInfo
371+
/// [`MAX_FILE_MAPPINGS`]: hyperlight_common::mem::MAX_FILE_MAPPINGS
372+
pub(crate) fn write_file_mapping_entry(
373+
&mut self,
374+
guest_addr: u64,
375+
size: u64,
376+
label: &[u8; hyperlight_common::mem::FILE_MAPPING_LABEL_MAX_LEN + 1],
377+
) -> Result<()> {
378+
use hyperlight_common::mem::{FileMappingInfo, MAX_FILE_MAPPINGS};
379+
380+
// Read the current entry count from the PEB. This is the source
381+
// of truth — it survives snapshot/restore because the PEB is
382+
// part of shared memory that gets snapshotted.
383+
let current_count =
384+
self.shared_mem
385+
.read::<u64>(self.layout.get_file_mappings_size_offset())? as usize;
386+
387+
if current_count >= MAX_FILE_MAPPINGS {
388+
return Err(crate::new_error!(
389+
"file mapping limit reached ({} of {})",
390+
current_count,
391+
MAX_FILE_MAPPINGS,
392+
));
393+
}
394+
395+
// Write the entry into the next available slot.
396+
let entry_offset = self.layout.get_file_mappings_array_offset()
397+
+ current_count * std::mem::size_of::<FileMappingInfo>();
398+
let guest_addr_offset = offset_of!(FileMappingInfo, guest_addr);
399+
let size_offset = offset_of!(FileMappingInfo, size);
400+
let label_offset = offset_of!(FileMappingInfo, label);
401+
self.shared_mem
402+
.write::<u64>(entry_offset + guest_addr_offset, guest_addr)?;
403+
self.shared_mem
404+
.write::<u64>(entry_offset + size_offset, size)?;
405+
self.shared_mem
406+
.copy_from_slice(label, entry_offset + label_offset)?;
407+
408+
// Increment the entry count.
409+
let new_count = (current_count + 1) as u64;
410+
self.shared_mem
411+
.write::<u64>(self.layout.get_file_mappings_size_offset(), new_count)?;
412+
413+
Ok(())
414+
}
415+
354416
/// Reads a host function call from memory
355417
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
356418
pub(crate) fn get_host_function_call(&mut self) -> Result<FunctionCall> {

0 commit comments

Comments
 (0)