Skip to content

Commit a8005d1

Browse files
fix: Validate guest address ranges for overlapping regions in map_region
Add overlap validation to HyperlightVm::map_region to enforce the safety contract documented on VirtualMachine::map_memory, which requires that memory regions must not overlap. The check validates the new region against: - Existing dynamically mapped regions (mmap_regions) - The snapshot region (starting at BASE_ADDRESS) - The scratch region (at the top of the GPA space) Add Overlapping variant to MapRegionError with a descriptive message showing both the new and conflicting ranges. Add four tests covering: - Exact duplicate mapping rejection - Partial overlap rejection - Adjacent non-overlapping regions (should succeed) - Overlap with the snapshot region Signed-off-by: Richard Durkee <Richard-Durkee@users.noreply.github.com>
1 parent cb1ceb5 commit a8005d1

2 files changed

Lines changed: 139 additions & 0 deletions

File tree

src/hyperlight_host/src/hypervisor/hyperlight_vm/mod.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,10 @@ pub enum MapRegionError {
243243
MapMemory(#[from] MapMemoryError),
244244
#[error("Region is not page-aligned (page size: {0:#x})")]
245245
NotPageAligned(usize),
246+
#[error(
247+
"Region [{0:#x}..{1:#x}) overlaps existing region [{2:#x}..{3:#x})"
248+
)]
249+
Overlapping(usize, usize, usize, usize),
246250
}
247251

248252
/// Errors that can occur when unmapping a memory region
@@ -420,6 +424,44 @@ impl HyperlightVm {
420424
return Err(MapRegionError::NotPageAligned(self.page_size));
421425
}
422426

