Skip to content

Commit 5e09e95

Browse files
fix: validate guest address ranges for overlapping regions in map_region
Closes #1289 Signed-off-by: Richard Durkee <Richard-Durkee@users.noreply.github.com>
1 parent 29bf180 commit 5e09e95

2 files changed

Lines changed: 175 additions & 17 deletions

File tree

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,13 @@ pub enum MapRegionError {
243243
MapMemory(#[from] MapMemoryError),
244244
#[error("Region is not page-aligned (page size: {0:#x})")]
245245
NotPageAligned(usize),
246+
#[error("Region [{new_start:#x}..{new_end:#x}) overlaps existing region [{existing_start:#x}..{existing_end:#x})")]
247+
Overlapping {
248+
new_start: usize,
249+
new_end: usize,
250+
existing_start: usize,
251+
existing_end: usize,
252+
},
246253
}
247254

248255
/// Errors that can occur when unmapping a memory region
@@ -420,6 +427,53 @@ impl HyperlightVm {
420427
return Err(MapRegionError::NotPageAligned(self.page_size));
421428
}
422429

430+
let new_start = region.guest_region.start;
431+
let new_end = region.guest_region.end;
432+
433+
// Check against existing dynamically mapped regions
434+
for (_, existing) in &self.mmap_regions {
435+
if new_start < existing.guest_region.end && new_end > existing.guest_region.start {
436+
return Err(MapRegionError::Overlapping {
437+
new_start,
438+
new_end,
439+
existing_start: existing.guest_region.start,
440+
existing_end: existing.guest_region.end,
441+
});
442+
}
443+
}
444+
445+
// Check against the snapshot region
446+
if let Some(ref snapshot) = self.snapshot_memory {
447+
let snap_start = crate::mem::layout::SandboxMemoryLayout::BASE_ADDRESS;
448+
#[cfg(not(unshared_snapshot_mem))]
449+
let snap_end = snap_start + snapshot.guest_mapped_size();
450+
#[cfg(unshared_snapshot_mem)]
451+
let snap_end = snap_start + snapshot.mem_size();
452+
if new_start < snap_end && new_end > snap_start {
453+
return Err(MapRegionError::Overlapping {
454+
new_start,
455+
new_end,
456+
existing_start: snap_start,
457+
existing_end: snap_end,
458+
});
459+
}
460+
}
461+
462+
// Check against the scratch region
463+
if let Some(ref scratch) = self.scratch_memory {
464+
let scratch_start =
465+
hyperlight_common::layout::scratch_base_gpa(scratch.mem_size()) as usize;
466+
let scratch_end = scratch_start + scratch.mem_size();
467+
if new_start < scratch_end && new_end > scratch_start {
468+
return Err(MapRegionError::Overlapping {
469+
new_start,
470+
new_end,
471+
existing_start: scratch_start,
472+
existing_end: scratch_end,
473+
});
474+
}
475+
}
476+
423477
// Try to reuse a freed slot first, otherwise use next_slot
424478
let slot = if let Some(freed_slot) = self.freed_slots.pop() {
425479
freed_slot

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 121 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -568,9 +568,10 @@ impl MultiUseSandbox {
568568
// writes can be rolled back when necessary.
569569
log_then_return!("TODO: Writable mappings not yet supported");
570570
}
571-
// Reset snapshot since we are mutating the sandbox state
572-
self.snapshot = None;
571+
572+
// Map first so overlaps are rejected before resetting the snapshot
573573
unsafe { self.vm.map_region(rgn) }.map_err(HyperlightVmError::MapRegion)?;
574+
self.snapshot = None;
574575
self.mem_mgr.mapped_rgns += 1;
575576
Ok(())
576577
}
@@ -642,25 +643,12 @@ impl MultiUseSandbox {
642643
// Phase 2: VM-side work (map into guest address space)
643644
let region = prepared.to_memory_region()?;
644645

645-
// Check for overlaps with existing file mappings in the VM.
646-
for existing_region in self.vm.get_mapped_regions() {
647-
let ex_start = existing_region.guest_region.start as u64;
648-
let ex_end = existing_region.guest_region.end as u64;
649-
if guest_base < ex_end && mapping_end > ex_start {
650-
return Err(crate::HyperlightError::Error(format!(
651-
"map_file_cow: mapping [{:#x}..{:#x}) overlaps existing mapping [{:#x}..{:#x})",
652-
guest_base, mapping_end, ex_start, ex_end,
653-
)));
654-
}
655-
}
656-
657-
// Reset snapshot since we are mutating the sandbox state
658-
self.snapshot = None;
659-
660646
unsafe { self.vm.map_region(&region) }
661647
.map_err(HyperlightVmError::MapRegion)
662648
.map_err(crate::HyperlightError::HyperlightVmError)?;
663649

650+
self.snapshot = None;
651+
664652
let size = prepared.size as u64;
665653

666654
// Mark consumed immediately after map_region succeeds.
@@ -2588,4 +2576,120 @@ mod tests {
25882576
}
25892577
let _ = std::fs::remove_file(&path);
25902578
}
2579+
2580+
#[test]
2581+
fn map_region_rejects_overlapping_regions() {
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+
2593+
// First mapping should succeed
2594+
unsafe { sbox.map_region(&region1).unwrap() };
2595+
2596+
// Exact same range should fail
2597+
let region2 = region_for_memory(&mem2, guest_base, MemoryRegionFlags::READ);
2598+
let err = unsafe { sbox.map_region(&region2) }.unwrap_err();
2599+
assert!(
2600+
format!("{err:?}").contains("Overlapping"),
2601+
"Expected Overlapping error, got: {err:?}"
2602+
);
2603+
}
2604+
2605+
#[test]
2606+
fn map_region_rejects_partial_overlap() {
2607+
let mut sbox: MultiUseSandbox = {
2608+
let path = simple_guest_as_string().unwrap();
2609+
let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2610+
u_sbox.evolve().unwrap()
2611+
};
2612+
2613+
// Use multi-page regions so partial overlap is geometrically possible
2614+
let mem1 = page_aligned_memory(&[0xAA; 8192]); // 2 pages
2615+
let mem2 = page_aligned_memory(&[0xBB; 8192]); // 2 pages
2616+
let guest_base: usize = 0x200000000;
2617+
let region1 = region_for_memory(&mem1, guest_base, MemoryRegionFlags::READ);
2618+
2619+
unsafe { sbox.map_region(&region1).unwrap() };
2620+
2621+
// region2 starts one page before region1, overlapping by one page
2622+
let overlap_base = guest_base - 0x1000;
2623+
let region2 = region_for_memory(&mem2, overlap_base, MemoryRegionFlags::READ);
2624+
let err = unsafe { sbox.map_region(&region2) }.unwrap_err();
2625+
assert!(
2626+
format!("{err:?}").contains("verlap"),
2627+
"Expected overlap error for partial overlap, got: {err:?}"
2628+
);
2629+
}
2630+
2631+
#[test]
2632+
fn map_region_allows_adjacent_non_overlapping() {
2633+
let mut sbox: MultiUseSandbox = {
2634+
let path = simple_guest_as_string().unwrap();
2635+
let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2636+
u_sbox.evolve().unwrap()
2637+
};
2638+
2639+
let mem1 = allocate_guest_memory();
2640+
let mem2 = allocate_guest_memory();
2641+
let guest_base: usize = 0x200000000;
2642+
let region1 = region_for_memory(&mem1, guest_base, MemoryRegionFlags::READ);
2643+
let region_size = mem1.mem_size();
2644+
2645+
unsafe { sbox.map_region(&region1).unwrap() };
2646+
2647+
// Adjacent region (starts right after the first one ends) should succeed
2648+
let adjacent_base = guest_base + region_size;
2649+
let region2 = region_for_memory(&mem2, adjacent_base, MemoryRegionFlags::READ);
2650+
unsafe { sbox.map_region(&region2).unwrap() };
2651+
}
2652+
2653+
#[test]
2654+
fn map_region_rejects_overlap_with_snapshot() {
2655+
let mut sbox: MultiUseSandbox = {
2656+
let path = simple_guest_as_string().unwrap();
2657+
let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2658+
u_sbox.evolve().unwrap()
2659+
};
2660+
2661+
// Try to map at BASE_ADDRESS (0x1000) which overlaps the snapshot region
2662+
let mem = allocate_guest_memory();
2663+
let region = region_for_memory(
2664+
&mem,
2665+
crate::mem::layout::SandboxMemoryLayout::BASE_ADDRESS,
2666+
MemoryRegionFlags::READ,
2667+
);
2668+
let err = unsafe { sbox.map_region(&region) }.unwrap_err();
2669+
assert!(
2670+
format!("{err:?}").contains("Overlapping"),
2671+
"Expected Overlapping error for snapshot overlap, got: {err:?}"
2672+
);
2673+
}
2674+
2675+
#[test]
2676+
fn map_region_rejects_overlap_with_scratch() {
2677+
let mut sbox: MultiUseSandbox = {
2678+
let path = simple_guest_as_string().unwrap();
2679+
let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2680+
u_sbox.evolve().unwrap()
2681+
};
2682+
2683+
// The scratch region occupies the top of the GPA space
2684+
let scratch_addr = hyperlight_common::layout::scratch_base_gpa(
2685+
crate::sandbox::SandboxConfiguration::DEFAULT_SCRATCH_SIZE,
2686+
) as usize;
2687+
let mem = allocate_guest_memory();
2688+
let region = region_for_memory(&mem, scratch_addr, MemoryRegionFlags::READ);
2689+
let err = unsafe { sbox.map_region(&region) }.unwrap_err();
2690+
assert!(
2691+
format!("{err:?}").contains("verlap"),
2692+
"Expected overlap error for scratch region, got: {err:?}"
2693+
);
2694+
}
25912695
}

0 commit comments

Comments
 (0)