1mod file;
5mod file_tests;
6mod tripwires;
7
8use std::collections::{BTreeMap, HashMap};
9
10pub(crate) use file::host_cpu_vendor_golden_tag;
11pub use file::reference::{OciDigest, OciReference, OciTag};
12use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
13use hyperlight_common::layout::{io_page, scratch_base_gpa, scratch_base_gva};
14use hyperlight_common::vmem;
15use hyperlight_common::vmem::{
16 BasicMapping, CowMapping, Mapping, MappingKind, PAGE_SIZE, SpaceAwareMapping, SpaceId, TableOps,
17};
18use tracing::{Span, instrument};
19
20use crate::Result;
21use crate::hypervisor::regs::CommonSpecialRegisters;
22#[cfg(target_arch = "x86_64")]
23use crate::hypervisor::regs::MsrEntry;
24use crate::mem::exe::{ExeInfo, LoadInfo};
25use crate::mem::layout::SandboxMemoryLayout;
26use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegion, MemoryRegionFlags};
27use crate::mem::mgr::{GuestPageTableBuffer, SnapshotSharedMemory};
28use crate::mem::shared_mem::{ReadonlySharedMemory, SharedMemory};
29use crate::sandbox::SandboxConfiguration;
30use crate::sandbox::uninitialized::{GuestBinary, GuestEnvironment};
31
32const PTE_SIZE: usize = size_of::<vmem::PageTableEntry>();
33
34#[derive(Copy, Clone, PartialEq, Eq)]
44pub enum NextAction {
45 Initialise(u64),
48 Call(u64),
51 #[cfg(test)]
54 None,
55}
56
57pub struct Snapshot {
60 layout: crate::mem::layout::SandboxMemoryLayout,
64 memory: ReadonlySharedMemory,
66 load_info: LoadInfo,
74 stack_top_gva: u64,
76
77 sregs: Option<CommonSpecialRegisters>,
83
84 #[cfg(target_arch = "x86_64")]
86 msrs: Option<Vec<MsrEntry>>,
87
88 next_action: NextAction,
90
91 original_entrypoint: u64,
99
100 snapshot_generation: u64,
106
107 host_functions: HostFunctionDetails,
113}
114impl core::convert::AsRef<Snapshot> for Snapshot {
115 fn as_ref(&self) -> &Self {
116 self
117 }
118}
119impl hyperlight_common::vmem::TableReadOps for Snapshot {
120 type TableAddr = u64;
121 fn entry_addr(addr: u64, offset: u64) -> u64 {
122 addr + offset
123 }
124 unsafe fn read_entry(&self, addr: u64) -> vmem::PageTableEntry {
125 let addr = addr as usize;
126 let Some(pte_bytes) = self.memory.as_slice().get(addr..addr + PTE_SIZE) else {
127 return 0;
132 };
133 #[allow(clippy::unwrap_used)]
135 vmem::PageTableEntry::from_le_bytes(pte_bytes.try_into().unwrap())
136 }
137 #[allow(clippy::unnecessary_cast)]
138 fn to_phys(addr: u64) -> vmem::PhysAddr {
139 addr as vmem::PhysAddr
140 }
141 #[allow(clippy::unnecessary_cast)]
142 fn from_phys(addr: vmem::PhysAddr) -> u64 {
143 addr as u64
144 }
145 fn root_table(&self) -> u64 {
146 self.root_pt_gpa()
147 }
148}
149
150pub(crate) fn access_gpa<'a>(
151 snap: &'a [u8],
152 scratch: &'a [u8],
153 layout: SandboxMemoryLayout,
154 gpa: u64,
155) -> Option<(&'a [u8], usize)> {
156 let resolved = layout.resolve_gpa(gpa, &[])?.with_memories(snap, scratch);
157 Some((resolved.base.as_ref(), resolved.offset))
158}
159
160pub(crate) struct SharedMemoryPageTableBuffer<'a> {
161 snap: &'a [u8],
162 scratch: &'a [u8],
163 layout: SandboxMemoryLayout,
164 root: u64,
165}
166impl<'a> SharedMemoryPageTableBuffer<'a> {
167 pub(crate) fn new(
168 snap: &'a [u8],
169 scratch: &'a [u8],
170 layout: SandboxMemoryLayout,
171 root: u64,
172 ) -> Self {
173 Self {
174 snap,
175 scratch,
176 layout,
177 root,
178 }
179 }
180}
181impl<'a> hyperlight_common::vmem::TableReadOps for SharedMemoryPageTableBuffer<'a> {
182 type TableAddr = u64;
183 fn entry_addr(addr: u64, offset: u64) -> u64 {
184 addr + offset
185 }
186 unsafe fn read_entry(&self, addr: u64) -> vmem::PageTableEntry {
187 let memoff = access_gpa(self.snap, self.scratch, self.layout, addr);
188 let Some(pte_bytes) = memoff.and_then(|(mem, off)| mem.get(off..off + PTE_SIZE)) else {
189 return 0;
194 };
195 #[allow(clippy::unwrap_used)]
197 vmem::PageTableEntry::from_le_bytes(pte_bytes.try_into().unwrap())
198 }
199 #[allow(clippy::unnecessary_cast)]
200 fn to_phys(addr: u64) -> vmem::PhysAddr {
201 addr as vmem::PhysAddr
202 }
203 #[allow(clippy::unnecessary_cast)]
204 fn from_phys(addr: vmem::PhysAddr) -> u64 {
205 addr as u64
206 }
207 fn root_table(&self) -> u64 {
208 self.root
209 }
210}
211impl<'a> core::convert::AsRef<SharedMemoryPageTableBuffer<'a>> for SharedMemoryPageTableBuffer<'a> {
212 fn as_ref(&self) -> &Self {
213 self
214 }
215}
216fn skip_virt(virt_base: u64, scratch_gva: u64) -> bool {
221 if virt_base >= scratch_gva {
222 return true;
223 }
224 if virt_base >= hyperlight_common::layout::SNAPSHOT_PT_GVA_MIN as u64
225 && virt_base <= hyperlight_common::layout::SNAPSHOT_PT_GVA_MAX as u64
226 {
227 return true;
228 }
229 false
230}
231
232unsafe fn guest_page<'a>(
240 snap: &'a [u8],
241 scratch: &'a [u8],
242 regions: &[MemoryRegion],
243 layout: SandboxMemoryLayout,
244 gpa: u64,
245) -> Option<&'a [u8]> {
246 let resolved = layout
247 .resolve_gpa(gpa, regions)?
248 .with_memories(snap, scratch);
249 if resolved.as_ref().len() < PAGE_SIZE {
250 return None;
251 }
252 Some(&resolved.as_ref()[..PAGE_SIZE])
253}
254
255fn map_specials(pt_buf: &GuestPageTableBuffer, scratch_size: usize) {
256 if let Some((phys_base, virt_base)) = io_page() {
257 let mapping = Mapping {
259 phys_base,
260 virt_base,
261 len: PAGE_SIZE as u64,
262 kind: MappingKind::Basic(BasicMapping {
263 readable: true,
264 writable: true,
265 executable: false,
266 }),
267 };
268 unsafe { vmem::map(pt_buf, mapping) };
269 }
270 let mapping = Mapping {
272 phys_base: scratch_base_gpa(scratch_size),
273 virt_base: scratch_base_gva(scratch_size),
274 len: scratch_size as u64,
275 kind: MappingKind::Basic(BasicMapping {
276 readable: true,
277 writable: true,
278 executable: false,
281 }),
282 };
283 unsafe { vmem::map(pt_buf, mapping) };
284}
285
286impl Snapshot {
287 pub(crate) fn from_env<'b>(
290 env: impl Into<GuestEnvironment<'b>>,
291 cfg: SandboxConfiguration,
292 ) -> Result<Self> {
293 let env = env.into();
294 let mut bin = env.guest_binary;
295 bin.canonicalize()?;
296 let blob = env.init_data;
297
298 let exe_info = match bin {
299 GuestBinary::FilePath(bin_path) => ExeInfo::from_file(&bin_path)?,
300 GuestBinary::Buffer(buffer) => ExeInfo::from_buf(buffer)?,
301 };
302
303 let host_version = env!("CARGO_PKG_VERSION");
305 if let Some(v) = exe_info.guest_bin_version()
306 && v != host_version
307 {
308 return Err(crate::HyperlightError::GuestBinVersionMismatch {
309 guest_bin_version: v.to_string(),
310 host_version: host_version.to_string(),
311 });
312 }
313
314 let guest_blob_size = blob.as_ref().map(|b| b.data.len()).unwrap_or(0);
315 let guest_blob_mem_flags = blob.as_ref().map(|b| b.permissions);
316
317 let mut layout = crate::mem::layout::SandboxMemoryLayout::new(
318 cfg,
319 exe_info.loaded_size(),
320 guest_blob_size,
321 guest_blob_mem_flags,
322 )?;
323
324 let load_addr = layout.get_guest_code_address() as u64;
325 let base_va = exe_info.base_va();
326 let entrypoint_va: u64 = exe_info.entrypoint().into();
327
328 let mut memory = vec![0; layout.get_memory_size()?];
329
330 let load_info = exe_info.load(
331 load_addr.try_into()?,
332 &mut memory[layout.guest_code_offset()..],
333 )?;
334
335 layout.write_peb(&mut memory)?;
336
337 blob.map(|x| layout.write_init_data(&mut memory, x.data))
338 .transpose()?;
339
340 let pt_buf = GuestPageTableBuffer::new(layout.get_pt_base_gpa() as usize);
342
343 for rgn in layout.get_memory_regions_::<GuestMemoryRegion>(())?.iter() {
345 let readable = rgn.flags.contains(MemoryRegionFlags::READ);
346 let executable = rgn.flags.contains(MemoryRegionFlags::EXECUTE);
347 let writable = rgn.flags.contains(MemoryRegionFlags::WRITE);
348 let kind = if writable {
349 MappingKind::Cow(CowMapping {
350 readable,
351 executable,
352 })
353 } else {
354 MappingKind::Basic(BasicMapping {
355 readable,
356 writable: false,
357 executable,
358 })
359 };
360 let mapping = Mapping {
361 phys_base: rgn.guest_region.start as u64,
362 virt_base: rgn.guest_region.start as u64,
363 len: rgn.guest_region.len() as u64,
364 kind,
365 };
366 unsafe { vmem::map(&pt_buf, mapping) };
367 }
368
369 map_specials(&pt_buf, layout.get_scratch_size());
371
372 let pt_bytes = pt_buf.into_bytes();
373 layout.set_pt_size(pt_bytes.len())?;
374 memory.extend(&pt_bytes);
375
376 let exn_stack_top_gva = hyperlight_common::layout::SCRATCH_TOP_GVA as u64
377 - hyperlight_common::layout::SCRATCH_TOP_EXN_STACK_OFFSET
378 + 1;
379
380 let entrypoint_gva = load_addr + entrypoint_va - base_va;
381
382 Ok(Self {
383 memory: ReadonlySharedMemory::from_bytes(&memory, layout.snapshot_size())?,
384 layout,
385 load_info,
386 stack_top_gva: exn_stack_top_gva,
387 sregs: None,
388 #[cfg(target_arch = "x86_64")]
389 msrs: None,
390 next_action: NextAction::Initialise(entrypoint_gva),
391 original_entrypoint: entrypoint_gva,
392 snapshot_generation: 0,
393 host_functions: HostFunctionDetails {
394 host_functions: None,
395 },
396 })
397 }
398
399 #[allow(clippy::too_many_arguments)]
405 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
408 pub(crate) fn new<S: SharedMemory>(
409 shared_mem: &mut SnapshotSharedMemory<S>,
410 scratch_mem: &mut S,
411 mut layout: SandboxMemoryLayout,
412 load_info: LoadInfo,
413 regions: Vec<MemoryRegion>,
414 root_pt_gpas: &[u64],
415 stack_top_gva: u64,
416 sregs: CommonSpecialRegisters,
417 #[cfg(target_arch = "x86_64")] msrs: Vec<MsrEntry>,
418 next_action: NextAction,
419 original_entrypoint: u64,
420 snapshot_generation: u64,
421 host_functions: HostFunctionDetails,
422 ) -> Result<Self> {
423 let mut phys_seen = HashMap::<u64, usize>::new();
424 let scratch_gva = scratch_base_gva(layout.get_scratch_size());
425 let memory = shared_mem.with_contents(|snap_c| {
426 scratch_mem.with_contents(|scratch_c| {
427 let op = SharedMemoryPageTableBuffer::new(
438 snap_c,
439 scratch_c,
440 layout,
441 root_pt_gpas.first().copied().unwrap_or(0),
442 );
443 let walk = unsafe {
444 vmem::walk_va_spaces(
445 &op,
446 root_pt_gpas,
447 0,
448 hyperlight_common::layout::SCRATCH_TOP_GVA as u64,
449 )
450 };
451
452 let mut snapshot_memory: Vec<u8> = Vec::new();
458 let pt_buf = GuestPageTableBuffer::new(layout.get_pt_base_gpa() as usize);
459 let mut root_addrs: Vec<u64> = Vec::with_capacity(root_pt_gpas.len());
463 root_addrs.push(pt_buf.initial_root());
464 for _ in 1..root_pt_gpas.len() {
465 root_addrs.push(unsafe { pt_buf.alloc_table() });
466 }
467
468 let mut built_roots: BTreeMap<SpaceId, u64> = BTreeMap::new();
469 for (root_idx, (space_id, mappings)) in walk.into_iter().enumerate() {
470 pt_buf.set_root(root_addrs[root_idx]);
471 built_roots.insert(space_id, root_addrs[root_idx]);
472
473 for sam in mappings {
474 match sam {
475 SpaceAwareMapping::ThisSpace(mapping) => {
476 if skip_virt(mapping.virt_base, scratch_gva) {
481 continue;
482 }
483 let Some(contents) = (unsafe {
484 guest_page(
485 snap_c,
486 scratch_c,
487 ®ions,
488 layout,
489 mapping.phys_base,
490 )
491 }) else {
492 continue;
493 };
494
495 let kind = match mapping.kind {
499 MappingKind::Cow(cm) => MappingKind::Cow(cm),
500 MappingKind::Basic(bm) if bm.writable => {
501 MappingKind::Cow(CowMapping {
502 readable: bm.readable,
503 executable: bm.executable,
504 })
505 }
506 MappingKind::Basic(bm) => MappingKind::Basic(BasicMapping {
507 readable: bm.readable,
508 writable: false,
509 executable: bm.executable,
510 }),
511 MappingKind::Unmapped => continue,
512 };
513 let new_gpa =
514 phys_seen.entry(mapping.phys_base).or_insert_with(|| {
515 let new_offset = snapshot_memory.len();
516 snapshot_memory.extend(contents);
517 new_offset + SandboxMemoryLayout::BASE_ADDRESS
518 });
519
520 let compacted = Mapping {
521 phys_base: *new_gpa as u64,
522 virt_base: mapping.virt_base,
523 len: PAGE_SIZE as u64,
524 kind,
525 };
526 unsafe { vmem::map(&pt_buf, compacted) };
527 }
528 SpaceAwareMapping::AnotherSpace(ref_map) => {
529 unsafe {
535 vmem::space_aware_map(&pt_buf, ref_map, &built_roots);
536 }
537 }
538 }
539 }
540 }
541
542 for &root_addr in &root_addrs {
544 pt_buf.set_root(root_addr);
545 map_specials(&pt_buf, layout.get_scratch_size());
546 }
547 pt_buf.set_root(pt_buf.initial_root());
548
549 snapshot_memory.resize(
550 snapshot_memory.len().next_multiple_of(page_size::get()),
551 0u8,
552 );
553
554 let pt_data = pt_buf.into_bytes();
556 layout.set_pt_size(pt_data.len())?;
557 snapshot_memory.extend(&pt_data);
558 Ok::<_, crate::HyperlightError>(snapshot_memory)
559 })
560 })???;
561 let guest_visible_size = memory.len() - layout.get_pt_size();
568 debug_assert!(guest_visible_size.is_multiple_of(page_size::get()));
569 layout.set_snapshot_size(guest_visible_size);
570
571 Ok(Self {
572 layout,
573 memory: ReadonlySharedMemory::from_bytes(&memory, guest_visible_size)?,
574 load_info,
575 stack_top_gva,
576 sregs: Some(sregs),
577 #[cfg(target_arch = "x86_64")]
578 msrs: Some(msrs),
579 next_action,
580 original_entrypoint,
581 snapshot_generation,
582 host_functions,
583 })
584 }
585
586 pub(crate) fn snapshot_generation(&self) -> u64 {
588 self.snapshot_generation
589 }
590
591 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
593 pub(crate) fn memory(&self) -> &ReadonlySharedMemory {
594 &self.memory
595 }
596
597 pub(crate) fn load_info(&self) -> LoadInfo {
599 self.load_info.clone()
600 }
601
602 pub(crate) fn layout(&self) -> &crate::mem::layout::SandboxMemoryLayout {
603 &self.layout
604 }
605
606 pub(crate) fn root_pt_gpa(&self) -> u64 {
607 self.layout.get_pt_base_gpa()
608 }
609
610 pub(crate) fn stack_top_gva(&self) -> u64 {
611 self.stack_top_gva
612 }
613
614 pub(crate) fn sregs(&self) -> Option<&CommonSpecialRegisters> {
620 self.sregs.as_ref()
621 }
622
623 #[cfg(target_arch = "x86_64")]
625 pub(crate) fn msrs(&self) -> Option<&Vec<MsrEntry>> {
626 self.msrs.as_ref()
627 }
628
629 pub(crate) fn next_action(&self) -> NextAction {
630 self.next_action
631 }
632
633 pub(crate) fn original_entrypoint(&self) -> u64 {
637 self.original_entrypoint
638 }
639
640 pub(crate) fn validate_host_functions(
649 &self,
650 provided: &crate::sandbox::host_funcs::FunctionRegistry,
651 ) -> Result<()> {
652 let required = match &self.host_functions.host_functions {
653 Some(v) => v,
654 None => return Ok(()),
655 };
656 if required.is_empty() {
657 return Ok(());
658 }
659
660 let mut missing: Vec<String> = Vec::new();
661 let mut signature_mismatches: Vec<String> = Vec::new();
662
663 for req in required {
664 match provided.function_signature(&req.function_name) {
665 None => missing.push(req.function_name.clone()),
667 Some((found_parameter_types, found_return_type))
669 if {
670 let params_match = match req.parameter_types.as_deref() {
671 Some(params) => params == found_parameter_types,
672 None => found_parameter_types.is_empty(),
673 };
674 !params_match || req.return_type != found_return_type
675 } =>
676 {
677 signature_mismatches.push(format!(
678 "{}: snapshot has {:?} -> {:?}, registered {:?} -> {:?}",
679 req.function_name,
680 req.parameter_types,
681 req.return_type,
682 Some(found_parameter_types.to_vec()),
683 found_return_type,
684 ));
685 }
686 Some(_) => {}
688 }
689 }
690
691 if missing.is_empty() && signature_mismatches.is_empty() {
692 return Ok(());
693 }
694
695 Err(crate::HyperlightError::SnapshotHostFunctionMismatch {
696 missing,
697 signature_mismatches,
698 })
699 }
700}
701
702#[cfg(test)]
703mod tests {
704 use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
705 use hyperlight_common::vmem::{self, BasicMapping, Mapping, MappingKind, PAGE_SIZE};
706
707 use crate::hypervisor::regs::CommonSpecialRegisters;
708 use crate::mem::exe::LoadInfo;
709 use crate::mem::layout::SandboxMemoryLayout;
710 use crate::mem::mgr::{GuestPageTableBuffer, SandboxMemoryManager, SnapshotSharedMemory};
711 use crate::mem::shared_mem::{
712 ExclusiveSharedMemory, HostSharedMemory, ReadonlySharedMemory, SharedMemory,
713 };
714
715 fn default_sregs() -> CommonSpecialRegisters {
716 CommonSpecialRegisters::default()
717 }
718
719 fn simple_pt_base() -> usize {
720 page_size::get() + SandboxMemoryLayout::BASE_ADDRESS
721 }
722
723 fn make_simple_pt_mem(contents: &[u8]) -> SnapshotSharedMemory<ExclusiveSharedMemory> {
724 let pt_buf = GuestPageTableBuffer::new(simple_pt_base());
725 let mapping = Mapping {
726 phys_base: SandboxMemoryLayout::BASE_ADDRESS as u64,
727 virt_base: SandboxMemoryLayout::BASE_ADDRESS as u64,
728 len: page_size::get() as u64,
729 kind: MappingKind::Basic(BasicMapping {
730 readable: true,
731 writable: true,
732 executable: true,
733 }),
734 };
735 unsafe { vmem::map(&pt_buf, mapping) };
736 super::map_specials(&pt_buf, PAGE_SIZE);
737 let pt_bytes = pt_buf.into_bytes();
738
739 let mut snapshot_mem = vec![0u8; page_size::get() + pt_bytes.len()];
740 snapshot_mem[0..page_size::get()].copy_from_slice(contents);
741 snapshot_mem[page_size::get()..].copy_from_slice(&pt_bytes);
742 ReadonlySharedMemory::from_bytes(&snapshot_mem, page_size::get())
743 .unwrap()
744 .to_mgr_snapshot_mem()
745 .unwrap()
746 }
747
748 fn make_simple_pt_mgr() -> (SandboxMemoryManager<HostSharedMemory>, u64) {
749 let cfg = crate::sandbox::SandboxConfiguration::default();
750 let scratch_mem = ExclusiveSharedMemory::new(cfg.get_scratch_size()).unwrap();
751 let mgr = SandboxMemoryManager::new(
752 SandboxMemoryLayout::new(cfg, 4096, 0x3000, None).unwrap(),
753 make_simple_pt_mem(&vec![0u8; page_size::get()]),
754 scratch_mem,
755 super::NextAction::None,
756 );
757 let (mgr, _) = mgr.build().unwrap();
758 (mgr, simple_pt_base() as u64)
759 }
760
761 #[test]
762 fn multiple_snapshots_independent() {
763 let (mut mgr, pt_base) = make_simple_pt_mgr();
764
765 let pattern_a = vec![0xAA; page_size::get()];
767 let snapshot_a = super::Snapshot::new(
768 &mut make_simple_pt_mem(&pattern_a).build().0,
769 &mut mgr.scratch_mem,
770 mgr.layout,
771 LoadInfo::dummy(),
772 Vec::new(),
773 &[pt_base],
774 0,
775 default_sregs(),
776 #[cfg(target_arch = "x86_64")]
777 Vec::new(),
778 super::NextAction::None,
779 0,
780 1,
781 HostFunctionDetails::default(),
782 )
783 .unwrap();
784
785 let pattern_b = vec![0xBB; page_size::get()];
787 let snapshot_b = super::Snapshot::new(
788 &mut make_simple_pt_mem(&pattern_b).build().0,
789 &mut mgr.scratch_mem,
790 mgr.layout,
791 LoadInfo::dummy(),
792 Vec::new(),
793 &[pt_base],
794 0,
795 default_sregs(),
796 #[cfg(target_arch = "x86_64")]
797 Vec::new(),
798 super::NextAction::None,
799 0,
800 2,
801 HostFunctionDetails::default(),
802 )
803 .unwrap();
804
805 mgr.restore_snapshot(&snapshot_a).unwrap();
807 mgr.shared_mem
808 .with_contents(|contents| assert_eq!(&contents[0..pattern_a.len()], &pattern_a[..]))
809 .unwrap();
810
811 mgr.restore_snapshot(&snapshot_b).unwrap();
813 mgr.shared_mem
814 .with_contents(|contents| assert_eq!(&contents[0..pattern_b.len()], &pattern_b[..]))
815 .unwrap();
816 }
817}