1#[cfg(feature = "nanvix-unstable")]
17use std::mem::offset_of;
18
19use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData;
20use nub_host_common::vmem::{self, PAGE_TABLE_SIZE};
21#[cfg(all(feature = "crashdump", not(feature = "i686-guest")))]
22use nub_host_common::vmem::{BasicMapping, MappingKind};
23use tracing::{Span, instrument};
24
25use super::layout::SandboxMemoryLayout;
26use super::shared_mem::{
27 ExclusiveSharedMemory, GuestSharedMemory, HostSharedMemory, ReadonlySharedMemory, SharedMemory,
28};
29use crate::Result;
30#[cfg(crashdump)]
31use crate::mem::memory_region::{CrashDumpRegion, MemoryRegionFlags, MemoryRegionType};
32#[allow(unused_imports)]
33use crate::new_error;
34use crate::sandbox::snapshot::{NextAction, Snapshot};
35
36#[cfg(all(feature = "crashdump", not(feature = "i686-guest")))]
37fn mapping_kind_to_flags(kind: &MappingKind) -> (MemoryRegionFlags, MemoryRegionType) {
38 match kind {
39 MappingKind::Basic(BasicMapping {
40 readable,
41 writable,
42 executable,
43 }) => {
44 let mut flags = MemoryRegionFlags::empty();
45 if *readable {
46 flags |= MemoryRegionFlags::READ;
47 }
48 if *writable {
49 flags |= MemoryRegionFlags::WRITE;
50 }
51 if *executable {
52 flags |= MemoryRegionFlags::EXECUTE;
53 }
54 (flags, MemoryRegionType::Snapshot)
55 }
56 MappingKind::Cow(cow) => {
57 let mut flags = MemoryRegionFlags::empty();
58 if cow.readable {
59 flags |= MemoryRegionFlags::READ;
60 }
61 if cow.executable {
62 flags |= MemoryRegionFlags::EXECUTE;
63 }
64 (flags, MemoryRegionType::Scratch)
65 }
66 MappingKind::Unmapped => (MemoryRegionFlags::empty(), MemoryRegionType::Snapshot),
67 }
68}
69
70#[cfg(all(feature = "crashdump", not(feature = "i686-guest")))]
75fn try_coalesce_region(
76 regions: &mut [CrashDumpRegion],
77 virt_base: usize,
78 virt_end: usize,
79 host_base: usize,
80 flags: MemoryRegionFlags,
81) -> bool {
82 if let Some(last) = regions.last_mut()
83 && last.guest_region.end == virt_base
84 && last.host_region.end == host_base
85 && last.flags == flags
86 {
87 last.guest_region.end = virt_end;
88 last.host_region.end = host_base + (virt_end - virt_base);
89 return true;
90 }
91 false
92}
93
94mod unused_hack {
103 #[cfg(not(unshared_snapshot_mem))]
104 use crate::mem::shared_mem::ReadonlySharedMemory;
105 use crate::mem::shared_mem::SharedMemory;
106 pub trait SnapshotSharedMemoryT {
107 type T<S: SharedMemory>;
108 }
109 pub struct SnapshotSharedMemory_;
110 impl SnapshotSharedMemoryT for SnapshotSharedMemory_ {
111 #[cfg(not(unshared_snapshot_mem))]
112 type T<S: SharedMemory> = ReadonlySharedMemory;
113 #[cfg(unshared_snapshot_mem)]
114 type T<S: SharedMemory> = S;
115 }
116 pub type SnapshotSharedMemory<S> = <SnapshotSharedMemory_ as SnapshotSharedMemoryT>::T<S>;
117}
118impl ReadonlySharedMemory {
119 pub(crate) fn to_mgr_snapshot_mem(
120 &self,
121 ) -> Result<SnapshotSharedMemory<ExclusiveSharedMemory>> {
122 #[cfg(not(unshared_snapshot_mem))]
123 let ret = self.clone();
124 #[cfg(unshared_snapshot_mem)]
125 let ret = self.copy_to_writable()?;
126 Ok(ret)
127 }
128}
129pub(crate) use unused_hack::SnapshotSharedMemory;
130#[derive(Clone)]
133pub(crate) struct SandboxMemoryManager<S: SharedMemory> {
134 pub(crate) shared_mem: SnapshotSharedMemory<S>,
136 pub(crate) scratch_mem: S,
138 pub(crate) layout: SandboxMemoryLayout,
140 pub(crate) entrypoint: NextAction,
142 pub(crate) mapped_rgns: u64,
144 pub(crate) abort_buffer: Vec<u8>,
146 pub(crate) snapshot_count: u64,
152}
153
154pub(crate) struct GuestPageTableBuffer {
158 buffer: std::cell::RefCell<Vec<u8>>,
159 phys_base: usize,
160 root: std::cell::Cell<u64>,
165}
166
167impl vmem::TableReadOps for GuestPageTableBuffer {
168 type TableAddr = u64;
169
170 fn entry_addr(addr: u64, offset: u64) -> u64 {
171 addr + offset
172 }
173
174 unsafe fn read_entry(&self, addr: u64) -> vmem::PageTableEntry {
175 let buffer = self.buffer.borrow();
176 let byte_offset = addr as usize - self.phys_base;
177 let pte_size = core::mem::size_of::<vmem::PageTableEntry>();
178 let Some(bytes) = buffer.get(byte_offset..byte_offset + pte_size) else {
179 return 0;
180 };
181 let mut buf = [0u8; 8];
182 buf[..pte_size].copy_from_slice(bytes);
183 vmem::PageTableEntry::from_le_bytes(buf[..pte_size].try_into().unwrap_or_default())
184 }
185
186 fn to_phys(addr: u64) -> vmem::PhysAddr {
187 addr as vmem::PhysAddr
188 }
189
190 fn from_phys(addr: vmem::PhysAddr) -> u64 {
191 #[allow(clippy::unnecessary_cast)]
192 {
193 addr as u64
194 }
195 }
196
197 fn root_table(&self) -> u64 {
198 self.root.get()
199 }
200}
201
202impl vmem::TableOps for GuestPageTableBuffer {
203 type TableMovability = vmem::MayNotMoveTable;
204
205 unsafe fn alloc_table(&self) -> u64 {
206 let mut b = self.buffer.borrow_mut();
207 let offset = b.len();
208 b.resize(offset + PAGE_TABLE_SIZE, 0);
209 (self.phys_base + offset) as u64
210 }
211
212 unsafe fn write_entry(&self, addr: u64, entry: vmem::PageTableEntry) -> Option<vmem::Void> {
213 let mut b = self.buffer.borrow_mut();
214 let byte_offset = addr as usize - self.phys_base;
215 let pte_size = core::mem::size_of::<vmem::PageTableEntry>();
216 if let Some(slice) = b.get_mut(byte_offset..byte_offset + pte_size) {
217 slice.copy_from_slice(&entry.to_le_bytes()[..pte_size]);
218 }
219 None
220 }
221
222 unsafe fn update_root(&self, impossible: vmem::Void) {
223 match impossible {}
224 }
225}
226
227impl core::convert::AsRef<GuestPageTableBuffer> for GuestPageTableBuffer {
228 fn as_ref(&self) -> &Self {
229 self
230 }
231}
232
233impl GuestPageTableBuffer {
234 pub(crate) fn new(phys_base: usize) -> Self {
238 GuestPageTableBuffer {
239 buffer: std::cell::RefCell::new(vec![0u8; PAGE_TABLE_SIZE]),
240 phys_base,
241 root: std::cell::Cell::new(phys_base as u64),
242 }
243 }
244
245 #[cfg(test)]
246 #[allow(dead_code)]
247 pub(crate) fn size(&self) -> usize {
248 self.buffer.borrow().len()
249 }
250
251 pub(crate) fn into_bytes(self) -> Box<[u8]> {
252 self.buffer.into_inner().into_boxed_slice()
253 }
254}
255
256impl<S> SandboxMemoryManager<S>
257where
258 S: SharedMemory,
259{
260 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
262 pub(crate) fn new(
263 layout: SandboxMemoryLayout,
264 shared_mem: SnapshotSharedMemory<S>,
265 scratch_mem: S,
266 entrypoint: NextAction,
267 ) -> Self {
268 Self {
269 layout,
270 shared_mem,
271 scratch_mem,
272 entrypoint,
273 mapped_rgns: 0,
274 abort_buffer: Vec::new(),
275 snapshot_count: 0,
276 }
277 }
278
279 pub(crate) fn get_abort_buffer_mut(&mut self) -> &mut Vec<u8> {
281 &mut self.abort_buffer
282 }
283}
284
285impl SandboxMemoryManager<ExclusiveSharedMemory> {
286 pub(crate) fn from_snapshot(s: &Snapshot) -> Result<Self> {
287 let layout = *s.layout();
288 let shared_mem = s.memory().to_mgr_snapshot_mem()?;
289 let scratch_mem = ExclusiveSharedMemory::new(s.layout().get_scratch_size())?;
290 let entrypoint = s.entrypoint();
291 Ok(Self::new(layout, shared_mem, scratch_mem, entrypoint))
292 }
293
294 pub fn build(
305 self,
306 ) -> Result<(
307 SandboxMemoryManager<HostSharedMemory>,
308 SandboxMemoryManager<GuestSharedMemory>,
309 )> {
310 let (hshm, gshm) = self.shared_mem.build();
311 let (hscratch, gscratch) = self.scratch_mem.build();
312 let mut host_mgr = SandboxMemoryManager {
313 shared_mem: hshm,
314 scratch_mem: hscratch,
315 layout: self.layout,
316 entrypoint: self.entrypoint,
317 mapped_rgns: self.mapped_rgns,
318 abort_buffer: self.abort_buffer,
319 snapshot_count: self.snapshot_count,
320 };
321 let guest_mgr = SandboxMemoryManager {
322 shared_mem: gshm,
323 scratch_mem: gscratch,
324 layout: self.layout,
325 entrypoint: self.entrypoint,
326 mapped_rgns: self.mapped_rgns,
327 abort_buffer: Vec::new(), snapshot_count: self.snapshot_count,
329 };
330 host_mgr.update_scratch_bookkeeping()?;
331 Ok((host_mgr, guest_mgr))
332 }
333}
334
335impl SandboxMemoryManager<HostSharedMemory> {
336 #[cfg(feature = "nanvix-unstable")]
353 pub(crate) fn write_file_mapping_entry(
354 &mut self,
355 guest_addr: u64,
356 size: u64,
357 label: &[u8; nub_host_common::mem::FILE_MAPPING_LABEL_MAX_LEN + 1],
358 ) -> Result<()> {
359 use nub_host_common::mem::{FileMappingInfo, MAX_FILE_MAPPINGS};
360
361 let current_count =
365 self.shared_mem
366 .read::<u64>(self.layout.get_file_mappings_size_offset())? as usize;
367
368 if current_count >= MAX_FILE_MAPPINGS {
369 return Err(crate::new_error!(
370 "file mapping limit reached ({} of {})",
371 current_count,
372 MAX_FILE_MAPPINGS,
373 ));
374 }
375
376 let entry_offset = self.layout.get_file_mappings_array_offset()
378 + current_count * std::mem::size_of::<FileMappingInfo>();
379 let guest_addr_offset = offset_of!(FileMappingInfo, guest_addr);
380 let size_offset = offset_of!(FileMappingInfo, size);
381 let label_offset = offset_of!(FileMappingInfo, label);
382 self.shared_mem
383 .write::<u64>(entry_offset + guest_addr_offset, guest_addr)?;
384 self.shared_mem
385 .write::<u64>(entry_offset + size_offset, size)?;
386 self.shared_mem
387 .copy_from_slice(label, entry_offset + label_offset)?;
388
389 let new_count = (current_count + 1) as u64;
391 self.shared_mem
392 .write::<u64>(self.layout.get_file_mappings_size_offset(), new_count)?;
393
394 Ok(())
395 }
396
397 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
400 pub(crate) fn write_guest_function_call_raw(&mut self, buffer: &[u8]) -> Result<()> {
401 self.scratch_mem.push_buffer(
402 self.layout.get_input_data_buffer_scratch_host_offset(),
403 self.layout.sandbox_memory_config.get_input_data_size(),
404 buffer,
405 )
406 }
407
408 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
411 pub(crate) fn read_guest_function_call_result_raw(&mut self) -> Result<Vec<u8>> {
412 self.scratch_mem.try_pop_buffer_raw(
413 self.layout.get_output_data_buffer_scratch_host_offset(),
414 self.layout.sandbox_memory_config.get_output_data_size(),
415 )
416 }
417
418 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
421 pub(crate) fn read_host_function_call_raw(&mut self) -> Result<Vec<u8>> {
422 self.scratch_mem.try_pop_buffer_raw(
423 self.layout.get_output_data_buffer_scratch_host_offset(),
424 self.layout.sandbox_memory_config.get_output_data_size(),
425 )
426 }
427
428 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
431 pub(crate) fn write_host_function_response_raw(&mut self, buffer: &[u8]) -> Result<()> {
432 self.scratch_mem.push_buffer(
433 self.layout.get_input_data_buffer_scratch_host_offset(),
434 self.layout.sandbox_memory_config.get_input_data_size(),
435 buffer,
436 )
437 }
438
439 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
441 pub(crate) fn read_guest_log_data(&mut self) -> Result<GuestLogData> {
442 self.scratch_mem.try_pop_buffer_into::<GuestLogData>(
443 self.layout.get_output_data_buffer_scratch_host_offset(),
444 self.layout.sandbox_memory_config.get_output_data_size(),
445 )
446 }
447
448 pub(crate) fn clear_io_buffers(&mut self) {
449 loop {
451 let Ok(_) = self.scratch_mem.try_pop_buffer_into::<Vec<u8>>(
452 self.layout.get_output_data_buffer_scratch_host_offset(),
453 self.layout.sandbox_memory_config.get_output_data_size(),
454 ) else {
455 break;
456 };
457 }
458 loop {
460 let Ok(_) = self.scratch_mem.try_pop_buffer_into::<Vec<u8>>(
461 self.layout.get_input_data_buffer_scratch_host_offset(),
462 self.layout.sandbox_memory_config.get_input_data_size(),
463 ) else {
464 break;
465 };
466 }
467 }
468
469 #[inline]
470 fn update_scratch_bookkeeping_item(&mut self, offset: u64, value: u64) -> Result<()> {
471 let scratch_size = self.scratch_mem.mem_size();
472 let base_offset = scratch_size - offset as usize;
473 self.scratch_mem.write::<u64>(base_offset, value)
474 }
475
476 fn update_scratch_bookkeeping(&mut self) -> Result<()> {
477 use nub_host_common::layout::*;
478 let scratch_size = self.scratch_mem.mem_size();
479 self.update_scratch_bookkeeping_item(SCRATCH_TOP_SIZE_OFFSET, scratch_size as u64)?;
480 self.update_scratch_bookkeeping_item(
481 SCRATCH_TOP_ALLOCATOR_OFFSET,
482 self.layout.get_first_free_scratch_gpa(),
483 )?;
484 self.update_scratch_bookkeeping_item(
493 SCRATCH_TOP_SNAPSHOT_PT_GPA_BASE_OFFSET,
494 self.layout.get_pt_base_gpa(),
495 )?;
496 self.update_scratch_bookkeeping_item(
497 SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET,
498 self.snapshot_count,
499 )?;
500
501 self.scratch_mem.write::<u64>(
504 self.layout.get_input_data_buffer_scratch_host_offset(),
505 SandboxMemoryLayout::STACK_POINTER_SIZE_BYTES,
506 )?;
507 self.scratch_mem.write::<u64>(
508 self.layout.get_output_data_buffer_scratch_host_offset(),
509 SandboxMemoryLayout::STACK_POINTER_SIZE_BYTES,
510 )?;
511
512 let snapshot_pt_end = self.shared_mem.mem_size();
519 let snapshot_pt_size = self.layout.get_pt_size();
520 let snapshot_pt_start = snapshot_pt_end - snapshot_pt_size;
521 self.scratch_mem.with_exclusivity(|scratch| {
522 #[cfg(not(unshared_snapshot_mem))]
523 let bytes = &self.shared_mem.as_slice()[snapshot_pt_start..snapshot_pt_end];
524 #[cfg(unshared_snapshot_mem)]
525 let bytes = {
526 let mut bytes = vec![0u8; snapshot_pt_size];
527 self.shared_mem
528 .copy_to_slice(&mut bytes, snapshot_pt_start)?;
529 bytes
530 };
531 #[allow(clippy::needless_borrow)]
532 scratch.copy_from_slice(&bytes, self.layout.get_pt_base_scratch_offset())
533 })??;
534
535 Ok(())
536 }
537
538 #[cfg(all(feature = "crashdump", not(feature = "i686-guest")))]
543 pub(crate) fn get_guest_memory_regions(
544 &mut self,
545 root_pt: u64,
546 mmap_regions: &[MemoryRegion],
547 ) -> Result<Vec<CrashDumpRegion>> {
548 use crate::sandbox::snapshot::SharedMemoryPageTableBuffer;
549
550 let len = nub_host_common::layout::MAX_GVA;
551
552 let regions = self.shared_mem.with_contents(|snapshot| {
553 self.scratch_mem.with_contents(|scratch| {
554 let pt_buf =
555 SharedMemoryPageTableBuffer::new(snapshot, scratch, self.layout, root_pt);
556
557 let mappings: Vec<_> =
558 unsafe { nub_host_common::vmem::virt_to_phys(&pt_buf, 0, len as u64) }
559 .collect();
560
561 if mappings.is_empty() {
562 return Err(new_error!("No page table mappings found (len {len})",));
563 }
564
565 let mut regions: Vec<CrashDumpRegion> = Vec::new();
566 for mapping in &mappings {
567 let virt_base = mapping.virt_base as usize;
568 let virt_end = (mapping.virt_base + mapping.len) as usize;
569
570 if let Some(resolved) = self.layout.resolve_gpa(mapping.phys_base, mmap_regions)
571 {
572 let (flags, region_type) = mapping_kind_to_flags(&mapping.kind);
573 let resolved = resolved.with_memories(snapshot, scratch);
574 let contents = resolved.as_ref();
575 let host_base = contents.as_ptr() as usize;
576 let host_len = (mapping.len as usize).min(contents.len());
577
578 if try_coalesce_region(&mut regions, virt_base, virt_end, host_base, flags)
579 {
580 continue;
581 }
582
583 regions.push(CrashDumpRegion {
584 guest_region: virt_base..virt_end,
585 host_region: host_base..host_base + host_len,
586 flags,
587 region_type,
588 });
589 }
590 }
591
592 Ok(regions)
593 })
594 })???;
595
596 Ok(regions)
597 }
598
599 #[cfg(all(feature = "crashdump", feature = "i686-guest"))]
605 pub(crate) fn get_guest_memory_regions(
606 &mut self,
607 _root_pt: u64,
608 mmap_regions: &[MemoryRegion],
609 ) -> Result<Vec<CrashDumpRegion>> {
610 use crate::mem::memory_region::HostGuestMemoryRegion;
611
612 let snapshot_base = SandboxMemoryLayout::BASE_ADDRESS;
613 let snapshot_size = self.shared_mem.mem_size();
614 let snapshot_host = self.shared_mem.base_addr();
615
616 let scratch_size = self.scratch_mem.mem_size();
617 let scratch_gva = nub_host_common::layout::scratch_base_gva(scratch_size) as usize;
618 let scratch_host = self.scratch_mem.base_addr();
619
620 let mut regions = vec![
621 CrashDumpRegion {
622 guest_region: snapshot_base..snapshot_base + snapshot_size,
623 host_region: snapshot_host..snapshot_host + snapshot_size,
624 flags: MemoryRegionFlags::READ | MemoryRegionFlags::EXECUTE,
625 region_type: MemoryRegionType::Snapshot,
626 },
627 CrashDumpRegion {
628 guest_region: scratch_gva..scratch_gva + scratch_size,
629 host_region: scratch_host..scratch_host + scratch_size,
630 flags: MemoryRegionFlags::READ
631 | MemoryRegionFlags::WRITE
632 | MemoryRegionFlags::EXECUTE,
633 region_type: MemoryRegionType::Scratch,
634 },
635 ];
636 for rgn in mmap_regions {
637 regions.push(CrashDumpRegion {
638 guest_region: rgn.guest_region.clone(),
639 host_region: HostGuestMemoryRegion::to_addr(rgn.host_region.start)
640 ..HostGuestMemoryRegion::to_addr(rgn.host_region.end),
641 flags: rgn.flags,
642 region_type: rgn.region_type,
643 });
644 }
645
646 Ok(regions)
647 }
648
649 #[cfg(feature = "trace_guest")]
662 pub(crate) fn read_guest_memory_by_gva(
663 &mut self,
664 gva: u64,
665 len: usize,
666 root_pt: u64,
667 ) -> Result<Vec<u8>> {
668 use nub_host_common::vmem::PAGE_SIZE;
669
670 use crate::sandbox::snapshot::{SharedMemoryPageTableBuffer, access_gpa};
671
672 self.shared_mem.with_contents(|snap| {
673 self.scratch_mem.with_contents(|scratch| {
674 let pt_buf = SharedMemoryPageTableBuffer::new(snap, scratch, self.layout, root_pt);
675
676 let mappings: Vec<_> = unsafe {
678 nub_host_common::vmem::virt_to_phys(&pt_buf, gva, len as u64)
679 }
680 .collect();
681
682 if mappings.is_empty() {
683 return Err(new_error!(
684 "No page table mappings found for GVA {:#x} (len {})",
685 gva,
686 len,
687 ));
688 }
689
690 let mut result = Vec::with_capacity(len);
692 let mut current_gva = gva;
693
694 for mapping in &mappings {
695 if mapping.virt_base > current_gva {
698 return Err(new_error!(
699 "Page table walker returned mapping with virt_base {:#x} > current read position {:#x}",
700 mapping.virt_base,
701 current_gva,
702 ));
703 }
704
705 let page_offset = (current_gva - mapping.virt_base) as usize;
707
708 let bytes_remaining = len - result.len();
709 let available_in_page = PAGE_SIZE - page_offset;
710 let bytes_to_copy = bytes_remaining.min(available_in_page);
711
712 let gpa = mapping.phys_base + page_offset as u64;
714 let (mem, offset) = access_gpa(snap, scratch, self.layout, gpa)
715 .ok_or_else(|| {
716 new_error!(
717 "Failed to resolve GPA {:#x} to host memory (GVA {:#x})",
718 gpa,
719 gva
720 )
721 })?;
722
723 let slice = mem
724 .get(offset..offset + bytes_to_copy)
725 .ok_or_else(|| {
726 new_error!(
727 "GPA {:#x} resolved to out-of-bounds host offset {} (need {} bytes)",
728 gpa,
729 offset,
730 bytes_to_copy
731 )
732 })?;
733
734 result.extend_from_slice(slice);
735 current_gva += bytes_to_copy as u64;
736 }
737
738 if result.len() != len {
739 tracing::error!(
740 "Page table walker returned mappings that don't cover the full requested length: got {}, expected {}",
741 result.len(),
742 len,
743 );
744 return Err(new_error!(
745 "Could not read full GVA range: got {} of {} bytes {:?}",
746 result.len(),
747 len,
748 mappings
749 ));
750 }
751
752 Ok(result)
753 })
754 })??
755 }
756}