1#[cfg(feature = "nanvix-unstable")]
17use std::mem::offset_of;
18
19use flatbuffers::FlatBufferBuilder;
20use hyperlight_common::flatbuffer_wrappers::function_call::{
21 FunctionCall, validate_guest_function_call_buffer,
22};
23use hyperlight_common::flatbuffer_wrappers::function_types::FunctionCallResult;
24use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData;
25use hyperlight_common::vmem::{self, PAGE_TABLE_SIZE, PageTableEntry, PhysAddr};
26#[cfg(all(feature = "crashdump", not(feature = "nanvix-unstable")))]
27use hyperlight_common::vmem::{BasicMapping, MappingKind};
28use tracing::{Span, instrument};
29
30use super::layout::SandboxMemoryLayout;
31use super::shared_mem::{
32 ExclusiveSharedMemory, GuestSharedMemory, HostSharedMemory, ReadonlySharedMemory, SharedMemory,
33};
34use crate::hypervisor::regs::CommonSpecialRegisters;
35use crate::mem::memory_region::MemoryRegion;
36#[cfg(crashdump)]
37use crate::mem::memory_region::{CrashDumpRegion, MemoryRegionFlags, MemoryRegionType};
38use crate::sandbox::snapshot::{NextAction, Snapshot};
39use crate::{Result, new_error};
40
41#[cfg(all(feature = "crashdump", not(feature = "nanvix-unstable")))]
42fn mapping_kind_to_flags(kind: &MappingKind) -> (MemoryRegionFlags, MemoryRegionType) {
43 match kind {
44 MappingKind::Basic(BasicMapping {
45 readable,
46 writable,
47 executable,
48 }) => {
49 let mut flags = MemoryRegionFlags::empty();
50 if *readable {
51 flags |= MemoryRegionFlags::READ;
52 }
53 if *writable {
54 flags |= MemoryRegionFlags::WRITE;
55 }
56 if *executable {
57 flags |= MemoryRegionFlags::EXECUTE;
58 }
59 (flags, MemoryRegionType::Snapshot)
60 }
61 MappingKind::Cow(cow) => {
62 let mut flags = MemoryRegionFlags::empty();
63 if cow.readable {
64 flags |= MemoryRegionFlags::READ;
65 }
66 if cow.executable {
67 flags |= MemoryRegionFlags::EXECUTE;
68 }
69 (flags, MemoryRegionType::Scratch)
70 }
71 MappingKind::Unmapped => (MemoryRegionFlags::empty(), MemoryRegionType::Snapshot),
72 }
73}
74
75#[cfg(all(feature = "crashdump", not(feature = "nanvix-unstable")))]
80fn try_coalesce_region(
81 regions: &mut [CrashDumpRegion],
82 virt_base: usize,
83 virt_end: usize,
84 host_base: usize,
85 flags: MemoryRegionFlags,
86) -> bool {
87 if let Some(last) = regions.last_mut()
88 && last.guest_region.end == virt_base
89 && last.host_region.end == host_base
90 && last.flags == flags
91 {
92 last.guest_region.end = virt_end;
93 last.host_region.end = host_base + (virt_end - virt_base);
94 return true;
95 }
96 false
97}
98
99mod unused_hack {
108 #[cfg(not(unshared_snapshot_mem))]
109 use crate::mem::shared_mem::ReadonlySharedMemory;
110 use crate::mem::shared_mem::SharedMemory;
111 pub trait SnapshotSharedMemoryT {
112 type T<S: SharedMemory>;
113 }
114 pub struct SnapshotSharedMemory_;
115 impl SnapshotSharedMemoryT for SnapshotSharedMemory_ {
116 #[cfg(not(unshared_snapshot_mem))]
117 type T<S: SharedMemory> = ReadonlySharedMemory;
118 #[cfg(unshared_snapshot_mem)]
119 type T<S: SharedMemory> = S;
120 }
121 pub type SnapshotSharedMemory<S> = <SnapshotSharedMemory_ as SnapshotSharedMemoryT>::T<S>;
122}
123impl ReadonlySharedMemory {
124 pub(crate) fn to_mgr_snapshot_mem(
125 &self,
126 ) -> Result<SnapshotSharedMemory<ExclusiveSharedMemory>> {
127 #[cfg(not(unshared_snapshot_mem))]
128 let ret = self.clone();
129 #[cfg(unshared_snapshot_mem)]
130 let ret = self.copy_to_writable()?;
131 Ok(ret)
132 }
133}
134pub(crate) use unused_hack::SnapshotSharedMemory;
135#[derive(Clone)]
138pub(crate) struct SandboxMemoryManager<S: SharedMemory> {
139 pub(crate) shared_mem: SnapshotSharedMemory<S>,
141 pub(crate) scratch_mem: S,
143 pub(crate) layout: SandboxMemoryLayout,
145 pub(crate) entrypoint: NextAction,
147 pub(crate) mapped_rgns: u64,
149 pub(crate) abort_buffer: Vec<u8>,
151}
152
153pub(crate) struct GuestPageTableBuffer {
154 buffer: std::cell::RefCell<Vec<u8>>,
155 phys_base: usize,
156}
157
158impl vmem::TableReadOps for GuestPageTableBuffer {
159 type TableAddr = (usize, usize); fn entry_addr(addr: (usize, usize), offset: u64) -> (usize, usize) {
162 let phys = Self::to_phys(addr) + offset;
164 Self::from_phys(phys)
165 }
166
167 unsafe fn read_entry(&self, addr: (usize, usize)) -> PageTableEntry {
168 let b = self.buffer.borrow();
169 let byte_offset =
170 (addr.0 - self.phys_base / PAGE_TABLE_SIZE) * PAGE_TABLE_SIZE + addr.1 * 8;
171 b.get(byte_offset..byte_offset + 8)
172 .and_then(|s| <[u8; 8]>::try_from(s).ok())
173 .map(u64::from_ne_bytes)
174 .unwrap_or(0)
175 }
176
177 fn to_phys(addr: (usize, usize)) -> PhysAddr {
178 (addr.0 as u64 * PAGE_TABLE_SIZE as u64) + (addr.1 as u64 * 8)
179 }
180
181 fn from_phys(addr: PhysAddr) -> (usize, usize) {
182 (
183 addr as usize / PAGE_TABLE_SIZE,
184 (addr as usize % PAGE_TABLE_SIZE) / 8,
185 )
186 }
187
188 fn root_table(&self) -> (usize, usize) {
189 (self.phys_base / PAGE_TABLE_SIZE, 0)
190 }
191}
192impl vmem::TableOps for GuestPageTableBuffer {
193 type TableMovability = vmem::MayNotMoveTable;
194
195 unsafe fn alloc_table(&self) -> (usize, usize) {
196 let mut b = self.buffer.borrow_mut();
197 let table_index = b.len() / PAGE_TABLE_SIZE;
198 let new_len = b.len() + PAGE_TABLE_SIZE;
199 b.resize(new_len, 0);
200 (self.phys_base / PAGE_TABLE_SIZE + table_index, 0)
201 }
202
203 unsafe fn write_entry(
204 &self,
205 addr: (usize, usize),
206 entry: PageTableEntry,
207 ) -> Option<vmem::Void> {
208 let mut b = self.buffer.borrow_mut();
209 let byte_offset =
210 (addr.0 - self.phys_base / PAGE_TABLE_SIZE) * PAGE_TABLE_SIZE + addr.1 * 8;
211 if let Some(slice) = b.get_mut(byte_offset..byte_offset + 8) {
212 slice.copy_from_slice(&entry.to_ne_bytes());
213 }
214 None
215 }
216
217 unsafe fn update_root(&self, impossible: vmem::Void) {
218 match impossible {}
219 }
220}
221
222impl GuestPageTableBuffer {
223 pub(crate) fn new(phys_base: usize) -> Self {
224 GuestPageTableBuffer {
225 buffer: std::cell::RefCell::new(vec![0u8; PAGE_TABLE_SIZE]),
226 phys_base,
227 }
228 }
229
230 #[cfg(test)]
231 #[allow(dead_code)]
232 pub(crate) fn size(&self) -> usize {
233 self.buffer.borrow().len()
234 }
235
236 pub(crate) fn into_bytes(self) -> Box<[u8]> {
237 self.buffer.into_inner().into_boxed_slice()
238 }
239}
240
241impl<S> SandboxMemoryManager<S>
242where
243 S: SharedMemory,
244{
245 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
247 pub(crate) fn new(
248 layout: SandboxMemoryLayout,
249 shared_mem: SnapshotSharedMemory<S>,
250 scratch_mem: S,
251 entrypoint: NextAction,
252 ) -> Self {
253 Self {
254 layout,
255 shared_mem,
256 scratch_mem,
257 entrypoint,
258 mapped_rgns: 0,
259 abort_buffer: Vec::new(),
260 }
261 }
262
263 pub(crate) fn get_abort_buffer_mut(&mut self) -> &mut Vec<u8> {
265 &mut self.abort_buffer
266 }
267
268 pub(crate) fn snapshot(
270 &mut self,
271 sandbox_id: u64,
272 mapped_regions: Vec<MemoryRegion>,
273 root_pt_gpa: u64,
274 rsp_gva: u64,
275 sregs: CommonSpecialRegisters,
276 entrypoint: NextAction,
277 ) -> Result<Snapshot> {
278 Snapshot::new(
279 &mut self.shared_mem,
280 &mut self.scratch_mem,
281 sandbox_id,
282 self.layout,
283 crate::mem::exe::LoadInfo::dummy(),
284 mapped_regions,
285 root_pt_gpa,
286 rsp_gva,
287 sregs,
288 entrypoint,
289 )
290 }
291}
292
293impl SandboxMemoryManager<ExclusiveSharedMemory> {
294 pub(crate) fn from_snapshot(s: &Snapshot) -> Result<Self> {
295 let layout = *s.layout();
296 let shared_mem = s.memory().to_mgr_snapshot_mem()?;
297 let scratch_mem = ExclusiveSharedMemory::new(s.layout().get_scratch_size())?;
298 let entrypoint = s.entrypoint();
299 Ok(Self::new(layout, shared_mem, scratch_mem, entrypoint))
300 }
301
302 pub fn build(
313 self,
314 ) -> Result<(
315 SandboxMemoryManager<HostSharedMemory>,
316 SandboxMemoryManager<GuestSharedMemory>,
317 )> {
318 let (hshm, gshm) = self.shared_mem.build();
319 let (hscratch, gscratch) = self.scratch_mem.build();
320 let mut host_mgr = SandboxMemoryManager {
321 shared_mem: hshm,
322 scratch_mem: hscratch,
323 layout: self.layout,
324 entrypoint: self.entrypoint,
325 mapped_rgns: self.mapped_rgns,
326 abort_buffer: self.abort_buffer,
327 };
328 let guest_mgr = SandboxMemoryManager {
329 shared_mem: gshm,
330 scratch_mem: gscratch,
331 layout: self.layout,
332 entrypoint: self.entrypoint,
333 mapped_rgns: self.mapped_rgns,
334 abort_buffer: Vec::new(), };
336 host_mgr.update_scratch_bookkeeping()?;
337 Ok((host_mgr, guest_mgr))
338 }
339}
340
341impl SandboxMemoryManager<HostSharedMemory> {
342 #[cfg(feature = "nanvix-unstable")]
359 pub(crate) fn write_file_mapping_entry(
360 &mut self,
361 guest_addr: u64,
362 size: u64,
363 label: &[u8; hyperlight_common::mem::FILE_MAPPING_LABEL_MAX_LEN + 1],
364 ) -> Result<()> {
365 use hyperlight_common::mem::{FileMappingInfo, MAX_FILE_MAPPINGS};
366
367 let current_count =
371 self.shared_mem
372 .read::<u64>(self.layout.get_file_mappings_size_offset())? as usize;
373
374 if current_count >= MAX_FILE_MAPPINGS {
375 return Err(crate::new_error!(
376 "file mapping limit reached ({} of {})",
377 current_count,
378 MAX_FILE_MAPPINGS,
379 ));
380 }
381
382 let entry_offset = self.layout.get_file_mappings_array_offset()
384 + current_count * std::mem::size_of::<FileMappingInfo>();
385 let guest_addr_offset = offset_of!(FileMappingInfo, guest_addr);
386 let size_offset = offset_of!(FileMappingInfo, size);
387 let label_offset = offset_of!(FileMappingInfo, label);
388 self.shared_mem
389 .write::<u64>(entry_offset + guest_addr_offset, guest_addr)?;
390 self.shared_mem
391 .write::<u64>(entry_offset + size_offset, size)?;
392 self.shared_mem
393 .copy_from_slice(label, entry_offset + label_offset)?;
394
395 let new_count = (current_count + 1) as u64;
397 self.shared_mem
398 .write::<u64>(self.layout.get_file_mappings_size_offset(), new_count)?;
399
400 Ok(())
401 }
402
403 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
405 pub(crate) fn get_host_function_call(&mut self) -> Result<FunctionCall> {
406 self.scratch_mem.try_pop_buffer_into::<FunctionCall>(
407 self.layout.get_output_data_buffer_scratch_host_offset(),
408 self.layout.sandbox_memory_config.get_output_data_size(),
409 )
410 }
411
412 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
414 pub(crate) fn write_response_from_host_function_call(
415 &mut self,
416 res: &FunctionCallResult,
417 ) -> Result<()> {
418 let mut builder = FlatBufferBuilder::new();
419 let data = res.encode(&mut builder);
420
421 self.scratch_mem.push_buffer(
422 self.layout.get_input_data_buffer_scratch_host_offset(),
423 self.layout.sandbox_memory_config.get_input_data_size(),
424 data,
425 )
426 }
427
428 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
430 pub(crate) fn write_guest_function_call(&mut self, buffer: &[u8]) -> Result<()> {
431 validate_guest_function_call_buffer(buffer).map_err(|e| {
432 new_error!(
433 "Guest function call buffer validation failed: {}",
434 e.to_string()
435 )
436 })?;
437
438 self.scratch_mem.push_buffer(
439 self.layout.get_input_data_buffer_scratch_host_offset(),
440 self.layout.sandbox_memory_config.get_input_data_size(),
441 buffer,
442 )?;
443 Ok(())
444 }
445
446 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
449 pub(crate) fn get_guest_function_call_result(&mut self) -> Result<FunctionCallResult> {
450 self.scratch_mem.try_pop_buffer_into::<FunctionCallResult>(
451 self.layout.get_output_data_buffer_scratch_host_offset(),
452 self.layout.sandbox_memory_config.get_output_data_size(),
453 )
454 }
455
456 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
458 pub(crate) fn read_guest_log_data(&mut self) -> Result<GuestLogData> {
459 self.scratch_mem.try_pop_buffer_into::<GuestLogData>(
460 self.layout.get_output_data_buffer_scratch_host_offset(),
461 self.layout.sandbox_memory_config.get_output_data_size(),
462 )
463 }
464
465 pub(crate) fn clear_io_buffers(&mut self) {
466 loop {
468 let Ok(_) = self.scratch_mem.try_pop_buffer_into::<Vec<u8>>(
469 self.layout.get_output_data_buffer_scratch_host_offset(),
470 self.layout.sandbox_memory_config.get_output_data_size(),
471 ) else {
472 break;
473 };
474 }
475 loop {
477 let Ok(_) = self.scratch_mem.try_pop_buffer_into::<Vec<u8>>(
478 self.layout.get_input_data_buffer_scratch_host_offset(),
479 self.layout.sandbox_memory_config.get_input_data_size(),
480 ) else {
481 break;
482 };
483 }
484 }
485
486 pub(crate) fn restore_snapshot(
488 &mut self,
489 snapshot: &Snapshot,
490 ) -> Result<(
491 Option<SnapshotSharedMemory<GuestSharedMemory>>,
492 Option<GuestSharedMemory>,
493 )> {
494 let gsnapshot = if *snapshot.memory() == self.shared_mem {
495 None
503 } else {
504 let new_snapshot_mem = snapshot.memory().to_mgr_snapshot_mem()?;
505 let (hsnapshot, gsnapshot) = new_snapshot_mem.build();
506 self.shared_mem = hsnapshot;
507 Some(gsnapshot)
508 };
509 let new_scratch_size = snapshot.layout().get_scratch_size();
510 let gscratch = if new_scratch_size == self.scratch_mem.mem_size() {
511 self.scratch_mem.zero()?;
512 None
513 } else {
514 let new_scratch_mem = ExclusiveSharedMemory::new(new_scratch_size)?;
515 let (hscratch, gscratch) = new_scratch_mem.build();
516 self.scratch_mem = hscratch;
522
523 Some(gscratch)
524 };
525 self.layout = *snapshot.layout();
526 self.update_scratch_bookkeeping()?;
527 Ok((gsnapshot, gscratch))
528 }
529
530 #[inline]
531 fn update_scratch_bookkeeping_item(&mut self, offset: u64, value: u64) -> Result<()> {
532 let scratch_size = self.scratch_mem.mem_size();
533 let base_offset = scratch_size - offset as usize;
534 self.scratch_mem.write::<u64>(base_offset, value)
535 }
536
537 fn update_scratch_bookkeeping(&mut self) -> Result<()> {
538 use hyperlight_common::layout::*;
539 let scratch_size = self.scratch_mem.mem_size();
540 self.update_scratch_bookkeeping_item(SCRATCH_TOP_SIZE_OFFSET, scratch_size as u64)?;
541 self.update_scratch_bookkeeping_item(
542 SCRATCH_TOP_ALLOCATOR_OFFSET,
543 self.layout.get_first_free_scratch_gpa(),
544 )?;
545
546 self.scratch_mem.write::<u64>(
549 self.layout.get_input_data_buffer_scratch_host_offset(),
550 SandboxMemoryLayout::STACK_POINTER_SIZE_BYTES,
551 )?;
552 self.scratch_mem.write::<u64>(
553 self.layout.get_output_data_buffer_scratch_host_offset(),
554 SandboxMemoryLayout::STACK_POINTER_SIZE_BYTES,
555 )?;
556
557 let snapshot_pt_end = self.shared_mem.mem_size();
559 let snapshot_pt_size = self.layout.get_pt_size();
560 let snapshot_pt_start = snapshot_pt_end - snapshot_pt_size;
561 self.scratch_mem.with_exclusivity(|scratch| {
562 #[cfg(not(unshared_snapshot_mem))]
563 let bytes = &self.shared_mem.as_slice()[snapshot_pt_start..snapshot_pt_end];
564 #[cfg(unshared_snapshot_mem)]
565 let bytes = {
566 let mut bytes = vec![0u8; snapshot_pt_size];
567 self.shared_mem
568 .copy_to_slice(&mut bytes, snapshot_pt_start)?;
569 bytes
570 };
571 #[allow(clippy::needless_borrow)]
572 scratch.copy_from_slice(&bytes, self.layout.get_pt_base_scratch_offset())
573 })??;
574
575 Ok(())
576 }
577
578 #[cfg(all(feature = "crashdump", not(feature = "nanvix-unstable")))]
583 pub(crate) fn get_guest_memory_regions(
584 &mut self,
585 root_pt: u64,
586 mmap_regions: &[MemoryRegion],
587 ) -> Result<Vec<CrashDumpRegion>> {
588 use crate::sandbox::snapshot::SharedMemoryPageTableBuffer;
589
590 let len = hyperlight_common::layout::MAX_GVA;
591
592 let regions = self.shared_mem.with_contents(|snapshot| {
593 self.scratch_mem.with_contents(|scratch| {
594 let pt_buf =
595 SharedMemoryPageTableBuffer::new(snapshot, scratch, self.layout, root_pt);
596
597 let mappings: Vec<_> =
598 unsafe { hyperlight_common::vmem::virt_to_phys(&pt_buf, 0, len as u64) }
599 .collect();
600
601 if mappings.is_empty() {
602 return Err(new_error!("No page table mappings found (len {len})",));
603 }
604
605 let mut regions: Vec<CrashDumpRegion> = Vec::new();
606 for mapping in &mappings {
607 let virt_base = mapping.virt_base as usize;
608 let virt_end = (mapping.virt_base + mapping.len) as usize;
609
610 if let Some(resolved) = self.layout.resolve_gpa(mapping.phys_base, mmap_regions)
611 {
612 let (flags, region_type) = mapping_kind_to_flags(&mapping.kind);
613 let resolved = resolved.with_memories(snapshot, scratch);
614 let contents = resolved.as_ref();
615 let host_base = contents.as_ptr() as usize;
616 let host_len = (mapping.len as usize).min(contents.len());
617
618 if try_coalesce_region(&mut regions, virt_base, virt_end, host_base, flags)
619 {
620 continue;
621 }
622
623 regions.push(CrashDumpRegion {
624 guest_region: virt_base..virt_end,
625 host_region: host_base..host_base + host_len,
626 flags,
627 region_type,
628 });
629 }
630 }
631
632 Ok(regions)
633 })
634 })???;
635
636 Ok(regions)
637 }
638
639 #[cfg(all(feature = "crashdump", feature = "nanvix-unstable"))]
645 pub(crate) fn get_guest_memory_regions(
646 &mut self,
647 _root_pt: u64,
648 mmap_regions: &[MemoryRegion],
649 ) -> Result<Vec<CrashDumpRegion>> {
650 use crate::mem::memory_region::HostGuestMemoryRegion;
651
652 let snapshot_base = SandboxMemoryLayout::BASE_ADDRESS;
653 let snapshot_size = self.shared_mem.mem_size();
654 let snapshot_host = self.shared_mem.base_addr();
655
656 let scratch_size = self.scratch_mem.mem_size();
657 let scratch_gva = hyperlight_common::layout::scratch_base_gva(scratch_size) as usize;
658 let scratch_host = self.scratch_mem.base_addr();
659
660 let mut regions = vec![
661 CrashDumpRegion {
662 guest_region: snapshot_base..snapshot_base + snapshot_size,
663 host_region: snapshot_host..snapshot_host + snapshot_size,
664 flags: MemoryRegionFlags::READ | MemoryRegionFlags::EXECUTE,
665 region_type: MemoryRegionType::Snapshot,
666 },
667 CrashDumpRegion {
668 guest_region: scratch_gva..scratch_gva + scratch_size,
669 host_region: scratch_host..scratch_host + scratch_size,
670 flags: MemoryRegionFlags::READ
671 | MemoryRegionFlags::WRITE
672 | MemoryRegionFlags::EXECUTE,
673 region_type: MemoryRegionType::Scratch,
674 },
675 ];
676 for rgn in mmap_regions {
677 regions.push(CrashDumpRegion {
678 guest_region: rgn.guest_region.clone(),
679 host_region: HostGuestMemoryRegion::to_addr(rgn.host_region.start)
680 ..HostGuestMemoryRegion::to_addr(rgn.host_region.end),
681 flags: rgn.flags,
682 region_type: rgn.region_type,
683 });
684 }
685
686 Ok(regions)
687 }
688
689 #[cfg(feature = "trace_guest")]
702 pub(crate) fn read_guest_memory_by_gva(
703 &mut self,
704 gva: u64,
705 len: usize,
706 root_pt: u64,
707 ) -> Result<Vec<u8>> {
708 use hyperlight_common::vmem::PAGE_SIZE;
709
710 use crate::sandbox::snapshot::{SharedMemoryPageTableBuffer, access_gpa};
711
712 self.shared_mem.with_contents(|snap| {
713 self.scratch_mem.with_contents(|scratch| {
714 let pt_buf = SharedMemoryPageTableBuffer::new(snap, scratch, self.layout, root_pt);
715
716 let mappings: Vec<_> = unsafe {
718 hyperlight_common::vmem::virt_to_phys(&pt_buf, gva, len as u64)
719 }
720 .collect();
721
722 if mappings.is_empty() {
723 return Err(new_error!(
724 "No page table mappings found for GVA {:#x} (len {})",
725 gva,
726 len,
727 ));
728 }
729
730 let mut result = Vec::with_capacity(len);
732 let mut current_gva = gva;
733
734 for mapping in &mappings {
735 if mapping.virt_base > current_gva {
738 return Err(new_error!(
739 "Page table walker returned mapping with virt_base {:#x} > current read position {:#x}",
740 mapping.virt_base,
741 current_gva,
742 ));
743 }
744
745 let page_offset = (current_gva - mapping.virt_base) as usize;
747
748 let bytes_remaining = len - result.len();
749 let available_in_page = PAGE_SIZE - page_offset;
750 let bytes_to_copy = bytes_remaining.min(available_in_page);
751
752 let gpa = mapping.phys_base + page_offset as u64;
754 let (mem, offset) = access_gpa(snap, scratch, self.layout, gpa)
755 .ok_or_else(|| {
756 new_error!(
757 "Failed to resolve GPA {:#x} to host memory (GVA {:#x})",
758 gpa,
759 gva
760 )
761 })?;
762
763 let slice = mem
764 .get(offset..offset + bytes_to_copy)
765 .ok_or_else(|| {
766 new_error!(
767 "GPA {:#x} resolved to out-of-bounds host offset {} (need {} bytes)",
768 gpa,
769 offset,
770 bytes_to_copy
771 )
772 })?;
773
774 result.extend_from_slice(slice);
775 current_gva += bytes_to_copy as u64;
776 }
777
778 if result.len() != len {
779 tracing::error!(
780 "Page table walker returned mappings that don't cover the full requested length: got {}, expected {}",
781 result.len(),
782 len,
783 );
784 return Err(new_error!(
785 "Could not read full GVA range: got {} of {} bytes {:?}",
786 result.len(),
787 len,
788 mappings
789 ));
790 }
791
792 Ok(result)
793 })
794 })??
795 }
796}
797
798#[cfg(test)]
799#[cfg(all(not(feature = "nanvix-unstable"), target_arch = "x86_64"))]
800mod tests {
801 use hyperlight_common::vmem::{MappingKind, PAGE_TABLE_SIZE};
802 use hyperlight_testing::sandbox_sizes::{LARGE_HEAP_SIZE, MEDIUM_HEAP_SIZE, SMALL_HEAP_SIZE};
803 use hyperlight_testing::simple_guest_as_string;
804
805 use crate::GuestBinary;
806 use crate::mem::memory_region::MemoryRegionFlags;
807 use crate::sandbox::SandboxConfiguration;
808 use crate::sandbox::snapshot::Snapshot;
809
810 fn verify_page_tables(name: &str, config: SandboxConfiguration) {
813 let path = simple_guest_as_string().expect("failed to get simple guest path");
814 let snapshot = Snapshot::from_env(GuestBinary::FilePath(path), config)
815 .unwrap_or_else(|e| panic!("{}: failed to create snapshot: {}", name, e));
816
817 let regions = snapshot.regions();
818
819 assert!(
821 unsafe { hyperlight_common::vmem::virt_to_phys(&snapshot, 0, 1) }
822 .next()
823 .is_none(),
824 "{}: NULL page (0x0) should NOT be mapped",
825 name
826 );
827
828 for region in regions {
830 let mut addr = region.guest_region.start as u64;
831
832 while addr < region.guest_region.end as u64 {
833 let mapping = unsafe { hyperlight_common::vmem::virt_to_phys(&snapshot, addr, 1) }
834 .next()
835 .unwrap_or_else(|| {
836 panic!(
837 "{}: {:?} region: address 0x{:x} is not mapped",
838 name, region.region_type, addr
839 )
840 });
841
842 assert_eq!(
844 mapping.phys_base, addr,
845 "{}: {:?} region: address 0x{:x} should identity map, got phys 0x{:x}",
846 name, region.region_type, addr, mapping.phys_base
847 );
848
849 let MappingKind::Basic(bm) = mapping.kind else {
851 panic!(
852 "{}: {:?} region: address 0x{:x} should be kind basic, got {:?}",
853 name, region.region_type, addr, mapping.kind
854 );
855 };
856
857 let actual = bm.writable;
859 let expected = region.flags.contains(MemoryRegionFlags::WRITE);
860 assert_eq!(
861 actual, expected,
862 "{}: {:?} region: address 0x{:x} has writable {}, expected {} (region flags: {:?})",
863 name, region.region_type, addr, actual, expected, region.flags
864 );
865
866 let actual = bm.executable;
868 let expected = region.flags.contains(MemoryRegionFlags::EXECUTE);
869 assert_eq!(
870 actual, expected,
871 "{}: {:?} region: address 0x{:x} has executable {}, expected {} (region flags: {:?})",
872 name, region.region_type, addr, actual, expected, region.flags
873 );
874
875 addr += PAGE_TABLE_SIZE as u64;
876 }
877 }
878 }
879
880 #[test]
881 fn test_page_tables_for_various_configurations() {
882 let test_cases: [(&str, SandboxConfiguration); 4] = [
883 ("default", { SandboxConfiguration::default() }),
884 ("small (8MB heap)", {
885 let mut cfg = SandboxConfiguration::default();
886 cfg.set_heap_size(SMALL_HEAP_SIZE);
887 cfg
888 }),
889 ("medium (64MB heap)", {
890 let mut cfg = SandboxConfiguration::default();
891 cfg.set_heap_size(MEDIUM_HEAP_SIZE);
892 cfg
893 }),
894 ("large (256MB heap)", {
895 let mut cfg = SandboxConfiguration::default();
896 cfg.set_heap_size(LARGE_HEAP_SIZE);
897 cfg.set_scratch_size(0x100000);
898 cfg
899 }),
900 ];
901
902 for (name, config) in test_cases {
903 verify_page_tables(name, config);
904 }
905 }
906}