1mod file;
18mod file_tests;
19
20use std::collections::{BTreeMap, HashMap};
21
22pub use file::reference::{OciDigest, OciReference, OciTag};
23use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
24use hyperlight_common::layout::{io_page, scratch_base_gpa, scratch_base_gva};
25use hyperlight_common::vmem;
26use hyperlight_common::vmem::{
27 BasicMapping, CowMapping, Mapping, MappingKind, PAGE_SIZE, SpaceAwareMapping, SpaceId, TableOps,
28};
29use tracing::{Span, instrument};
30
31use crate::Result;
32use crate::hypervisor::regs::CommonSpecialRegisters;
33use crate::mem::exe::{ExeInfo, LoadInfo};
34use crate::mem::layout::SandboxMemoryLayout;
35use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegion, MemoryRegionFlags};
36use crate::mem::mgr::{GuestPageTableBuffer, SnapshotSharedMemory};
37use crate::mem::shared_mem::{ReadonlySharedMemory, SharedMemory};
38use crate::sandbox::SandboxConfiguration;
39use crate::sandbox::uninitialized::{GuestBinary, GuestEnvironment};
40
41const PTE_SIZE: usize = size_of::<vmem::PageTableEntry>();
42
43#[derive(Copy, Clone, PartialEq, Eq)]
53pub enum NextAction {
54 Initialise(u64),
57 Call(u64),
60 #[cfg(test)]
63 None,
64}
65
66pub struct Snapshot {
69 layout: crate::mem::layout::SandboxMemoryLayout,
73 memory: ReadonlySharedMemory,
75 load_info: LoadInfo,
83 stack_top_gva: u64,
85
86 sregs: Option<CommonSpecialRegisters>,
92
93 entrypoint: NextAction,
95
96 snapshot_generation: u64,
102
103 host_functions: HostFunctionDetails,
109}
110impl core::convert::AsRef<Snapshot> for Snapshot {
111 fn as_ref(&self) -> &Self {
112 self
113 }
114}
115impl hyperlight_common::vmem::TableReadOps for Snapshot {
116 type TableAddr = u64;
117 fn entry_addr(addr: u64, offset: u64) -> u64 {
118 addr + offset
119 }
120 unsafe fn read_entry(&self, addr: u64) -> vmem::PageTableEntry {
121 let addr = addr as usize;
122 let Some(pte_bytes) = self.memory.as_slice().get(addr..addr + PTE_SIZE) else {
123 return 0;
128 };
129 #[allow(clippy::unwrap_used)]
131 vmem::PageTableEntry::from_le_bytes(pte_bytes.try_into().unwrap())
132 }
133 #[allow(clippy::unnecessary_cast)]
134 fn to_phys(addr: u64) -> vmem::PhysAddr {
135 addr as vmem::PhysAddr
136 }
137 #[allow(clippy::unnecessary_cast)]
138 fn from_phys(addr: vmem::PhysAddr) -> u64 {
139 addr as u64
140 }
141 fn root_table(&self) -> u64 {
142 self.root_pt_gpa()
143 }
144}
145
146pub(crate) fn access_gpa<'a>(
147 snap: &'a [u8],
148 scratch: &'a [u8],
149 layout: SandboxMemoryLayout,
150 gpa: u64,
151) -> Option<(&'a [u8], usize)> {
152 let resolved = layout.resolve_gpa(gpa, &[])?.with_memories(snap, scratch);
153 Some((resolved.base.as_ref(), resolved.offset))
154}
155
156pub(crate) struct SharedMemoryPageTableBuffer<'a> {
157 snap: &'a [u8],
158 scratch: &'a [u8],
159 layout: SandboxMemoryLayout,
160 root: u64,
161}
162impl<'a> SharedMemoryPageTableBuffer<'a> {
163 pub(crate) fn new(
164 snap: &'a [u8],
165 scratch: &'a [u8],
166 layout: SandboxMemoryLayout,
167 root: u64,
168 ) -> Self {
169 Self {
170 snap,
171 scratch,
172 layout,
173 root,
174 }
175 }
176}
177impl<'a> hyperlight_common::vmem::TableReadOps for SharedMemoryPageTableBuffer<'a> {
178 type TableAddr = u64;
179 fn entry_addr(addr: u64, offset: u64) -> u64 {
180 addr + offset
181 }
182 unsafe fn read_entry(&self, addr: u64) -> vmem::PageTableEntry {
183 let memoff = access_gpa(self.snap, self.scratch, self.layout, addr);
184 let Some(pte_bytes) = memoff.and_then(|(mem, off)| mem.get(off..off + PTE_SIZE)) else {
185 return 0;
190 };
191 #[allow(clippy::unwrap_used)]
193 vmem::PageTableEntry::from_le_bytes(pte_bytes.try_into().unwrap())
194 }
195 #[allow(clippy::unnecessary_cast)]
196 fn to_phys(addr: u64) -> vmem::PhysAddr {
197 addr as vmem::PhysAddr
198 }
199 #[allow(clippy::unnecessary_cast)]
200 fn from_phys(addr: vmem::PhysAddr) -> u64 {
201 addr as u64
202 }
203 fn root_table(&self) -> u64 {
204 self.root
205 }
206}
207impl<'a> core::convert::AsRef<SharedMemoryPageTableBuffer<'a>> for SharedMemoryPageTableBuffer<'a> {
208 fn as_ref(&self) -> &Self {
209 self
210 }
211}
212fn skip_virt(virt_base: u64, scratch_gva: u64) -> bool {
217 if virt_base >= scratch_gva {
218 return true;
219 }
220 if virt_base >= hyperlight_common::layout::SNAPSHOT_PT_GVA_MIN as u64
221 && virt_base <= hyperlight_common::layout::SNAPSHOT_PT_GVA_MAX as u64
222 {
223 return true;
224 }
225 false
226}
227
228unsafe fn guest_page<'a>(
236 snap: &'a [u8],
237 scratch: &'a [u8],
238 regions: &[MemoryRegion],
239 layout: SandboxMemoryLayout,
240 gpa: u64,
241) -> Option<&'a [u8]> {
242 let resolved = layout
243 .resolve_gpa(gpa, regions)?
244 .with_memories(snap, scratch);
245 if resolved.as_ref().len() < PAGE_SIZE {
246 return None;
247 }
248 Some(&resolved.as_ref()[..PAGE_SIZE])
249}
250
251fn map_specials(pt_buf: &GuestPageTableBuffer, scratch_size: usize) {
252 if let Some((phys_base, virt_base)) = io_page() {
253 let mapping = Mapping {
255 phys_base,
256 virt_base,
257 len: PAGE_SIZE as u64,
258 kind: MappingKind::Basic(BasicMapping {
259 readable: true,
260 writable: true,
261 executable: false,
262 }),
263 };
264 unsafe { vmem::map(pt_buf, mapping) };
265 }
266 let mapping = Mapping {
268 phys_base: scratch_base_gpa(scratch_size),
269 virt_base: scratch_base_gva(scratch_size),
270 len: scratch_size as u64,
271 kind: MappingKind::Basic(BasicMapping {
272 readable: true,
273 writable: true,
274 executable: false,
277 }),
278 };
279 unsafe { vmem::map(pt_buf, mapping) };
280}
281
282impl Snapshot {
283 pub(crate) fn from_env<'a, 'b>(
286 env: impl Into<GuestEnvironment<'a, 'b>>,
287 cfg: SandboxConfiguration,
288 ) -> Result<Self> {
289 let env = env.into();
290 let mut bin = env.guest_binary;
291 bin.canonicalize()?;
292 let blob = env.init_data;
293
294 let exe_info = match bin {
295 GuestBinary::FilePath(bin_path_str) => ExeInfo::from_file(&bin_path_str)?,
296 GuestBinary::Buffer(buffer) => ExeInfo::from_buf(buffer)?,
297 };
298
299 let host_version = env!("CARGO_PKG_VERSION");
301 if let Some(v) = exe_info.guest_bin_version()
302 && v != host_version
303 {
304 return Err(crate::HyperlightError::GuestBinVersionMismatch {
305 guest_bin_version: v.to_string(),
306 host_version: host_version.to_string(),
307 });
308 }
309
310 let guest_blob_size = blob.as_ref().map(|b| b.data.len()).unwrap_or(0);
311 let guest_blob_mem_flags = blob.as_ref().map(|b| b.permissions);
312
313 let mut layout = crate::mem::layout::SandboxMemoryLayout::new(
314 cfg,
315 exe_info.loaded_size(),
316 guest_blob_size,
317 guest_blob_mem_flags,
318 )?;
319
320 let load_addr = layout.get_guest_code_address() as u64;
321 let base_va = exe_info.base_va();
322 let entrypoint_va: u64 = exe_info.entrypoint().into();
323
324 let mut memory = vec![0; layout.get_memory_size()?];
325
326 let load_info = exe_info.load(
327 load_addr.try_into()?,
328 &mut memory[layout.get_guest_code_offset()..],
329 )?;
330
331 layout.write_peb(&mut memory)?;
332
333 blob.map(|x| layout.write_init_data(&mut memory, x.data))
334 .transpose()?;
335
336 let pt_buf = GuestPageTableBuffer::new(layout.get_pt_base_gpa() as usize);
338
339 for rgn in layout.get_memory_regions_::<GuestMemoryRegion>(())?.iter() {
341 let readable = rgn.flags.contains(MemoryRegionFlags::READ);
342 let executable = rgn.flags.contains(MemoryRegionFlags::EXECUTE);
343 let writable = rgn.flags.contains(MemoryRegionFlags::WRITE);
344 let kind = if writable {
345 MappingKind::Cow(CowMapping {
346 readable,
347 executable,
348 })
349 } else {
350 MappingKind::Basic(BasicMapping {
351 readable,
352 writable: false,
353 executable,
354 })
355 };
356 let mapping = Mapping {
357 phys_base: rgn.guest_region.start as u64,
358 virt_base: rgn.guest_region.start as u64,
359 len: rgn.guest_region.len() as u64,
360 kind,
361 };
362 unsafe { vmem::map(&pt_buf, mapping) };
363 }
364
365 map_specials(&pt_buf, layout.get_scratch_size());
367
368 let pt_bytes = pt_buf.into_bytes();
369 layout.set_pt_size(pt_bytes.len())?;
370 memory.extend(&pt_bytes);
371
372 let exn_stack_top_gva = hyperlight_common::layout::SCRATCH_TOP_GVA as u64
373 - hyperlight_common::layout::SCRATCH_TOP_EXN_STACK_OFFSET
374 + 1;
375
376 Ok(Self {
377 memory: ReadonlySharedMemory::from_bytes(&memory, layout.snapshot_size)?,
378 layout,
379 load_info,
380 stack_top_gva: exn_stack_top_gva,
381 sregs: None,
382 entrypoint: NextAction::Initialise(load_addr + entrypoint_va - base_va),
383 snapshot_generation: 0,
384 host_functions: HostFunctionDetails {
385 host_functions: None,
386 },
387 })
388 }
389
390 #[allow(clippy::too_many_arguments)]
396 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
399 pub(crate) fn new<S: SharedMemory>(
400 shared_mem: &mut SnapshotSharedMemory<S>,
401 scratch_mem: &mut S,
402 mut layout: SandboxMemoryLayout,
403 load_info: LoadInfo,
404 regions: Vec<MemoryRegion>,
405 root_pt_gpas: &[u64],
406 stack_top_gva: u64,
407 sregs: CommonSpecialRegisters,
408 entrypoint: NextAction,
409 snapshot_generation: u64,
410 host_functions: HostFunctionDetails,
411 ) -> Result<Self> {
412 let mut phys_seen = HashMap::<u64, usize>::new();
413 let scratch_gva = scratch_base_gva(layout.get_scratch_size());
414 let memory = shared_mem.with_contents(|snap_c| {
415 scratch_mem.with_contents(|scratch_c| {
416 let op = SharedMemoryPageTableBuffer::new(
427 snap_c,
428 scratch_c,
429 layout,
430 root_pt_gpas.first().copied().unwrap_or(0),
431 );
432 let walk = unsafe {
433 vmem::walk_va_spaces(
434 &op,
435 root_pt_gpas,
436 0,
437 hyperlight_common::layout::SCRATCH_TOP_GVA as u64,
438 )
439 };
440
441 let mut snapshot_memory: Vec<u8> = Vec::new();
447 let pt_buf = GuestPageTableBuffer::new(layout.get_pt_base_gpa() as usize);
448 let mut root_addrs: Vec<u64> = Vec::with_capacity(root_pt_gpas.len());
452 root_addrs.push(pt_buf.initial_root());
453 for _ in 1..root_pt_gpas.len() {
454 root_addrs.push(unsafe { pt_buf.alloc_table() });
455 }
456
457 let mut built_roots: BTreeMap<SpaceId, u64> = BTreeMap::new();
458 for (root_idx, (space_id, mappings)) in walk.into_iter().enumerate() {
459 pt_buf.set_root(root_addrs[root_idx]);
460 built_roots.insert(space_id, root_addrs[root_idx]);
461
462 for sam in mappings {
463 match sam {
464 SpaceAwareMapping::ThisSpace(mapping) => {
465 if skip_virt(mapping.virt_base, scratch_gva) {
470 continue;
471 }
472 let Some(contents) = (unsafe {
473 guest_page(
474 snap_c,
475 scratch_c,
476 ®ions,
477 layout,
478 mapping.phys_base,
479 )
480 }) else {
481 continue;
482 };
483
484 let kind = match mapping.kind {
488 MappingKind::Cow(cm) => MappingKind::Cow(cm),
489 MappingKind::Basic(bm) if bm.writable => {
490 MappingKind::Cow(CowMapping {
491 readable: bm.readable,
492 executable: bm.executable,
493 })
494 }
495 MappingKind::Basic(bm) => MappingKind::Basic(BasicMapping {
496 readable: bm.readable,
497 writable: false,
498 executable: bm.executable,
499 }),
500 MappingKind::Unmapped => continue,
501 };
502 let new_gpa =
503 phys_seen.entry(mapping.phys_base).or_insert_with(|| {
504 let new_offset = snapshot_memory.len();
505 snapshot_memory.extend(contents);
506 new_offset + SandboxMemoryLayout::BASE_ADDRESS
507 });
508
509 let compacted = Mapping {
510 phys_base: *new_gpa as u64,
511 virt_base: mapping.virt_base,
512 len: PAGE_SIZE as u64,
513 kind,
514 };
515 unsafe { vmem::map(&pt_buf, compacted) };
516 }
517 SpaceAwareMapping::AnotherSpace(ref_map) => {
518 unsafe {
524 vmem::space_aware_map(&pt_buf, ref_map, &built_roots);
525 }
526 }
527 }
528 }
529 }
530
531 for &root_addr in &root_addrs {
533 pt_buf.set_root(root_addr);
534 map_specials(&pt_buf, layout.get_scratch_size());
535 }
536 pt_buf.set_root(pt_buf.initial_root());
537
538 let pt_data = pt_buf.into_bytes();
540 layout.set_pt_size(pt_data.len())?;
541 snapshot_memory.extend(&pt_data);
542 Ok::<_, crate::HyperlightError>(snapshot_memory)
543 })
544 })???;
545 let guest_visible_size = memory.len() - layout.get_pt_size();
552 debug_assert!(guest_visible_size.is_multiple_of(PAGE_SIZE));
553 layout.set_snapshot_size(guest_visible_size);
554
555 Ok(Self {
556 layout,
557 memory: ReadonlySharedMemory::from_bytes(&memory, guest_visible_size)?,
558 load_info,
559 stack_top_gva,
560 sregs: Some(sregs),
561 entrypoint,
562 snapshot_generation,
563 host_functions,
564 })
565 }
566
567 pub(crate) fn snapshot_generation(&self) -> u64 {
569 self.snapshot_generation
570 }
571
572 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
574 pub(crate) fn memory(&self) -> &ReadonlySharedMemory {
575 &self.memory
576 }
577
578 pub(crate) fn load_info(&self) -> LoadInfo {
580 self.load_info.clone()
581 }
582
583 pub(crate) fn layout(&self) -> &crate::mem::layout::SandboxMemoryLayout {
584 &self.layout
585 }
586
587 pub(crate) fn root_pt_gpa(&self) -> u64 {
588 self.layout.get_pt_base_gpa()
589 }
590
591 pub(crate) fn stack_top_gva(&self) -> u64 {
592 self.stack_top_gva
593 }
594
595 pub(crate) fn sregs(&self) -> Option<&CommonSpecialRegisters> {
601 self.sregs.as_ref()
602 }
603
604 pub(crate) fn entrypoint(&self) -> NextAction {
605 self.entrypoint
606 }
607
608 pub(crate) fn validate_host_functions(
617 &self,
618 provided: &crate::sandbox::host_funcs::FunctionRegistry,
619 ) -> Result<()> {
620 let required = match &self.host_functions.host_functions {
621 Some(v) => v,
622 None => return Ok(()),
623 };
624 if required.is_empty() {
625 return Ok(());
626 }
627
628 let mut missing: Vec<String> = Vec::new();
629 let mut signature_mismatches: Vec<String> = Vec::new();
630
631 for req in required {
632 match provided.function_signature(&req.function_name) {
633 None => missing.push(req.function_name.clone()),
635 Some((found_parameter_types, found_return_type))
637 if {
638 let params_match = match req.parameter_types.as_deref() {
639 Some(params) => params == found_parameter_types,
640 None => found_parameter_types.is_empty(),
641 };
642 !params_match || req.return_type != found_return_type
643 } =>
644 {
645 signature_mismatches.push(format!(
646 "{}: snapshot has {:?} -> {:?}, registered {:?} -> {:?}",
647 req.function_name,
648 req.parameter_types,
649 req.return_type,
650 Some(found_parameter_types.to_vec()),
651 found_return_type,
652 ));
653 }
654 Some(_) => {}
656 }
657 }
658
659 if missing.is_empty() && signature_mismatches.is_empty() {
660 return Ok(());
661 }
662
663 Err(crate::HyperlightError::SnapshotHostFunctionMismatch {
664 missing,
665 signature_mismatches,
666 })
667 }
668
669 pub(crate) fn validate_compatibility(
679 &self,
680 layout: &crate::mem::layout::SandboxMemoryLayout,
681 host_funcs: &crate::sandbox::host_funcs::FunctionRegistry,
682 ) -> Result<()> {
683 if !self.layout().is_compatible_with(layout) {
684 return Err(crate::HyperlightError::SnapshotLayoutMismatch);
685 }
686 self.validate_host_functions(host_funcs)
687 }
688}
689
690#[cfg(test)]
691mod tests {
692 use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
693 use hyperlight_common::vmem::{self, BasicMapping, Mapping, MappingKind, PAGE_SIZE};
694
695 use crate::hypervisor::regs::CommonSpecialRegisters;
696 use crate::mem::exe::LoadInfo;
697 use crate::mem::layout::SandboxMemoryLayout;
698 use crate::mem::mgr::{GuestPageTableBuffer, SandboxMemoryManager, SnapshotSharedMemory};
699 use crate::mem::shared_mem::{
700 ExclusiveSharedMemory, HostSharedMemory, ReadonlySharedMemory, SharedMemory,
701 };
702
703 fn default_sregs() -> CommonSpecialRegisters {
704 CommonSpecialRegisters::default()
705 }
706
707 const SIMPLE_PT_BASE: usize = PAGE_SIZE + SandboxMemoryLayout::BASE_ADDRESS;
708
709 fn make_simple_pt_mem(contents: &[u8]) -> SnapshotSharedMemory<ExclusiveSharedMemory> {
710 let pt_buf = GuestPageTableBuffer::new(SIMPLE_PT_BASE);
711 let mapping = Mapping {
712 phys_base: SandboxMemoryLayout::BASE_ADDRESS as u64,
713 virt_base: SandboxMemoryLayout::BASE_ADDRESS as u64,
714 len: PAGE_SIZE as u64,
715 kind: MappingKind::Basic(BasicMapping {
716 readable: true,
717 writable: true,
718 executable: true,
719 }),
720 };
721 unsafe { vmem::map(&pt_buf, mapping) };
722 super::map_specials(&pt_buf, PAGE_SIZE);
723 let pt_bytes = pt_buf.into_bytes();
724
725 let mut snapshot_mem = vec![0u8; PAGE_SIZE + pt_bytes.len()];
726 snapshot_mem[0..PAGE_SIZE].copy_from_slice(contents);
727 snapshot_mem[PAGE_SIZE..].copy_from_slice(&pt_bytes);
728 ReadonlySharedMemory::from_bytes(&snapshot_mem, PAGE_SIZE)
729 .unwrap()
730 .to_mgr_snapshot_mem()
731 .unwrap()
732 }
733
734 fn make_simple_pt_mgr() -> (SandboxMemoryManager<HostSharedMemory>, u64) {
735 let cfg = crate::sandbox::SandboxConfiguration::default();
736 let scratch_mem = ExclusiveSharedMemory::new(cfg.get_scratch_size()).unwrap();
737 let mgr = SandboxMemoryManager::new(
738 SandboxMemoryLayout::new(cfg, 4096, 0x3000, None).unwrap(),
739 make_simple_pt_mem(&[0u8; PAGE_SIZE]),
740 scratch_mem,
741 super::NextAction::None,
742 );
743 let (mgr, _) = mgr.build().unwrap();
744 (mgr, SIMPLE_PT_BASE as u64)
745 }
746
747 #[test]
748 fn multiple_snapshots_independent() {
749 let (mut mgr, pt_base) = make_simple_pt_mgr();
750
751 let pattern_a = vec![0xAA; PAGE_SIZE];
753 let snapshot_a = super::Snapshot::new(
754 &mut make_simple_pt_mem(&pattern_a).build().0,
755 &mut mgr.scratch_mem,
756 mgr.layout,
757 LoadInfo::dummy(),
758 Vec::new(),
759 &[pt_base],
760 0,
761 default_sregs(),
762 super::NextAction::None,
763 1,
764 HostFunctionDetails::default(),
765 )
766 .unwrap();
767
768 let pattern_b = vec![0xBB; PAGE_SIZE];
770 let snapshot_b = super::Snapshot::new(
771 &mut make_simple_pt_mem(&pattern_b).build().0,
772 &mut mgr.scratch_mem,
773 mgr.layout,
774 LoadInfo::dummy(),
775 Vec::new(),
776 &[pt_base],
777 0,
778 default_sregs(),
779 super::NextAction::None,
780 2,
781 HostFunctionDetails::default(),
782 )
783 .unwrap();
784
785 mgr.restore_snapshot(&snapshot_a).unwrap();
787 mgr.shared_mem
788 .with_contents(|contents| assert_eq!(&contents[0..pattern_a.len()], &pattern_a[..]))
789 .unwrap();
790
791 mgr.restore_snapshot(&snapshot_b).unwrap();
793 mgr.shared_mem
794 .with_contents(|contents| assert_eq!(&contents[0..pattern_b.len()], &pattern_b[..]))
795 .unwrap();
796 }
797}