1use flatbuffers::FlatBufferBuilder;
18use hyperlight_common::flatbuffer_wrappers::function_call::{
19 FunctionCall, validate_guest_function_call_buffer,
20};
21use hyperlight_common::flatbuffer_wrappers::function_types::FunctionCallResult;
22use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData;
23use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
24use hyperlight_common::vmem::{self, PAGE_TABLE_SIZE};
25#[cfg(crashdump)]
26use hyperlight_common::vmem::{BasicMapping, MappingKind};
27use tracing::{Span, instrument};
28
29use super::layout::SandboxMemoryLayout;
30use super::shared_mem::{
31 ExclusiveSharedMemory, GuestSharedMemory, HostSharedMemory, ReadonlySharedMemory, SharedMemory,
32};
33use crate::hypervisor::regs::CommonSpecialRegisters;
34use crate::mem::memory_region::MemoryRegion;
35#[cfg(crashdump)]
36use crate::mem::memory_region::{CrashDumpRegion, MemoryRegionFlags, MemoryRegionType};
37use crate::sandbox::snapshot::{NextAction, Snapshot};
38use crate::{Result, new_error};
39
40#[cfg(crashdump)]
41fn mapping_kind_to_flags(kind: &MappingKind) -> (MemoryRegionFlags, MemoryRegionType) {
42 match kind {
43 MappingKind::Basic(BasicMapping {
44 readable,
45 writable,
46 executable,
47 }) => {
48 let mut flags = MemoryRegionFlags::empty();
49 if *readable {
50 flags |= MemoryRegionFlags::READ;
51 }
52 if *writable {
53 flags |= MemoryRegionFlags::WRITE;
54 }
55 if *executable {
56 flags |= MemoryRegionFlags::EXECUTE;
57 }
58 (flags, MemoryRegionType::Snapshot)
59 }
60 MappingKind::Cow(cow) => {
61 let mut flags = MemoryRegionFlags::empty();
62 if cow.readable {
63 flags |= MemoryRegionFlags::READ;
64 }
65 if cow.executable {
66 flags |= MemoryRegionFlags::EXECUTE;
67 }
68 (flags, MemoryRegionType::Scratch)
69 }
70 MappingKind::Unmapped => (MemoryRegionFlags::empty(), MemoryRegionType::Snapshot),
71 }
72}
73
74#[cfg(crashdump)]
79fn try_coalesce_region(
80 regions: &mut [CrashDumpRegion],
81 virt_base: usize,
82 virt_end: usize,
83 host_base: usize,
84 flags: MemoryRegionFlags,
85) -> bool {
86 if let Some(last) = regions.last_mut()
87 && last.guest_region.end == virt_base
88 && last.host_region.end == host_base
89 && last.flags == flags
90 {
91 last.guest_region.end = virt_end;
92 last.host_region.end = host_base + (virt_end - virt_base);
93 return true;
94 }
95 false
96}
97
98mod unused_hack {
106 #[cfg(not(unshared_snapshot_mem))]
107 use crate::mem::shared_mem::ReadonlySharedMemory;
108 use crate::mem::shared_mem::SharedMemory;
109 pub trait SnapshotSharedMemoryT {
110 type T<S: SharedMemory>;
111 }
112 pub struct SnapshotSharedMemory_;
113 impl SnapshotSharedMemoryT for SnapshotSharedMemory_ {
114 #[cfg(not(unshared_snapshot_mem))]
115 type T<S: SharedMemory> = ReadonlySharedMemory;
116 #[cfg(unshared_snapshot_mem)]
117 type T<S: SharedMemory> = S;
118 }
119 pub type SnapshotSharedMemory<S> = <SnapshotSharedMemory_ as SnapshotSharedMemoryT>::T<S>;
120}
121impl ReadonlySharedMemory {
122 pub(crate) fn to_mgr_snapshot_mem(
123 &self,
124 ) -> Result<SnapshotSharedMemory<ExclusiveSharedMemory>> {
125 #[cfg(not(unshared_snapshot_mem))]
126 let ret = self.clone();
127 #[cfg(unshared_snapshot_mem)]
128 let ret = self.copy_to_writable()?;
129 Ok(ret)
130 }
131}
132pub(crate) use unused_hack::SnapshotSharedMemory;
133#[derive(Clone)]
136pub(crate) struct SandboxMemoryManager<S: SharedMemory> {
137 pub(crate) shared_mem: SnapshotSharedMemory<S>,
139 pub(crate) scratch_mem: S,
141 pub(crate) layout: SandboxMemoryLayout,
143 pub(crate) entrypoint: NextAction,
145 pub(crate) abort_buffer: Vec<u8>,
147 pub(crate) snapshot_count: u64,
153}
154
155pub(crate) struct GuestPageTableBuffer {
159 buffer: std::cell::RefCell<Vec<u8>>,
160 phys_base: usize,
161 root: std::cell::Cell<u64>,
166}
167
168impl vmem::TableReadOps for GuestPageTableBuffer {
169 type TableAddr = u64;
170
171 fn entry_addr(addr: u64, offset: u64) -> u64 {
172 addr + offset
173 }
174
175 unsafe fn read_entry(&self, addr: u64) -> vmem::PageTableEntry {
176 let buffer = self.buffer.borrow();
177 let byte_offset = addr as usize - self.phys_base;
178 let pte_size = core::mem::size_of::<vmem::PageTableEntry>();
179 let Some(bytes) = buffer.get(byte_offset..byte_offset + pte_size) else {
180 return 0;
181 };
182 let mut buf = [0u8; 8];
183 buf[..pte_size].copy_from_slice(bytes);
184 vmem::PageTableEntry::from_le_bytes(buf[..pte_size].try_into().unwrap_or_default())
185 }
186
187 fn to_phys(addr: u64) -> vmem::PhysAddr {
188 addr as vmem::PhysAddr
189 }
190
191 fn from_phys(addr: vmem::PhysAddr) -> u64 {
192 #[allow(clippy::unnecessary_cast)]
193 {
194 addr as u64
195 }
196 }
197
198 fn root_table(&self) -> u64 {
199 self.root.get()
200 }
201}
202
203impl vmem::TableOps for GuestPageTableBuffer {
204 type TableMovability = vmem::MayNotMoveTable;
205
206 unsafe fn alloc_table(&self) -> u64 {
207 let mut b = self.buffer.borrow_mut();
208 let offset = b.len();
209 b.resize(offset + PAGE_TABLE_SIZE, 0);
210 (self.phys_base + offset) as u64
211 }
212
213 unsafe fn write_entry(&self, addr: u64, entry: vmem::PageTableEntry) -> Option<vmem::Void> {
214 let mut b = self.buffer.borrow_mut();
215 let byte_offset = addr as usize - self.phys_base;
216 let pte_size = core::mem::size_of::<vmem::PageTableEntry>();
217 if let Some(slice) = b.get_mut(byte_offset..byte_offset + pte_size) {
218 slice.copy_from_slice(&entry.to_le_bytes()[..pte_size]);
219 }
220 None
221 }
222
223 unsafe fn update_root(&self, impossible: vmem::Void) {
224 match impossible {}
225 }
226}
227
228impl core::convert::AsRef<GuestPageTableBuffer> for GuestPageTableBuffer {
229 fn as_ref(&self) -> &Self {
230 self
231 }
232}
233
234impl GuestPageTableBuffer {
235 pub(crate) fn new(phys_base: usize) -> Self {
239 GuestPageTableBuffer {
240 buffer: std::cell::RefCell::new(vec![0u8; PAGE_TABLE_SIZE]),
241 phys_base,
242 root: std::cell::Cell::new(phys_base as u64),
243 }
244 }
245
246 pub(crate) fn set_root(&self, addr: u64) {
249 self.root.set(addr);
250 }
251
252 pub(crate) fn initial_root(&self) -> u64 {
254 self.phys_base as u64
255 }
256
257 #[cfg(test)]
258 #[allow(dead_code)]
259 pub(crate) fn size(&self) -> usize {
260 self.buffer.borrow().len()
261 }
262
263 pub(crate) fn into_bytes(self) -> Box<[u8]> {
264 self.buffer.into_inner().into_boxed_slice()
265 }
266}
267
268impl<S> SandboxMemoryManager<S>
269where
270 S: SharedMemory,
271{
272 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
274 pub(crate) fn new(
275 layout: SandboxMemoryLayout,
276 shared_mem: SnapshotSharedMemory<S>,
277 scratch_mem: S,
278 entrypoint: NextAction,
279 ) -> Self {
280 Self {
281 layout,
282 shared_mem,
283 scratch_mem,
284 entrypoint,
285 abort_buffer: Vec::new(),
286 snapshot_count: 0,
287 }
288 }
289
290 pub(crate) fn get_abort_buffer_mut(&mut self) -> &mut Vec<u8> {
292 &mut self.abort_buffer
293 }
294
295 #[allow(clippy::too_many_arguments)]
297 pub(crate) fn snapshot(
298 &mut self,
299 mapped_regions: Vec<MemoryRegion>,
300 root_pt_gpas: &[u64],
301 rsp_gva: u64,
302 sregs: CommonSpecialRegisters,
303 entrypoint: NextAction,
304 host_functions: HostFunctionDetails,
305 ) -> Result<Snapshot> {
306 self.snapshot_count += 1;
307 Snapshot::new(
308 &mut self.shared_mem,
309 &mut self.scratch_mem,
310 self.layout,
311 crate::mem::exe::LoadInfo::dummy(),
312 mapped_regions,
313 root_pt_gpas,
314 rsp_gva,
315 sregs,
316 entrypoint,
317 self.snapshot_count,
318 host_functions,
319 )
320 }
321}
322
323impl SandboxMemoryManager<ExclusiveSharedMemory> {
324 pub(crate) fn from_snapshot(s: &Snapshot) -> Result<Self> {
325 let layout = *s.layout();
326 let shared_mem = s.memory().to_mgr_snapshot_mem()?;
327 let scratch_mem = ExclusiveSharedMemory::new(s.layout().get_scratch_size())?;
328 let entrypoint = s.entrypoint();
329 let mut mgr = Self::new(layout, shared_mem, scratch_mem, entrypoint);
330 mgr.snapshot_count = s.snapshot_generation();
335 Ok(mgr)
336 }
337
338 pub fn build(
349 self,
350 ) -> Result<(
351 SandboxMemoryManager<HostSharedMemory>,
352 SandboxMemoryManager<GuestSharedMemory>,
353 )> {
354 let (hshm, gshm) = self.shared_mem.build();
355 let (hscratch, gscratch) = self.scratch_mem.build();
356 let mut host_mgr = SandboxMemoryManager {
357 shared_mem: hshm,
358 scratch_mem: hscratch,
359 layout: self.layout,
360 entrypoint: self.entrypoint,
361 abort_buffer: self.abort_buffer,
362 snapshot_count: self.snapshot_count,
363 };
364 let guest_mgr = SandboxMemoryManager {
365 shared_mem: gshm,
366 scratch_mem: gscratch,
367 layout: self.layout,
368 entrypoint: self.entrypoint,
369 abort_buffer: Vec::new(), snapshot_count: self.snapshot_count,
371 };
372 host_mgr.update_scratch_bookkeeping()?;
373 Ok((host_mgr, guest_mgr))
374 }
375}
376
377impl SandboxMemoryManager<HostSharedMemory> {
378 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
380 pub(crate) fn get_host_function_call(&mut self) -> Result<FunctionCall> {
381 self.scratch_mem.try_pop_buffer_into::<FunctionCall>(
382 self.layout.get_output_data_buffer_scratch_host_offset(),
383 self.layout.output_data_size,
384 )
385 }
386
387 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
389 pub(crate) fn write_response_from_host_function_call(
390 &mut self,
391 res: &FunctionCallResult,
392 ) -> Result<()> {
393 let mut builder = FlatBufferBuilder::new();
394 let data = res.encode(&mut builder);
395
396 self.scratch_mem.push_buffer(
397 self.layout.get_input_data_buffer_scratch_host_offset(),
398 self.layout.input_data_size,
399 data,
400 )
401 }
402
403 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
405 pub(crate) fn write_guest_function_call(&mut self, buffer: &[u8]) -> Result<()> {
406 validate_guest_function_call_buffer(buffer).map_err(|e| {
407 new_error!(
408 "Guest function call buffer validation failed: {}",
409 e.to_string()
410 )
411 })?;
412
413 self.scratch_mem.push_buffer(
414 self.layout.get_input_data_buffer_scratch_host_offset(),
415 self.layout.input_data_size,
416 buffer,
417 )?;
418 Ok(())
419 }
420
421 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
424 pub(crate) fn get_guest_function_call_result(&mut self) -> Result<FunctionCallResult> {
425 self.scratch_mem.try_pop_buffer_into::<FunctionCallResult>(
426 self.layout.get_output_data_buffer_scratch_host_offset(),
427 self.layout.output_data_size,
428 )
429 }
430
431 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
433 pub(crate) fn read_guest_log_data(&mut self) -> Result<GuestLogData> {
434 self.scratch_mem.try_pop_buffer_into::<GuestLogData>(
435 self.layout.get_output_data_buffer_scratch_host_offset(),
436 self.layout.output_data_size,
437 )
438 }
439
440 pub(crate) fn clear_io_buffers(&mut self) {
441 loop {
443 let Ok(_) = self.scratch_mem.try_pop_buffer_into::<Vec<u8>>(
444 self.layout.get_output_data_buffer_scratch_host_offset(),
445 self.layout.output_data_size,
446 ) else {
447 break;
448 };
449 }
450 loop {
452 let Ok(_) = self.scratch_mem.try_pop_buffer_into::<Vec<u8>>(
453 self.layout.get_input_data_buffer_scratch_host_offset(),
454 self.layout.input_data_size,
455 ) else {
456 break;
457 };
458 }
459 }
460
461 pub(crate) fn restore_snapshot(
463 &mut self,
464 snapshot: &Snapshot,
465 ) -> Result<(
466 Option<SnapshotSharedMemory<GuestSharedMemory>>,
467 Option<GuestSharedMemory>,
468 )> {
469 let gsnapshot = if *snapshot.memory() == self.shared_mem {
470 None
478 } else {
479 let new_snapshot_mem = snapshot.memory().to_mgr_snapshot_mem()?;
480 let (hsnapshot, gsnapshot) = new_snapshot_mem.build();
481 self.shared_mem = hsnapshot;
482 Some(gsnapshot)
483 };
484 let new_scratch_size = snapshot.layout().get_scratch_size();
485 let gscratch = if new_scratch_size == self.scratch_mem.mem_size() {
486 self.scratch_mem.zero()?;
487 None
488 } else {
489 let new_scratch_mem = ExclusiveSharedMemory::new(new_scratch_size)?;
490 let (hscratch, gscratch) = new_scratch_mem.build();
491 self.scratch_mem = hscratch;
497
498 Some(gscratch)
499 };
500 self.layout = *snapshot.layout();
501 self.snapshot_count = snapshot.snapshot_generation();
506
507 self.update_scratch_bookkeeping()?;
508 Ok((gsnapshot, gscratch))
509 }
510
511 #[inline]
512 fn update_scratch_bookkeeping_item(&mut self, offset: u64, value: u64) -> Result<()> {
513 let scratch_size = self.scratch_mem.mem_size();
514 let base_offset = scratch_size - offset as usize;
515 self.scratch_mem.write::<u64>(base_offset, value)
516 }
517
518 fn update_scratch_bookkeeping(&mut self) -> Result<()> {
519 use hyperlight_common::layout::*;
520 let scratch_size = self.scratch_mem.mem_size();
521 self.update_scratch_bookkeeping_item(SCRATCH_TOP_SIZE_OFFSET, scratch_size as u64)?;
522 self.update_scratch_bookkeeping_item(
523 SCRATCH_TOP_ALLOCATOR_OFFSET,
524 self.layout.get_first_free_scratch_gpa(),
525 )?;
526 self.update_scratch_bookkeeping_item(
535 SCRATCH_TOP_SNAPSHOT_PT_GPA_BASE_OFFSET,
536 self.layout.get_pt_base_gpa(),
537 )?;
538 self.update_scratch_bookkeeping_item(
539 SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET,
540 self.snapshot_count,
541 )?;
542
543 self.scratch_mem.write::<u64>(
546 self.layout.get_input_data_buffer_scratch_host_offset(),
547 SandboxMemoryLayout::STACK_POINTER_SIZE_BYTES,
548 )?;
549 self.scratch_mem.write::<u64>(
550 self.layout.get_output_data_buffer_scratch_host_offset(),
551 SandboxMemoryLayout::STACK_POINTER_SIZE_BYTES,
552 )?;
553
554 let snapshot_pt_end = self.shared_mem.mem_size();
561 let snapshot_pt_size = self.layout.get_pt_size();
562 let snapshot_pt_start = snapshot_pt_end - snapshot_pt_size;
563 self.scratch_mem.with_exclusivity(|scratch| {
564 #[cfg(not(unshared_snapshot_mem))]
565 let bytes = &self.shared_mem.as_slice()[snapshot_pt_start..snapshot_pt_end];
566 #[cfg(unshared_snapshot_mem)]
567 let bytes = {
568 let mut bytes = vec![0u8; snapshot_pt_size];
569 self.shared_mem
570 .copy_to_slice(&mut bytes, snapshot_pt_start)?;
571 bytes
572 };
573 #[allow(clippy::needless_borrow)]
574 scratch.copy_from_slice(&bytes, self.layout.get_pt_base_scratch_offset())
575 })??;
576
577 Ok(())
578 }
579
580 #[cfg(crashdump)]
585 pub(crate) fn get_guest_memory_regions(
586 &mut self,
587 root_pt: u64,
588 mmap_regions: &[MemoryRegion],
589 ) -> Result<Vec<CrashDumpRegion>> {
590 use crate::sandbox::snapshot::SharedMemoryPageTableBuffer;
591
592 let len = hyperlight_common::layout::SCRATCH_TOP_GVA;
593
594 let regions = self.shared_mem.with_contents(|snapshot| {
595 self.scratch_mem.with_contents(|scratch| {
596 let pt_buf =
597 SharedMemoryPageTableBuffer::new(snapshot, scratch, self.layout, root_pt);
598
599 let mappings: Vec<_> =
600 unsafe { hyperlight_common::vmem::virt_to_phys(&pt_buf, 0, len as u64) }
601 .collect();
602
603 if mappings.is_empty() {
604 return Err(new_error!("No page table mappings found (len {len})",));
605 }
606
607 let mut regions: Vec<CrashDumpRegion> = Vec::new();
608 for mapping in &mappings {
609 let virt_base = mapping.virt_base as usize;
610 let virt_end = (mapping.virt_base + mapping.len) as usize;
611
612 if let Some(resolved) = self.layout.resolve_gpa(mapping.phys_base, mmap_regions)
613 {
614 let (flags, region_type) = mapping_kind_to_flags(&mapping.kind);
615 let resolved = resolved.with_memories(snapshot, scratch);
616 let contents = resolved.as_ref();
617 let host_base = contents.as_ptr() as usize;
618 let host_len = (mapping.len as usize).min(contents.len());
619
620 if try_coalesce_region(&mut regions, virt_base, virt_end, host_base, flags)
621 {
622 continue;
623 }
624
625 regions.push(CrashDumpRegion {
626 guest_region: virt_base..virt_end,
627 host_region: host_base..host_base + host_len,
628 flags,
629 region_type,
630 });
631 }
632 }
633
634 Ok(regions)
635 })
636 })???;
637
638 Ok(regions)
639 }
640
641 #[cfg(feature = "trace_guest")]
654 pub(crate) fn read_guest_memory_by_gva(
655 &mut self,
656 gva: u64,
657 len: usize,
658 root_pt: u64,
659 ) -> Result<Vec<u8>> {
660 use hyperlight_common::vmem::PAGE_SIZE;
661
662 use crate::sandbox::snapshot::{SharedMemoryPageTableBuffer, access_gpa};
663
664 self.shared_mem.with_contents(|snap| {
665 self.scratch_mem.with_contents(|scratch| {
666 let pt_buf = SharedMemoryPageTableBuffer::new(snap, scratch, self.layout, root_pt);
667
668 let mappings: Vec<_> = unsafe {
670 hyperlight_common::vmem::virt_to_phys(&pt_buf, gva, len as u64)
671 }
672 .collect();
673
674 if mappings.is_empty() {
675 return Err(new_error!(
676 "No page table mappings found for GVA {:#x} (len {})",
677 gva,
678 len,
679 ));
680 }
681
682 let mut result = Vec::with_capacity(len);
684 let mut current_gva = gva;
685
686 for mapping in &mappings {
687 if mapping.virt_base > current_gva {
690 return Err(new_error!(
691 "Page table walker returned mapping with virt_base {:#x} > current read position {:#x}",
692 mapping.virt_base,
693 current_gva,
694 ));
695 }
696
697 let page_offset = (current_gva - mapping.virt_base) as usize;
699
700 let bytes_remaining = len - result.len();
701 let available_in_page = PAGE_SIZE - page_offset;
702 let bytes_to_copy = bytes_remaining.min(available_in_page);
703
704 let gpa = mapping.phys_base + page_offset as u64;
706 let (mem, offset) = access_gpa(snap, scratch, self.layout, gpa)
707 .ok_or_else(|| {
708 new_error!(
709 "Failed to resolve GPA {:#x} to host memory (GVA {:#x})",
710 gpa,
711 gva
712 )
713 })?;
714
715 let slice = mem
716 .get(offset..offset + bytes_to_copy)
717 .ok_or_else(|| {
718 new_error!(
719 "GPA {:#x} resolved to out-of-bounds host offset {} (need {} bytes)",
720 gpa,
721 offset,
722 bytes_to_copy
723 )
724 })?;
725
726 result.extend_from_slice(slice);
727 current_gva += bytes_to_copy as u64;
728 }
729
730 if result.len() != len {
731 tracing::error!(
732 "Page table walker returned mappings that don't cover the full requested length: got {}, expected {}",
733 result.len(),
734 len,
735 );
736 return Err(new_error!(
737 "Could not read full GVA range: got {} of {} bytes {:?}",
738 result.len(),
739 len,
740 mappings
741 ));
742 }
743
744 Ok(result)
745 })
746 })??
747 }
748}
749
750#[cfg(test)]
751#[cfg(target_arch = "x86_64")]
752mod tests {
753 use hyperlight_testing::sandbox_sizes::{LARGE_HEAP_SIZE, MEDIUM_HEAP_SIZE, SMALL_HEAP_SIZE};
754 use hyperlight_testing::simple_guest_as_string;
755
756 use crate::GuestBinary;
757 use crate::sandbox::SandboxConfiguration;
758 use crate::sandbox::snapshot::Snapshot;
759
760 fn verify_page_tables(name: &str, config: SandboxConfiguration) {
763 let path = simple_guest_as_string().expect("failed to get simple guest path");
764 let snapshot = Snapshot::from_env(GuestBinary::FilePath(path), config)
765 .unwrap_or_else(|e| panic!("{}: failed to create snapshot: {}", name, e));
766
767 assert!(
769 unsafe { hyperlight_common::vmem::virt_to_phys(&snapshot, 0, 1) }
770 .next()
771 .is_none(),
772 "{}: NULL page (0x0) should NOT be mapped",
773 name
774 );
775 }
776
777 #[test]
778 fn test_page_tables_for_various_configurations() {
779 let test_cases: [(&str, SandboxConfiguration); 4] = [
780 ("default", { SandboxConfiguration::default() }),
781 ("small (8MB heap)", {
782 let mut cfg = SandboxConfiguration::default();
783 cfg.set_heap_size(SMALL_HEAP_SIZE);
784 cfg
785 }),
786 ("medium (64MB heap)", {
787 let mut cfg = SandboxConfiguration::default();
788 cfg.set_heap_size(MEDIUM_HEAP_SIZE);
789 cfg
790 }),
791 ("large (256MB heap)", {
792 let mut cfg = SandboxConfiguration::default();
793 cfg.set_heap_size(LARGE_HEAP_SIZE);
794 cfg.set_scratch_size(0x100000);
795 cfg
796 }),
797 ];
798
799 for (name, config) in test_cases {
800 verify_page_tables(name, config);
801 }
802 }
803}