427+
let new_start = region.guest_region.start;
428+
let new_end = region.guest_region.end;
429+
430+
// Check against existing dynamically mapped regions
431+
for (_, existing) in &self.mmap_regions {
432+
if new_start < existing.guest_region.end && new_end > existing.guest_region.start {
433+
return Err(MapRegionError::Overlapping(
434+
new_start,
435+
new_end,
436+
existing.guest_region.start,
437+
existing.guest_region.end,
438+
));
439+
}
440+
}
441+
442+
// Check against the snapshot region
443+
if let Some(ref snapshot) = self.snapshot_memory {
444+
let snap_start = crate::mem::layout::SandboxMemoryLayout::BASE_ADDRESS;
445+
let snap_end = snap_start + snapshot.mem_size();
446+
if new_start < snap_end && new_end > snap_start {
447+
return Err(MapRegionError::Overlapping(
448+
new_start, new_end, snap_start, snap_end,
449+
));
450+
}
451+
}
452+
453+
// Check against the scratch region
454+
if let Some(ref scratch) = self.scratch_memory {
455+
let scratch_start =
456+
hyperlight_common::layout::scratch_base_gpa(scratch.mem_size()) as usize;
457+
let scratch_end = scratch_start + scratch.mem_size();
458+
if new_start < scratch_end && new_end > scratch_start {
459+
return Err(MapRegionError::Overlapping(
460+
new_start, new_end, scratch_start, scratch_end,
461+
));
462+
}
463+
}
464+
423465
// Try to reuse a freed slot first, otherwise use next_slot
424466
let slot = if let Some(freed_slot) = self.freed_slots.pop() {
425467
freed_slot

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2551,4 +2551,101 @@ mod tests {
25512551
}
25522552
let _ = std::fs::remove_file(&path);
25532553
}
2554+
2555+
#[test]
2556+
fn map_region_rejects_overlapping_regions() {
2557+
let mut sbox: MultiUseSandbox = {
2558+
let path = simple_guest_as_string().unwrap();
2559+
let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2560+
u_sbox.evolve().unwrap()
2561+
};
2562+
2563+
let mem1 = allocate_guest_memory();
2564+
let mem2 = allocate_guest_memory();
2565+
let guest_base: usize = 0x200000000;
2566+
let region1 = region_for_memory(&mem1, guest_base, MemoryRegionFlags::READ);
2567+
2568+
// First mapping should succeed
2569+
unsafe { sbox.map_region(&region1).unwrap() };
2570+
2571+
// Exact same range should fail
2572+
let region2 = region_for_memory(&mem2, guest_base, MemoryRegionFlags::READ);
2573+
let err = unsafe { sbox.map_region(&region2) }.unwrap_err();
2574+
assert!(
2575+
format!("{err:?}").contains("Overlapping"),
2576+
"Expected Overlapping error, got: {err:?}"
2577+
);
2578+
}
2579+
2580+
#[test]
2581+
fn map_region_rejects_partial_overlap() {
2582+
let mut sbox: MultiUseSandbox = {
2583+
let path = simple_guest_as_string().unwrap();
2584+
let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2585+
u_sbox.evolve().unwrap()
2586+
};
2587+
2588+
let mem1 = allocate_guest_memory();
2589+
let mem2 = allocate_guest_memory();
2590+
let guest_base: usize = 0x200000000;
2591+
let region1 = region_for_memory(&mem1, guest_base, MemoryRegionFlags::READ);
2592+
let region_size = mem1.mem_size();
2593+
2594+
unsafe { sbox.map_region(&region1).unwrap() };
2595+
2596+
// Overlapping from below (starts before, ends inside)
2597+
let overlap_base = guest_base - region_size / 2;
2598+
// Align to page boundary
2599+
let overlap_base = overlap_base & !(0x1000 - 1);
2600+
let region2 = region_for_memory(&mem2, overlap_base, MemoryRegionFlags::READ);
2601+
let err = unsafe { sbox.map_region(&region2) }.unwrap_err();
2602+
assert!(
2603+
format!("{err:?}").contains("Overlapping"),
2604+
"Expected Overlapping error for partial overlap, got: {err:?}"
2605+
);
2606+
}
2607+
2608+
#[test]
2609+
fn map_region_allows_adjacent_non_overlapping() {
2610+
let mut sbox: MultiUseSandbox = {
2611+
let path = simple_guest_as_string().unwrap();
2612+
let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2613+
u_sbox.evolve().unwrap()
2614+
};
2615+
2616+
let mem1 = allocate_guest_memory();
2617+
let mem2 = allocate_guest_memory();
2618+
let guest_base: usize = 0x200000000;
2619+
let region1 = region_for_memory(&mem1, guest_base, MemoryRegionFlags::READ);
2620+
let region_size = mem1.mem_size();
2621+
2622+
unsafe { sbox.map_region(&region1).unwrap() };
2623+
2624+
// Adjacent region (starts right after the first one ends) should succeed
2625+
let adjacent_base = guest_base + region_size;
2626+
let region2 = region_for_memory(&mem2, adjacent_base, MemoryRegionFlags::READ);
2627+
unsafe { sbox.map_region(&region2).unwrap() };
2628+
}
2629+
2630+
#[test]
2631+
fn map_region_rejects_overlap_with_snapshot() {
2632+
let mut sbox: MultiUseSandbox = {
2633+
let path = simple_guest_as_string().unwrap();
2634+
let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2635+
u_sbox.evolve().unwrap()
2636+
};
2637+
2638+
// Try to map at BASE_ADDRESS (0x1000) which overlaps the snapshot region
2639+
let mem = allocate_guest_memory();
2640+
let region = region_for_memory(
2641+
&mem,
2642+
crate::mem::layout::SandboxMemoryLayout::BASE_ADDRESS,
2643+
MemoryRegionFlags::READ,
2644+
);
2645+
let err = unsafe { sbox.map_region(&region) }.unwrap_err();
2646+
assert!(
2647+
format!("{err:?}").contains("Overlapping"),
2648+
"Expected Overlapping error for snapshot overlap, got: {err:?}"
2649+
);
2650+
}
25542651
}

0 commit comments

Comments
 (0)