1use std::any::type_name;
18use std::ffi::c_void;
19use std::io::Error;
20use std::mem::{align_of, size_of};
21#[cfg(target_os = "linux")]
22use std::ptr::null_mut;
23use std::sync::{Arc, RwLock};
24
25use bytemuck::Pod;
26use hyperlight_common::mem::PAGE_SIZE_USIZE;
27use tracing::{Span, instrument};
28#[cfg(target_os = "windows")]
29use windows::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE};
30#[cfg(target_os = "windows")]
31use windows::Win32::System::Memory::PAGE_READWRITE;
32#[cfg(target_os = "windows")]
33use windows::Win32::System::Memory::{
34 CreateFileMappingA, FILE_MAP_ALL_ACCESS, MEM_PRESERVE_PLACEHOLDER, MEM_RELEASE,
35 MEM_REPLACE_PLACEHOLDER, MEM_RESERVE, MEM_RESERVE_PLACEHOLDER, MEMORY_MAPPED_VIEW_ADDRESS,
36 MapViewOfFile, MapViewOfFile3, PAGE_NOACCESS, PAGE_PROTECTION_FLAGS, PAGE_READONLY,
37 UnmapViewOfFile, VIRTUAL_ALLOCATION_TYPE, VIRTUAL_FREE_TYPE, VirtualAlloc2, VirtualFree,
38 VirtualProtect,
39};
40#[cfg(target_os = "windows")]
41use windows::core::PCSTR;
42
43use super::memory_region::{
44 HostGuestMemoryRegion, MemoryRegion, MemoryRegionFlags, MemoryRegionKind, MemoryRegionType,
45};
46#[cfg(target_os = "windows")]
47use crate::HyperlightError::WindowsAPIError;
48use crate::{HyperlightError, Result, log_then_return, new_error};
49
50macro_rules! bounds_check {
52 ($offset:expr, $size:expr, $mem_size:expr) => {
53 if $offset.checked_add($size).is_none_or(|end| end > $mem_size) {
54 return Err(new_error!(
55 "Cannot read value from offset {} with size {} in memory of size {}",
56 $offset,
57 $size,
58 $mem_size
59 ));
60 }
61 };
62}
63
64macro_rules! generate_reader {
66 ($fname:ident, $ty:ty) => {
67 #[allow(dead_code)]
69 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
70 pub(crate) fn $fname(&self, offset: usize) -> Result<$ty> {
71 let data = self.as_slice();
72 bounds_check!(offset, std::mem::size_of::<$ty>(), data.len());
73 Ok(<$ty>::from_le_bytes(
74 data[offset..offset + std::mem::size_of::<$ty>()].try_into()?,
75 ))
76 }
77 };
78}
79
80macro_rules! generate_writer {
82 ($fname:ident, $ty:ty) => {
83 #[allow(dead_code)]
85 pub(crate) fn $fname(&mut self, offset: usize, value: $ty) -> Result<()> {
86 let data = self.as_mut_slice();
87 bounds_check!(offset, std::mem::size_of::<$ty>(), data.len());
88 data[offset..offset + std::mem::size_of::<$ty>()].copy_from_slice(&value.to_le_bytes());
89 Ok(())
90 }
91 };
92}
93
94#[derive(Debug)]
99pub struct HostMapping {
100 #[cfg(not(target_os = "windows"))]
101 mmap: Mmap,
102 #[cfg(target_os = "windows")]
103 mapping: WindowsMapping,
104}
105
106#[cfg(target_os = "windows")]
108#[derive(Debug)]
109enum WindowsMapping {
110 Anonymous {
119 view: MappedView,
120 file_mapping: FileMapping,
121 },
122 FileBacked {
131 leading: Placeholder,
132 view: MappedView,
133 trailing: Placeholder,
134 file_mapping: FileMapping,
135 },
136}
137
138impl HostMapping {
139 pub(crate) fn ptr(&self) -> *mut u8 {
141 #[cfg(not(target_os = "windows"))]
142 {
143 self.mmap.base as *mut u8
144 }
145 #[cfg(target_os = "windows")]
146 match &self.mapping {
147 WindowsMapping::Anonymous { view, .. } => view.addr as *mut u8,
148 WindowsMapping::FileBacked { leading, .. } => leading.addr as *mut u8,
149 }
150 }
151
152 pub(crate) fn size(&self) -> usize {
154 #[cfg(not(target_os = "windows"))]
155 {
156 self.mmap.len
157 }
158 #[cfg(target_os = "windows")]
159 match &self.mapping {
160 WindowsMapping::Anonymous { view, .. } => view.len,
161 WindowsMapping::FileBacked {
162 leading,
163 view,
164 trailing,
165 ..
166 } => leading.size + view.len + trailing.size,
167 }
168 }
169
170 #[cfg(target_os = "windows")]
172 pub(crate) fn file_mapping_handle(&self) -> HANDLE {
173 match &self.mapping {
174 WindowsMapping::Anonymous { file_mapping, .. }
175 | WindowsMapping::FileBacked { file_mapping, .. } => file_mapping.0,
176 }
177 }
178}
179
180#[cfg(target_os = "linux")]
182#[derive(Debug)]
183struct Mmap {
184 base: *mut c_void,
185 len: usize,
186}
187
188#[cfg(target_os = "linux")]
189impl Drop for Mmap {
190 fn drop(&mut self) {
191 unsafe {
195 if libc::munmap(self.base, self.len) != 0 {
196 tracing::error!(
197 "Mmap::drop: munmap failed: {:?}",
198 std::io::Error::last_os_error()
199 );
200 }
201 }
202 }
203}
204
205#[cfg(target_os = "windows")]
207#[derive(Debug)]
208struct MappedView {
209 addr: *mut c_void,
210 len: usize,
211}
212
213#[cfg(target_os = "windows")]
214impl Drop for MappedView {
215 fn drop(&mut self) {
216 let view = MEMORY_MAPPED_VIEW_ADDRESS { Value: self.addr };
217 if let Err(e) = unsafe { UnmapViewOfFile(view) } {
226 tracing::error!(
227 "MappedView::drop(addr={:?}, len={}) UnmapViewOfFile failed: {:?}",
228 self.addr,
229 self.len,
230 e
231 );
232 }
233 }
234}
235
236#[cfg(target_os = "windows")]
238#[derive(Debug)]
239struct FileMapping(HANDLE);
240
241#[cfg(target_os = "windows")]
242impl Drop for FileMapping {
243 fn drop(&mut self) {
244 unsafe {
247 if let Err(e) = CloseHandle(self.0) {
248 tracing::error!(
249 "FileMapping::drop(handle={:?}) CloseHandle failed: {:?}",
250 self.0,
251 e
252 );
253 }
254 }
255 }
256}
257
258#[cfg(target_os = "windows")]
263#[derive(Debug)]
264pub(crate) struct Placeholder {
265 addr: *mut c_void,
266 size: usize,
267}
268
269#[cfg(target_os = "windows")]
270impl Placeholder {
271 fn reserve(size: usize) -> Result<Self> {
272 let addr = unsafe {
278 VirtualAlloc2(
279 None,
280 None,
281 size,
282 VIRTUAL_ALLOCATION_TYPE(MEM_RESERVE.0 | MEM_RESERVE_PLACEHOLDER.0),
283 PAGE_NOACCESS.0,
284 None,
285 )
286 };
287 if addr.is_null() {
288 log_then_return!(HyperlightError::MemoryAllocationFailed(
289 Error::last_os_error().raw_os_error()
290 ));
291 }
292 Ok(Placeholder { addr, size })
293 }
294
295 fn split_front(self, front_size: usize) -> Result<(Placeholder, Placeholder)> {
296 debug_assert!(front_size > 0 && front_size < self.size);
297 debug_assert!(front_size.is_multiple_of(PAGE_SIZE_USIZE));
298 if let Err(e) = unsafe {
304 VirtualFree(
305 self.addr,
306 front_size,
307 VIRTUAL_FREE_TYPE(MEM_RELEASE.0 | MEM_PRESERVE_PLACEHOLDER.0),
308 )
309 } {
310 log_then_return!(WindowsAPIError(e.clone()));
312 }
313 let addr = self.addr;
314 let total = self.size;
315 std::mem::forget(self);
318 let front = Placeholder {
319 addr,
320 size: front_size,
321 };
322 let back = Placeholder {
323 addr: unsafe { (addr as *mut u8).add(front_size) as *mut c_void },
326 size: total - front_size,
327 };
328 Ok((front, back))
329 }
330
331 fn split_into_three(
332 self,
333 front_size: usize,
334 middle_size: usize,
335 ) -> Result<(Placeholder, Placeholder, Placeholder)> {
336 let (front, rest) = self.split_front(front_size)?;
337 let (middle, back) = rest.split_front(middle_size)?;
338 Ok((front, middle, back))
339 }
340
341 fn map_file_view(self, file_mapping: HANDLE) -> Result<MappedView> {
342 let mapped = unsafe {
350 MapViewOfFile3(
351 file_mapping,
352 None,
353 Some(self.addr),
354 0,
355 self.size,
356 MEM_REPLACE_PLACEHOLDER,
357 PAGE_READONLY.0,
358 None,
359 )
360 };
361 if mapped.Value.is_null() {
362 log_then_return!(HyperlightError::MemoryAllocationFailed(
364 Error::last_os_error().raw_os_error()
365 ));
366 }
367 let addr = self.addr;
368 let len = self.size;
369 std::mem::forget(self);
370 Ok(MappedView { addr, len })
371 }
372}
373
374#[cfg(target_os = "windows")]
375impl Drop for Placeholder {
376 fn drop(&mut self) {
377 if let Err(e) = unsafe { VirtualFree(self.addr, 0, VIRTUAL_FREE_TYPE(MEM_RELEASE.0)) } {
381 tracing::error!(
382 "Placeholder::drop(addr={:?}, size={}) VirtualFree failed: {:?}",
383 self.addr,
384 self.size,
385 e
386 );
387 }
388 }
389}
390
391pub trait SharedMemory {
396 fn region(&self) -> &HostMapping;
398
399 fn base_addr(&self) -> usize {
404 self.region().ptr() as usize + PAGE_SIZE_USIZE
405 }
406
407 fn base_ptr(&self) -> *mut u8 {
412 self.region().ptr().wrapping_add(PAGE_SIZE_USIZE)
413 }
414
415 fn mem_size(&self) -> usize {
419 self.region().size() - 2 * PAGE_SIZE_USIZE
420 }
421
422 fn raw_ptr(&self) -> *mut u8 {
425 self.region().ptr()
426 }
427
428 fn raw_mem_size(&self) -> usize {
431 self.region().size()
432 }
433
434 fn host_region_base(&self) -> <HostGuestMemoryRegion as MemoryRegionKind>::HostBaseType {
441 #[cfg(not(windows))]
442 {
443 self.base_addr()
444 }
445 #[cfg(windows)]
446 {
447 super::memory_region::HostRegionBase {
448 from_handle: self.region().file_mapping_handle().into(),
449 handle_base: self.region().ptr() as usize,
450 handle_size: self.region().size(),
451 offset: PAGE_SIZE_USIZE,
452 }
453 }
454 }
455
456 fn host_region_end(&self) -> <HostGuestMemoryRegion as MemoryRegionKind>::HostBaseType {
458 <HostGuestMemoryRegion as MemoryRegionKind>::add(self.host_region_base(), self.mem_size())
459 }
460
461 fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
467 &mut self,
468 f: F,
469 ) -> Result<T>;
470
471 fn with_contents<T, F: FnOnce(&[u8]) -> T>(&mut self, f: F) -> Result<T> {
477 self.with_exclusivity(|m| f(m.as_slice()))
478 }
479
480 fn zero(&mut self) -> Result<()> {
482 self.with_exclusivity(|e| {
483 #[allow(unused_mut)] let mut do_copy = true;
485 #[cfg(all(target_os = "linux", feature = "kvm", not(any(feature = "mshv3"))))]
489 unsafe {
490 let ret = libc::madvise(
491 e.region.ptr() as *mut libc::c_void,
492 e.region.size(),
493 libc::MADV_DONTNEED,
494 );
495 if ret == 0 {
496 do_copy = false;
497 }
498 }
499 if do_copy {
500 e.as_mut_slice().fill(0);
501 }
502 })
503 }
504}
505
506fn mapping_at(
507 s: &impl SharedMemory,
508 gpa: u64,
509 size: usize,
510 region_type: MemoryRegionType,
511 flags: MemoryRegionFlags,
512) -> MemoryRegion {
513 let guest_base = gpa as usize;
514
515 MemoryRegion {
516 guest_region: guest_base..(guest_base + size),
517 host_region: s.host_region_base()
518 ..<HostGuestMemoryRegion as MemoryRegionKind>::add(s.host_region_base(), size),
519 region_type,
520 flags,
521 }
522}
523
524#[derive(Debug)]
530pub struct ExclusiveSharedMemory {
531 region: Arc<HostMapping>,
532}
533unsafe impl Send for ExclusiveSharedMemory {}
534
535impl ExclusiveSharedMemory {
536 #[cfg(target_os = "linux")]
541 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
542 pub fn new(min_size_bytes: usize) -> Result<Self> {
543 use libc::{
544 MAP_ANONYMOUS, MAP_FAILED, MAP_PRIVATE, PROT_READ, PROT_WRITE, c_int, mmap, off_t,
545 size_t,
546 };
547 #[cfg(not(miri))]
548 use libc::{MAP_NORESERVE, PROT_NONE, mprotect};
549
550 if min_size_bytes == 0 {
551 return Err(new_error!("Cannot create shared memory with size 0"));
552 }
553
554 let total_size = min_size_bytes
555 .checked_add(2 * PAGE_SIZE_USIZE) .ok_or_else(|| new_error!("Memory required for sandbox exceeded usize::MAX"))?;
557
558 if total_size % PAGE_SIZE_USIZE != 0 {
559 return Err(new_error!(
560 "shared memory must be a multiple of {}",
561 PAGE_SIZE_USIZE
562 ));
563 }
564
565 if total_size > isize::MAX as usize {
568 return Err(HyperlightError::MemoryRequestTooBig(
569 total_size,
570 isize::MAX as usize,
571 ));
572 }
573
574 #[cfg(not(miri))]
576 let flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE;
577 #[cfg(miri)]
578 let flags = MAP_ANONYMOUS | MAP_PRIVATE;
579
580 let addr = unsafe {
581 mmap(
582 null_mut(),
583 total_size as size_t,
584 PROT_READ | PROT_WRITE,
585 flags,
586 -1 as c_int,
587 0 as off_t,
588 )
589 };
590 if addr == MAP_FAILED {
591 log_then_return!(HyperlightError::MmapFailed(
592 Error::last_os_error().raw_os_error()
593 ));
594 }
595 let mmap = Mmap {
596 base: addr,
597 len: total_size,
598 };
599
600 #[cfg(not(miri))]
602 {
603 let res = unsafe { mprotect(mmap.base, PAGE_SIZE_USIZE, PROT_NONE) };
604 if res != 0 {
605 return Err(HyperlightError::MprotectFailed(
606 Error::last_os_error().raw_os_error(),
607 ));
608 }
609 let res = unsafe {
610 mprotect(
611 (mmap.base as *const u8).add(total_size - PAGE_SIZE_USIZE) as *mut c_void,
612 PAGE_SIZE_USIZE,
613 PROT_NONE,
614 )
615 };
616 if res != 0 {
617 return Err(HyperlightError::MprotectFailed(
618 Error::last_os_error().raw_os_error(),
619 ));
620 }
621 }
622
623 Ok(Self {
624 #[allow(clippy::arc_with_non_send_sync)]
633 region: Arc::new(HostMapping { mmap }),
634 })
635 }
636
637 #[cfg(target_os = "windows")]
642 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
643 pub fn new(min_size_bytes: usize) -> Result<Self> {
644 if min_size_bytes == 0 {
645 return Err(new_error!("Cannot create shared memory with size 0"));
646 }
647
648 let total_size = min_size_bytes
649 .checked_add(2 * PAGE_SIZE_USIZE)
650 .ok_or_else(|| new_error!("Memory required for sandbox exceeded {}", usize::MAX))?;
651
652 if total_size % PAGE_SIZE_USIZE != 0 {
653 return Err(new_error!(
654 "shared memory must be a multiple of {}",
655 PAGE_SIZE_USIZE
656 ));
657 }
658
659 if total_size > isize::MAX as usize {
662 return Err(HyperlightError::MemoryRequestTooBig(
663 total_size,
664 isize::MAX as usize,
665 ));
666 }
667
668 let mut dwmaximumsizehigh = 0;
669 let mut dwmaximumsizelow = 0;
670
671 if std::mem::size_of::<usize>() == 8 {
672 dwmaximumsizehigh = (total_size >> 32) as u32;
673 dwmaximumsizelow = (total_size & 0xFFFFFFFF) as u32;
674 }
675
676 let flags = PAGE_READWRITE;
680
681 let handle = unsafe {
682 CreateFileMappingA(
683 INVALID_HANDLE_VALUE,
684 None,
685 flags,
686 dwmaximumsizehigh,
687 dwmaximumsizelow,
688 PCSTR::null(),
689 )?
690 };
691
692 if handle.is_invalid() {
693 log_then_return!(HyperlightError::MemoryAllocationFailed(
694 Error::last_os_error().raw_os_error()
695 ));
696 }
697 let file_mapping = FileMapping(handle);
698
699 let file_map = FILE_MAP_ALL_ACCESS;
700 let addr = unsafe { MapViewOfFile(file_mapping.0, file_map, 0, 0, 0) };
701
702 if addr.Value.is_null() {
703 log_then_return!(HyperlightError::MemoryAllocationFailed(
704 Error::last_os_error().raw_os_error()
705 ));
706 }
707 let view = MappedView {
708 addr: addr.Value,
709 len: total_size,
710 };
711
712 let mut unused_out_old_prot_flags = PAGE_PROTECTION_FLAGS(0);
715
716 let first_guard_page_start = view.addr;
719 if let Err(e) = unsafe {
720 VirtualProtect(
721 first_guard_page_start,
722 PAGE_SIZE_USIZE,
723 PAGE_NOACCESS,
724 &mut unused_out_old_prot_flags,
725 )
726 } {
727 log_then_return!(WindowsAPIError(e.clone()));
728 }
729
730 let last_guard_page_start = unsafe { view.addr.add(total_size - PAGE_SIZE_USIZE) };
731 if let Err(e) = unsafe {
732 VirtualProtect(
733 last_guard_page_start,
734 PAGE_SIZE_USIZE,
735 PAGE_NOACCESS,
736 &mut unused_out_old_prot_flags,
737 )
738 } {
739 log_then_return!(WindowsAPIError(e.clone()));
740 }
741
742 Ok(Self {
743 #[allow(clippy::arc_with_non_send_sync)]
752 region: Arc::new(HostMapping {
753 mapping: WindowsMapping::Anonymous { view, file_mapping },
754 }),
755 })
756 }
757
758 pub(super) fn as_mut_slice(&mut self) -> &mut [u8] {
794 unsafe { std::slice::from_raw_parts_mut(self.base_ptr(), self.mem_size()) }
795 }
796
797 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
810 pub fn as_slice<'a>(&'a self) -> &'a [u8] {
811 unsafe { std::slice::from_raw_parts(self.base_ptr(), self.mem_size()) }
812 }
813
814 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
816 #[cfg(test)]
817 pub(crate) fn copy_all_to_vec(&self) -> Result<Vec<u8>> {
818 let data = self.as_slice();
819 Ok(data.to_vec())
820 }
821
822 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
824 pub fn copy_from_slice(&mut self, src: &[u8], offset: usize) -> Result<()> {
825 let data = self.as_mut_slice();
826 bounds_check!(offset, src.len(), data.len());
827 data[offset..offset + src.len()].copy_from_slice(src);
828 Ok(())
829 }
830
831 generate_reader!(read_u8, u8);
832 generate_reader!(read_i8, i8);
833 generate_reader!(read_u16, u16);
834 generate_reader!(read_i16, i16);
835 generate_reader!(read_u32, u32);
836 generate_reader!(read_i32, i32);
837 generate_reader!(read_u64, u64);
838 generate_reader!(read_i64, i64);
839 generate_reader!(read_usize, usize);
840 generate_reader!(read_isize, isize);
841
842 generate_writer!(write_u8, u8);
843 generate_writer!(write_i8, i8);
844 generate_writer!(write_u16, u16);
845 generate_writer!(write_i16, i16);
846 generate_writer!(write_u32, u32);
847 generate_writer!(write_i32, i32);
848 generate_writer!(write_u64, u64);
849 generate_writer!(write_i64, i64);
850 generate_writer!(write_usize, usize);
851 generate_writer!(write_isize, isize);
852
853 pub fn build(self) -> (HostSharedMemory, GuestSharedMemory) {
860 let lock = Arc::new(RwLock::new(()));
861 let hshm = HostSharedMemory {
862 region: self.region.clone(),
863 lock: lock.clone(),
864 };
865 (
866 hshm,
867 GuestSharedMemory {
868 region: self.region.clone(),
869 lock,
870 },
871 )
872 }
873
874 #[cfg(target_os = "windows")]
876 pub fn get_mmap_file_handle(&self) -> HANDLE {
877 self.region.file_mapping_handle()
878 }
879}
880
881impl SharedMemory for ExclusiveSharedMemory {
882 fn region(&self) -> &HostMapping {
883 &self.region
884 }
885 fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
886 &mut self,
887 f: F,
888 ) -> Result<T> {
889 Ok(f(self))
890 }
891}
892
893#[derive(Debug)]
902pub struct GuestSharedMemory {
903 region: Arc<HostMapping>,
904 pub lock: Arc<RwLock<()>>,
915}
916unsafe impl Send for GuestSharedMemory {}
917
918impl GuestSharedMemory {
919 pub(crate) fn mapping_at(
922 &self,
923 guest_base: u64,
924 region_type: MemoryRegionType,
925 ) -> MemoryRegion {
926 let flags = match region_type {
927 MemoryRegionType::Scratch => {
928 MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE
929 }
930 #[cfg(unshared_snapshot_mem)]
931 MemoryRegionType::Snapshot => {
932 MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE
933 }
934 #[allow(clippy::panic)]
935 _ => panic!(
944 "GuestSharedMemory::mapping_at should only be used for Scratch or Snapshot regions"
945 ),
946 };
947 mapping_at(self, guest_base, self.mem_size(), region_type, flags)
948 }
949}
950
951impl SharedMemory for GuestSharedMemory {
952 fn region(&self) -> &HostMapping {
953 &self.region
954 }
955 fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
956 &mut self,
957 f: F,
958 ) -> Result<T> {
959 let guard = self
960 .lock
961 .try_write()
962 .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
963 let mut excl = ExclusiveSharedMemory {
964 region: self.region.clone(),
965 };
966 let ret = f(&mut excl);
967 drop(excl);
968 drop(guard);
969 Ok(ret)
970 }
971}
972
973#[derive(Clone, Debug)]
1128pub struct HostSharedMemory {
1129 region: Arc<HostMapping>,
1130 lock: Arc<RwLock<()>>,
1131}
1132unsafe impl Send for HostSharedMemory {}
1133
1134impl HostSharedMemory {
1135 pub fn read<T: Pod>(&self, offset: usize) -> Result<T> {
1138 bounds_check!(offset, std::mem::size_of::<T>(), self.mem_size());
1139 let mut ret = T::zeroed();
1140 self.copy_to_slice(bytemuck::bytes_of_mut(&mut ret), offset)?;
1141 Ok(ret)
1142 }
1143
1144 pub fn write<T: Pod>(&self, offset: usize, data: T) -> Result<()> {
1147 bounds_check!(offset, std::mem::size_of::<T>(), self.mem_size());
1148 self.copy_from_slice(bytemuck::bytes_of(&data), offset)
1149 }
1150
1151 pub fn copy_to_slice(&self, slice: &mut [u8], offset: usize) -> Result<()> {
1154 bounds_check!(offset, slice.len(), self.mem_size());
1155 let base = self.base_ptr().wrapping_add(offset);
1156 let guard = self
1157 .lock
1158 .try_read()
1159 .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
1160
1161 const CHUNK: usize = size_of::<u128>();
1162 let len = slice.len();
1163 let mut i = 0;
1164
1165 let align_offset = base.align_offset(align_of::<u128>());
1170 let head_len = align_offset.min(len);
1171 while i < head_len {
1172 unsafe {
1173 slice[i] = base.add(i).read_volatile();
1174 }
1175 i += 1;
1176 }
1177
1178 let dst = slice.as_mut_ptr();
1182 while i + CHUNK <= len {
1183 unsafe {
1184 let value = (base.add(i) as *const u128).read_volatile();
1185 std::ptr::write_unaligned(dst.add(i) as *mut u128, value);
1186 }
1187 i += CHUNK;
1188 }
1189
1190 while i < len {
1192 unsafe {
1193 slice[i] = base.add(i).read_volatile();
1194 }
1195 i += 1;
1196 }
1197
1198 drop(guard);
1199 Ok(())
1200 }
1201
1202 pub fn copy_from_slice(&self, slice: &[u8], offset: usize) -> Result<()> {
1205 bounds_check!(offset, slice.len(), self.mem_size());
1206 let base = self.base_ptr().wrapping_add(offset);
1207 let guard = self
1208 .lock
1209 .try_read()
1210 .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
1211
1212 const CHUNK: usize = size_of::<u128>();
1213 let len = slice.len();
1214 let mut i = 0;
1215
1216 let align_offset = base.align_offset(align_of::<u128>());
1221 let head_len = align_offset.min(len);
1222 while i < head_len {
1223 unsafe {
1224 base.add(i).write_volatile(slice[i]);
1225 }
1226 i += 1;
1227 }
1228
1229 let src = slice.as_ptr();
1233 while i + CHUNK <= len {
1234 unsafe {
1235 let value = std::ptr::read_unaligned(src.add(i) as *const u128);
1236 (base.add(i) as *mut u128).write_volatile(value);
1237 }
1238 i += CHUNK;
1239 }
1240
1241 while i < len {
1243 unsafe {
1244 base.add(i).write_volatile(slice[i]);
1245 }
1246 i += 1;
1247 }
1248
1249 drop(guard);
1250 Ok(())
1251 }
1252
1253 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
1255 pub fn fill(&mut self, value: u8, offset: usize, len: usize) -> Result<()> {
1256 bounds_check!(offset, len, self.mem_size());
1257 let base = self.base_ptr().wrapping_add(offset);
1258 let guard = self
1259 .lock
1260 .try_read()
1261 .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
1262
1263 const CHUNK: usize = size_of::<u128>();
1264 let value_u128 = u128::from_ne_bytes([value; CHUNK]);
1265 let mut i = 0;
1266
1267 let align_offset = base.align_offset(align_of::<u128>());
1272 let head_len = align_offset.min(len);
1273 while i < head_len {
1274 unsafe {
1275 base.add(i).write_volatile(value);
1276 }
1277 i += 1;
1278 }
1279
1280 while i + CHUNK <= len {
1283 unsafe {
1284 (base.add(i) as *mut u128).write_volatile(value_u128);
1285 }
1286 i += CHUNK;
1287 }
1288
1289 while i < len {
1291 unsafe {
1292 base.add(i).write_volatile(value);
1293 }
1294 i += 1;
1295 }
1296
1297 drop(guard);
1298 Ok(())
1299 }
1300
1301 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
1304 pub fn push_buffer(
1305 &mut self,
1306 buffer_start_offset: usize,
1307 buffer_size: usize,
1308 data: &[u8],
1309 ) -> Result<()> {
1310 let stack_pointer_rel = self.read::<u64>(buffer_start_offset)? as usize;
1311 let buffer_size_u64: u64 = buffer_size.try_into()?;
1312
1313 if stack_pointer_rel > buffer_size || stack_pointer_rel < 8 {
1314 return Err(new_error!(
1315 "Unable to push data to buffer: Stack pointer is out of bounds. Stack pointer: {}, Buffer size: {}",
1316 stack_pointer_rel,
1317 buffer_size_u64
1318 ));
1319 }
1320
1321 let size_required = data.len() + 8;
1322 let size_available = buffer_size - stack_pointer_rel;
1323
1324 if size_required > size_available {
1325 return Err(new_error!(
1326 "Not enough space in buffer to push data. Required: {}, Available: {}",
1327 size_required,
1328 size_available
1329 ));
1330 }
1331
1332 let stack_pointer_abs = stack_pointer_rel + buffer_start_offset;
1334
1335 self.copy_from_slice(data, stack_pointer_abs)?;
1337
1338 self.write::<u64>(stack_pointer_abs + data.len(), stack_pointer_rel as u64)?;
1341
1342 self.write::<u64>(
1344 buffer_start_offset,
1345 (stack_pointer_rel + data.len() + 8) as u64,
1346 )?;
1347 Ok(())
1348 }
1349
1350 pub fn try_pop_buffer_into<T>(
1354 &mut self,
1355 buffer_start_offset: usize,
1356 buffer_size: usize,
1357 ) -> Result<T>
1358 where
1359 T: for<'b> TryFrom<&'b [u8]>,
1360 {
1361 let stack_pointer_rel = self.read::<u64>(buffer_start_offset)? as usize;
1363
1364 if stack_pointer_rel > buffer_size || stack_pointer_rel < 16 {
1365 return Err(new_error!(
1366 "Unable to pop data from buffer: Stack pointer is out of bounds. Stack pointer: {}, Buffer size: {}",
1367 stack_pointer_rel,
1368 buffer_size
1369 ));
1370 }
1371
1372 let last_element_offset_abs = stack_pointer_rel + buffer_start_offset;
1374
1375 let last_element_offset_rel: usize =
1377 self.read::<u64>(last_element_offset_abs - 8)? as usize;
1378
1379 if last_element_offset_rel > stack_pointer_rel.saturating_sub(16)
1383 || last_element_offset_rel < 8
1384 {
1385 return Err(new_error!(
1386 "Corrupt buffer back-pointer: element offset {} is outside valid range [8, {}].",
1387 last_element_offset_rel,
1388 stack_pointer_rel.saturating_sub(16),
1389 ));
1390 }
1391
1392 let last_element_offset_abs = last_element_offset_rel + buffer_start_offset;
1394
1395 let max_element_size = stack_pointer_rel - last_element_offset_rel - 8;
1397
1398 let fb_buffer_size = {
1400 let raw_prefix = self.read::<u32>(last_element_offset_abs)?;
1401 let total = raw_prefix.checked_add(4).ok_or_else(|| {
1404 new_error!(
1405 "Corrupt buffer size prefix: value {} overflows when adding 4-byte header.",
1406 raw_prefix
1407 )
1408 })?;
1409 usize::try_from(total)
1410 }?;
1411
1412 if fb_buffer_size > max_element_size {
1413 return Err(new_error!(
1414 "Corrupt buffer size prefix: flatbuffer claims {} bytes but the element slot is only {} bytes.",
1415 fb_buffer_size,
1416 max_element_size
1417 ));
1418 }
1419
1420 let mut result_buffer = vec![0; fb_buffer_size];
1421
1422 self.copy_to_slice(&mut result_buffer, last_element_offset_abs)?;
1423 let to_return = T::try_from(result_buffer.as_slice()).map_err(|_e| {
1424 new_error!(
1425 "pop_buffer_into: failed to convert buffer to {}",
1426 type_name::<T>()
1427 )
1428 })?;
1429
1430 self.write::<u64>(buffer_start_offset, last_element_offset_rel as u64)?;
1432
1433 let num_bytes_to_zero = stack_pointer_rel - last_element_offset_rel;
1435 self.fill(0, last_element_offset_abs, num_bytes_to_zero)?;
1436
1437 Ok(to_return)
1438 }
1439}
1440
1441impl SharedMemory for HostSharedMemory {
1442 fn region(&self) -> &HostMapping {
1443 &self.region
1444 }
1445 fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
1446 &mut self,
1447 f: F,
1448 ) -> Result<T> {
1449 let guard = self
1450 .lock
1451 .try_write()
1452 .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
1453 let mut excl = ExclusiveSharedMemory {
1454 region: self.region.clone(),
1455 };
1456 let ret = f(&mut excl);
1457 drop(excl);
1458 drop(guard);
1459 Ok(ret)
1460 }
1461}
1462
1463#[derive(Clone, Debug)]
1468pub struct ReadonlySharedMemory {
1469 region: Arc<HostMapping>,
1470 #[cfg_attr(unshared_snapshot_mem, allow(dead_code))]
1475 guest_mapped_size: usize,
1476}
1477unsafe impl Send for ReadonlySharedMemory {}
1488unsafe impl Sync for ReadonlySharedMemory {}
1489
1490impl ReadonlySharedMemory {
1491 pub(crate) fn from_bytes(contents: &[u8], guest_mapped_size: usize) -> Result<Self> {
1492 if guest_mapped_size == 0 || !guest_mapped_size.is_multiple_of(PAGE_SIZE_USIZE) {
1493 return Err(new_error!(
1494 "guest_mapped_size {} must be a non-zero multiple of PAGE_SIZE",
1495 guest_mapped_size
1496 ));
1497 }
1498 if guest_mapped_size > contents.len() {
1499 return Err(new_error!(
1500 "guest_mapped_size {} exceeds blob length {}",
1501 guest_mapped_size,
1502 contents.len()
1503 ));
1504 }
1505 let mut anon = ExclusiveSharedMemory::new(contents.len())?;
1506 anon.copy_from_slice(contents, 0)?;
1507 Ok(ReadonlySharedMemory {
1508 region: anon.region,
1509 guest_mapped_size,
1510 })
1511 }
1512
1513 #[cfg(not(unshared_snapshot_mem))]
1515 pub(crate) fn guest_mapped_size(&self) -> usize {
1516 self.guest_mapped_size
1517 }
1518
1519 pub(crate) fn from_file(file: &std::fs::File, guest_mapped_size: usize) -> Result<Self> {
1525 let len: usize = file
1526 .metadata()
1527 .map_err(|e| new_error!("Failed to read file metadata: {}", e))?
1528 .len()
1529 .try_into()
1530 .map_err(|_| new_error!("File length exceeds usize::MAX"))?;
1531
1532 if len == 0 {
1533 return Err(new_error!(
1534 "Cannot create file-backed shared memory with size 0"
1535 ));
1536 }
1537
1538 if !len.is_multiple_of(PAGE_SIZE_USIZE) {
1539 return Err(new_error!(
1540 "file length {} must be a multiple of PAGE_SIZE",
1541 len
1542 ));
1543 }
1544
1545 if guest_mapped_size == 0
1546 || guest_mapped_size > len
1547 || !guest_mapped_size.is_multiple_of(PAGE_SIZE_USIZE)
1548 {
1549 return Err(new_error!(
1550 "guest_mapped_size {} must be a non-zero multiple of PAGE_SIZE no greater than file length {}",
1551 guest_mapped_size,
1552 len
1553 ));
1554 }
1555
1556 let region = Self::map_file(file, len)?;
1557 Ok(ReadonlySharedMemory {
1558 region,
1559 guest_mapped_size,
1560 })
1561 }
1562
1563 #[cfg(target_os = "linux")]
1567 fn map_file(file: &std::fs::File, len: usize) -> Result<Arc<HostMapping>> {
1568 use std::os::unix::io::AsRawFd;
1569
1570 #[cfg(mshv3)]
1571 use libc::PROT_WRITE;
1572 use libc::{
1573 MAP_ANONYMOUS, MAP_FAILED, MAP_FIXED, MAP_NORESERVE, MAP_PRIVATE, PROT_NONE, PROT_READ,
1574 mmap, off_t, size_t,
1575 };
1576
1577 let total_size = len.checked_add(2 * PAGE_SIZE_USIZE).ok_or_else(|| {
1578 new_error!("Memory required for file-backed mapping exceeded usize::MAX")
1579 })?;
1580
1581 let fd = file.as_raw_fd();
1582
1583 let base = unsafe {
1589 mmap(
1590 null_mut(),
1591 total_size as size_t,
1592 PROT_NONE,
1593 MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE,
1594 -1,
1595 0 as off_t,
1596 )
1597 };
1598 if base == MAP_FAILED {
1599 return Err(HyperlightError::MmapFailed(
1600 std::io::Error::last_os_error().raw_os_error(),
1601 ));
1602 }
1603 let reservation = Mmap {
1604 base,
1605 len: total_size,
1606 };
1607
1608 #[cfg(mshv3)]
1617 let file_prot = PROT_READ | PROT_WRITE;
1618 #[cfg(not(mshv3))]
1619 let file_prot = PROT_READ;
1620 let usable_ptr = unsafe { (base as *mut u8).add(PAGE_SIZE_USIZE) };
1623 let mapped = unsafe {
1629 mmap(
1630 usable_ptr as *mut c_void,
1631 len as size_t,
1632 file_prot,
1633 MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE,
1634 fd,
1635 0 as off_t,
1636 )
1637 };
1638 if mapped == MAP_FAILED {
1639 return Err(HyperlightError::MmapFailed(
1640 std::io::Error::last_os_error().raw_os_error(),
1641 ));
1642 }
1643
1644 #[allow(clippy::arc_with_non_send_sync)]
1648 Ok(Arc::new(HostMapping { mmap: reservation }))
1649 }
1650
1651 #[cfg(target_os = "windows")]
1655 fn map_file(file: &std::fs::File, len: usize) -> Result<Arc<HostMapping>> {
1656 use std::os::windows::io::AsRawHandle;
1657
1658 let total_size = len.checked_add(2 * PAGE_SIZE_USIZE).ok_or_else(|| {
1659 new_error!("Memory required for file-backed mapping exceeded usize::MAX")
1660 })?;
1661
1662 let file_handle = HANDLE(file.as_raw_handle());
1663
1664 let whole = Placeholder::reserve(total_size)?;
1667
1668 let (leading, middle, trailing) = whole.split_into_three(PAGE_SIZE_USIZE, len)?;
1672
1673 let raw_handle =
1678 unsafe { CreateFileMappingA(file_handle, None, PAGE_READONLY, 0, 0, PCSTR::null()) }?;
1679 if raw_handle.is_invalid() {
1680 log_then_return!(HyperlightError::MemoryAllocationFailed(
1681 Error::last_os_error().raw_os_error()
1682 ));
1683 }
1684 let file_mapping = FileMapping(raw_handle);
1685
1686 let view = middle.map_file_view(raw_handle)?;
1689
1690 #[allow(clippy::arc_with_non_send_sync)]
1691 Ok(Arc::new(HostMapping {
1692 mapping: WindowsMapping::FileBacked {
1693 leading,
1694 view,
1695 trailing,
1696 file_mapping,
1697 },
1698 }))
1699 }
1700
1701 pub(crate) fn as_slice(&self) -> &[u8] {
1702 unsafe { std::slice::from_raw_parts(self.base_ptr(), self.mem_size()) }
1703 }
1704
1705 #[cfg(unshared_snapshot_mem)]
1706 pub(crate) fn copy_to_writable(&self) -> Result<ExclusiveSharedMemory> {
1707 let mut writable = ExclusiveSharedMemory::new(self.mem_size())?;
1708 writable.copy_from_slice(self.as_slice(), 0)?;
1709 Ok(writable)
1710 }
1711
1712 #[cfg(not(unshared_snapshot_mem))]
1713 pub(crate) fn build(self) -> (Self, Self) {
1714 (self.clone(), self)
1715 }
1716
1717 #[cfg(not(unshared_snapshot_mem))]
1718 pub(crate) fn mapping_at(
1719 &self,
1720 guest_base: u64,
1721 region_type: MemoryRegionType,
1722 ) -> MemoryRegion {
1723 #[allow(clippy::panic)]
1724 if region_type != MemoryRegionType::Snapshot {
1728 panic!("ReadonlySharedMemory::mapping_at should only be used for Snapshot regions");
1729 }
1730 mapping_at(
1731 self,
1732 guest_base,
1733 self.guest_mapped_size(),
1734 region_type,
1735 MemoryRegionFlags::READ | MemoryRegionFlags::EXECUTE,
1736 )
1737 }
1738}
1739
1740impl SharedMemory for ReadonlySharedMemory {
1741 fn region(&self) -> &HostMapping {
1742 &self.region
1743 }
1744 #[cfg(windows)]
1754 fn host_region_base(&self) -> <HostGuestMemoryRegion as MemoryRegionKind>::HostBaseType {
1755 match &self.region().mapping {
1756 WindowsMapping::Anonymous { .. } => super::memory_region::HostRegionBase {
1757 from_handle: self.region().file_mapping_handle().into(),
1758 handle_base: self.region().ptr() as usize,
1759 handle_size: self.region().size(),
1760 offset: PAGE_SIZE_USIZE,
1761 },
1762 WindowsMapping::FileBacked { .. } => super::memory_region::HostRegionBase {
1763 from_handle: self.region().file_mapping_handle().into(),
1764 handle_base: self.base_ptr() as usize,
1765 handle_size: self.mem_size(),
1766 offset: 0,
1767 },
1768 }
1769 }
1770 fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
1773 &mut self,
1774 _: F,
1775 ) -> Result<T> {
1776 Err(new_error!(
1777 "Cannot take exclusive access to a ReadonlySharedMemory"
1778 ))
1779 }
1780 fn with_contents<T, F: FnOnce(&[u8]) -> T>(&mut self, f: F) -> Result<T> {
1782 Ok(f(self.as_slice()))
1783 }
1784}
1785
1786impl<S: SharedMemory> PartialEq<S> for ReadonlySharedMemory {
1787 fn eq(&self, other: &S) -> bool {
1788 self.raw_ptr() == other.raw_ptr()
1789 }
1790}
1791
1792#[cfg(test)]
1793mod tests {
1794 use hyperlight_common::mem::PAGE_SIZE_USIZE;
1795 #[cfg(not(miri))]
1796 use proptest::prelude::*;
1797
1798 #[cfg(not(miri))]
1799 use super::HostSharedMemory;
1800 use super::{ExclusiveSharedMemory, SharedMemory};
1801 use crate::Result;
1802 #[cfg(not(miri))]
1803 use crate::mem::shared_mem_tests::read_write_test_suite;
1804
1805 #[test]
1806 fn fill() {
1807 let mem_size: usize = 4096;
1808 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
1809 let (mut hshm, _) = eshm.build();
1810
1811 hshm.fill(1, 0, 1024).unwrap();
1812 hshm.fill(2, 1024, 1024).unwrap();
1813 hshm.fill(3, 2048, 1024).unwrap();
1814 hshm.fill(4, 3072, 1024).unwrap();
1815
1816 let vec = hshm
1817 .with_exclusivity(|e| e.copy_all_to_vec().unwrap())
1818 .unwrap();
1819
1820 assert!(vec[0..1024].iter().all(|&x| x == 1));
1821 assert!(vec[1024..2048].iter().all(|&x| x == 2));
1822 assert!(vec[2048..3072].iter().all(|&x| x == 3));
1823 assert!(vec[3072..4096].iter().all(|&x| x == 4));
1824
1825 hshm.fill(5, 0, 4096).unwrap();
1826
1827 let vec2 = hshm
1828 .with_exclusivity(|e| e.copy_all_to_vec().unwrap())
1829 .unwrap();
1830 assert!(vec2.iter().all(|&x| x == 5));
1831
1832 assert!(hshm.fill(0, 0, mem_size + 1).is_err());
1833 assert!(hshm.fill(0, mem_size, 1).is_err());
1834 }
1835
1836 #[test]
1839 fn bounds_check_overflow() {
1840 let mem_size: usize = 4096;
1841 let mut eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
1842
1843 assert!(eshm.read_i32(usize::MAX).is_err());
1845 assert!(eshm.write_i32(usize::MAX, 0).is_err());
1846 assert!(eshm.copy_from_slice(&[0u8; 1], usize::MAX).is_err());
1847
1848 let (mut hshm, _) = eshm.build();
1850
1851 assert!(hshm.read::<u8>(usize::MAX).is_err());
1852 assert!(hshm.read::<u64>(usize::MAX - 3).is_err());
1853 assert!(hshm.write::<u8>(usize::MAX, 0).is_err());
1854 assert!(hshm.write::<u64>(usize::MAX - 3, 0).is_err());
1855
1856 let mut buf = [0u8; 1];
1857 assert!(hshm.copy_to_slice(&mut buf, usize::MAX).is_err());
1858 assert!(hshm.copy_from_slice(&[0u8; 1], usize::MAX).is_err());
1859
1860 assert!(hshm.fill(0, usize::MAX, 1).is_err());
1861 assert!(hshm.fill(0, 1, usize::MAX).is_err());
1862 }
1863
1864 #[test]
1865 fn copy_into_from() -> Result<()> {
1866 let mem_size: usize = 4096;
1867 let vec_len = 10;
1868 let eshm = ExclusiveSharedMemory::new(mem_size)?;
1869 let (hshm, _) = eshm.build();
1870 let vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
1871 hshm.copy_from_slice(&vec, 0)?;
1873
1874 let mut vec2 = vec![0; vec_len];
1875 hshm.copy_to_slice(vec2.as_mut_slice(), 0)?;
1877 assert_eq!(vec, vec2);
1878
1879 let offset = mem_size - vec.len();
1880 hshm.copy_from_slice(&vec, offset)?;
1882
1883 let mut vec3 = vec![0; vec_len];
1884 hshm.copy_to_slice(&mut vec3, offset)?;
1886 assert_eq!(vec, vec3);
1887
1888 let offset = mem_size / 2;
1889 hshm.copy_from_slice(&vec, offset)?;
1891
1892 let mut vec4 = vec![0; vec_len];
1893 hshm.copy_to_slice(&mut vec4, offset)?;
1895 assert_eq!(vec, vec4);
1896
1897 let mut vec5 = vec![0; vec_len];
1899 assert!(hshm.copy_to_slice(&mut vec5, mem_size).is_err());
1900
1901 assert!(hshm.copy_from_slice(&vec5, mem_size).is_err());
1903
1904 let mut vec6 = vec![0; vec_len];
1906 assert!(hshm.copy_to_slice(&mut vec6, mem_size * 2).is_err());
1907
1908 assert!(hshm.copy_from_slice(&vec6, mem_size * 2).is_err());
1910
1911 let mut vec7 = vec![0; mem_size * 2];
1913 assert!(hshm.copy_to_slice(&mut vec7, 0).is_err());
1914
1915 assert!(hshm.copy_from_slice(&vec7, 0).is_err());
1917
1918 Ok(())
1919 }
1920
1921 #[cfg(not(miri))]
1923 proptest! {
1924 #[test]
1925 fn read_write_i32(val in -0x1000_i32..0x1000_i32) {
1926 read_write_test_suite(
1927 val,
1928 ExclusiveSharedMemory::new,
1929 Box::new(ExclusiveSharedMemory::read_i32),
1930 Box::new(ExclusiveSharedMemory::write_i32),
1931 )
1932 .unwrap();
1933 read_write_test_suite(
1934 val,
1935 |s| {
1936 let e = ExclusiveSharedMemory::new(s)?;
1937 let (h, _) = e.build();
1938 Ok(h)
1939 },
1940 Box::new(HostSharedMemory::read::<i32>),
1941 Box::new(|h, o, v| h.write::<i32>(o, v)),
1942 )
1943 .unwrap();
1944 }
1945 }
1946
1947 #[test]
1948 fn alloc_fail() {
1949 let gm = ExclusiveSharedMemory::new(0);
1950 assert!(gm.is_err());
1951 let gm = ExclusiveSharedMemory::new(usize::MAX);
1952 assert!(gm.is_err());
1953 }
1954
1955 #[test]
1956 fn clone() {
1957 let eshm = ExclusiveSharedMemory::new(PAGE_SIZE_USIZE).unwrap();
1958 let (hshm1, _) = eshm.build();
1959 let hshm2 = hshm1.clone();
1960
1961 assert_eq!(hshm1.mem_size(), hshm2.mem_size());
1964 assert_eq!(hshm1.base_addr(), hshm2.base_addr());
1965
1966 hshm1.copy_from_slice(b"a", 0).unwrap();
1969 hshm2.copy_from_slice(b"b", 1).unwrap();
1970
1971 for (raw_offset, expected) in &[(0, b'a'), (1, b'b')] {
1974 assert_eq!(hshm1.read::<u8>(*raw_offset).unwrap(), *expected);
1975 assert_eq!(hshm2.read::<u8>(*raw_offset).unwrap(), *expected);
1976 }
1977
1978 drop(hshm1);
1981
1982 for (raw_offset, expected) in &[(0, b'a'), (1, b'b')] {
1984 assert_eq!(hshm2.read::<u8>(*raw_offset).unwrap(), *expected);
1985 }
1986 hshm2.copy_from_slice(b"c", 2).unwrap();
1987 assert_eq!(hshm2.read::<u8>(2).unwrap(), b'c');
1988 drop(hshm2);
1989 }
1990
1991 #[test]
1992 fn copy_all_to_vec() {
1993 let mut data = vec![b'a', b'b', b'c'];
1994 data.resize(4096, 0);
1995 let mut eshm = ExclusiveSharedMemory::new(data.len()).unwrap();
1996 eshm.copy_from_slice(data.as_slice(), 0).unwrap();
1997 let ret_vec = eshm.copy_all_to_vec().unwrap();
1998 assert_eq!(data, ret_vec);
1999 }
2000
2001 #[test]
2004 #[cfg(all(target_os = "linux", not(miri)))]
2005 fn test_drop() {
2006 use proc_maps::get_process_maps;
2007
2008 const UNIQUE_SIZE: usize = PAGE_SIZE_USIZE * 17;
2018
2019 let pid = std::process::id();
2020
2021 let eshm = ExclusiveSharedMemory::new(UNIQUE_SIZE).unwrap();
2022 let (hshm1, gshm) = eshm.build();
2023 let hshm2 = hshm1.clone();
2024
2025 let base_ptr = hshm1.base_ptr() as usize;
2027 let mem_size = hshm1.mem_size();
2028
2029 let has_exact_mapping = |ptr: usize, size: usize| -> bool {
2031 get_process_maps(pid.try_into().unwrap())
2032 .unwrap()
2033 .iter()
2034 .any(|m| m.start() == ptr && m.size() == size)
2035 };
2036
2037 assert!(
2039 has_exact_mapping(base_ptr, mem_size),
2040 "shared memory mapping not found at {:#x} with size {}",
2041 base_ptr,
2042 mem_size
2043 );
2044
2045 drop(hshm1);
2047 drop(hshm2);
2048 drop(gshm);
2049
2050 assert!(
2052 !has_exact_mapping(base_ptr, mem_size),
2053 "shared memory mapping still exists at {:#x} with size {} after drop",
2054 base_ptr,
2055 mem_size
2056 );
2057 }
2058
2059 mod alignment_tests {
2063 use super::*;
2064
2065 const CHUNK_SIZE: usize = size_of::<u128>();
2066
2067 #[test]
2069 fn copy_with_various_alignments() {
2070 let mem_size: usize = 4096;
2072 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2073 let (hshm, _) = eshm.build();
2074
2075 for start_offset in 0..CHUNK_SIZE {
2077 let test_len = 64; let test_data: Vec<u8> = (0..test_len).map(|i| (i + start_offset) as u8).collect();
2079
2080 hshm.copy_from_slice(&test_data, start_offset).unwrap();
2082
2083 let mut read_buf = vec![0u8; test_len];
2085 hshm.copy_to_slice(&mut read_buf, start_offset).unwrap();
2086
2087 assert_eq!(
2088 test_data, read_buf,
2089 "Mismatch at alignment offset {}",
2090 start_offset
2091 );
2092 }
2093 }
2094
2095 #[test]
2097 fn copy_small_lengths() {
2098 let mem_size: usize = 4096;
2099 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2100 let (hshm, _) = eshm.build();
2101
2102 for len in 0..CHUNK_SIZE {
2103 let test_data: Vec<u8> = (0..len).map(|i| i as u8).collect();
2104
2105 hshm.copy_from_slice(&test_data, 0).unwrap();
2106
2107 let mut read_buf = vec![0u8; len];
2108 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2109
2110 assert_eq!(test_data, read_buf, "Mismatch for length {}", len);
2111 }
2112 }
2113
2114 #[test]
2116 fn copy_non_aligned_lengths() {
2117 let mem_size: usize = 4096;
2118 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2119 let (hshm, _) = eshm.build();
2120
2121 let test_lengths = [17, 31, 33, 47, 63, 65, 100, 127, 129, 255, 257];
2123
2124 for &len in &test_lengths {
2125 let test_data: Vec<u8> = (0..len).map(|i| (i % 256) as u8).collect();
2126
2127 hshm.copy_from_slice(&test_data, 0).unwrap();
2128
2129 let mut read_buf = vec![0u8; len];
2130 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2131
2132 assert_eq!(test_data, read_buf, "Mismatch for length {}", len);
2133 }
2134 }
2135
2136 #[test]
2138 fn copy_exact_chunk_size() {
2139 let mem_size: usize = 4096;
2140 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2141 let (hshm, _) = eshm.build();
2142
2143 let test_data: Vec<u8> = (0..CHUNK_SIZE).map(|i| i as u8).collect();
2144
2145 hshm.copy_from_slice(&test_data, 0).unwrap();
2146
2147 let mut read_buf = vec![0u8; CHUNK_SIZE];
2148 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2149
2150 assert_eq!(test_data, read_buf);
2151 }
2152
2153 #[test]
2155 fn fill_with_various_alignments() {
2156 let mem_size: usize = 4096;
2157 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2158 let (mut hshm, _) = eshm.build();
2159
2160 for start_offset in 0..CHUNK_SIZE {
2161 let fill_len = 64;
2162 let fill_value = (start_offset % 256) as u8;
2163
2164 hshm.fill(0, 0, mem_size).unwrap();
2166
2167 hshm.fill(fill_value, start_offset, fill_len).unwrap();
2169
2170 let mut read_buf = vec![0u8; fill_len];
2172 hshm.copy_to_slice(&mut read_buf, start_offset).unwrap();
2173
2174 assert!(
2175 read_buf.iter().all(|&b| b == fill_value),
2176 "Fill mismatch at alignment offset {}",
2177 start_offset
2178 );
2179 }
2180 }
2181
2182 #[test]
2184 fn fill_small_lengths() {
2185 let mem_size: usize = 4096;
2186 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2187 let (mut hshm, _) = eshm.build();
2188
2189 for len in 0..CHUNK_SIZE {
2190 let fill_value = 0xAB;
2191
2192 hshm.fill(0, 0, mem_size).unwrap(); hshm.fill(fill_value, 0, len).unwrap();
2194
2195 let mut read_buf = vec![0u8; len];
2196 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2197
2198 assert!(
2199 read_buf.iter().all(|&b| b == fill_value),
2200 "Fill mismatch for length {}",
2201 len
2202 );
2203 }
2204 }
2205
2206 #[test]
2208 fn fill_non_aligned_lengths() {
2209 let mem_size: usize = 4096;
2210 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2211 let (mut hshm, _) = eshm.build();
2212
2213 let test_lengths = [17, 31, 33, 47, 63, 65, 100, 127, 129, 255, 257];
2214
2215 for &len in &test_lengths {
2216 let fill_value = 0xCD;
2217
2218 hshm.fill(0, 0, mem_size).unwrap(); hshm.fill(fill_value, 0, len).unwrap();
2220
2221 let mut read_buf = vec![0u8; len];
2222 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2223
2224 assert!(
2225 read_buf.iter().all(|&b| b == fill_value),
2226 "Fill mismatch for length {}",
2227 len
2228 );
2229 }
2230 }
2231
2232 #[test]
2234 fn copy_edge_cases() {
2235 let mem_size: usize = 4096;
2236 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2237 let (hshm, _) = eshm.build();
2238
2239 let empty: Vec<u8> = vec![];
2241 hshm.copy_from_slice(&empty, 0).unwrap();
2242 let mut read_buf: Vec<u8> = vec![];
2243 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2244 assert!(read_buf.is_empty());
2245
2246 let single = vec![0x42u8];
2248 hshm.copy_from_slice(&single, 0).unwrap();
2249 let mut read_buf = vec![0u8; 1];
2250 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2251 assert_eq!(single, read_buf);
2252 }
2253
2254 #[test]
2256 fn copy_unaligned_start_and_length() {
2257 let mem_size: usize = 4096;
2258 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2259 let (hshm, _) = eshm.build();
2260
2261 let start_offset = 7;
2263 let len = 37;
2264 let test_data: Vec<u8> = (0..len).map(|i| (i * 3) as u8).collect();
2265
2266 hshm.copy_from_slice(&test_data, start_offset).unwrap();
2267
2268 let mut read_buf = vec![0u8; len];
2269 hshm.copy_to_slice(&mut read_buf, start_offset).unwrap();
2270
2271 assert_eq!(test_data, read_buf);
2272 }
2273 }
2274
2275 mod try_pop_buffer_bounds {
2277 use super::*;
2278
2279 #[derive(Debug, PartialEq)]
2280 struct RawBytes(Vec<u8>);
2281
2282 impl TryFrom<&[u8]> for RawBytes {
2283 type Error = String;
2284 fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
2285 Ok(RawBytes(value.to_vec()))
2286 }
2287 }
2288
2289 fn make_buffer(mem_size: usize) -> super::super::HostSharedMemory {
2291 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2292 let (hshm, _) = eshm.build();
2293 hshm.write::<u64>(0, 8u64).unwrap();
2294 hshm
2295 }
2296
2297 #[test]
2298 fn normal_push_pop_roundtrip() {
2299 let mem_size = 4096;
2300 let mut hshm = make_buffer(mem_size);
2301
2302 let payload = b"hello";
2304 let mut data = Vec::new();
2305 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2306 data.extend_from_slice(payload);
2307
2308 hshm.push_buffer(0, mem_size, &data).unwrap();
2309 let result: RawBytes = hshm.try_pop_buffer_into(0, mem_size).unwrap();
2310 assert_eq!(result.0, data);
2311 }
2312
2313 #[test]
2314 fn malicious_flatbuffer_size_prefix() {
2315 let mem_size = 4096;
2316 let mut hshm = make_buffer(mem_size);
2317
2318 let payload = b"small";
2319 let mut data = Vec::new();
2320 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2321 data.extend_from_slice(payload);
2322 hshm.push_buffer(0, mem_size, &data).unwrap();
2323
2324 hshm.write::<u32>(8, 0xFFFF_FFFBu32).unwrap(); let result: Result<RawBytes> = hshm.try_pop_buffer_into(0, mem_size);
2328 let err_msg = format!("{}", result.unwrap_err());
2329 assert!(
2330 err_msg.contains("Corrupt buffer size prefix: flatbuffer claims 4294967295 bytes but the element slot is only 9 bytes"),
2331 "Unexpected error message: {}",
2332 err_msg
2333 );
2334 }
2335
2336 #[test]
2337 fn malicious_element_offset_too_small() {
2338 let mem_size = 4096;
2339 let mut hshm = make_buffer(mem_size);
2340
2341 let payload = b"test";
2342 let mut data = Vec::new();
2343 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2344 data.extend_from_slice(payload);
2345 hshm.push_buffer(0, mem_size, &data).unwrap();
2346
2347 hshm.write::<u64>(16, 0u64).unwrap();
2349
2350 let result: Result<RawBytes> = hshm.try_pop_buffer_into(0, mem_size);
2351 let err_msg = format!("{}", result.unwrap_err());
2352 assert!(
2353 err_msg.contains(
2354 "Corrupt buffer back-pointer: element offset 0 is outside valid range [8, 8]"
2355 ),
2356 "Unexpected error message: {}",
2357 err_msg
2358 );
2359 }
2360
2361 #[test]
2362 fn malicious_element_offset_past_stack_pointer() {
2363 let mem_size = 4096;
2364 let mut hshm = make_buffer(mem_size);
2365
2366 let payload = b"test";
2367 let mut data = Vec::new();
2368 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2369 data.extend_from_slice(payload);
2370 hshm.push_buffer(0, mem_size, &data).unwrap();
2371
2372 hshm.write::<u64>(16, 9999u64).unwrap();
2374
2375 let result: Result<RawBytes> = hshm.try_pop_buffer_into(0, mem_size);
2376 let err_msg = format!("{}", result.unwrap_err());
2377 assert!(
2378 err_msg.contains(
2379 "Corrupt buffer back-pointer: element offset 9999 is outside valid range [8, 8]"
2380 ),
2381 "Unexpected error message: {}",
2382 err_msg
2383 );
2384 }
2385
2386 #[test]
2387 fn malicious_flatbuffer_size_off_by_one() {
2388 let mem_size = 4096;
2389 let mut hshm = make_buffer(mem_size);
2390
2391 let payload = b"abcd";
2392 let mut data = Vec::new();
2393 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2394 data.extend_from_slice(payload);
2395 hshm.push_buffer(0, mem_size, &data).unwrap();
2396
2397 hshm.write::<u32>(8, 5u32).unwrap(); let result: Result<RawBytes> = hshm.try_pop_buffer_into(0, mem_size);
2401 let err_msg = format!("{}", result.unwrap_err());
2402 assert!(
2403 err_msg.contains("Corrupt buffer size prefix: flatbuffer claims 9 bytes but the element slot is only 8 bytes"),
2404 "Unexpected error message: {}",
2405 err_msg
2406 );
2407 }
2408
2409 #[test]
2412 fn back_pointer_near_stack_pointer_underflow() {
2413 let mem_size = 4096;
2414 let mut hshm = make_buffer(mem_size);
2415
2416 let payload = b"test";
2417 let mut data = Vec::new();
2418 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2419 data.extend_from_slice(payload);
2420 hshm.push_buffer(0, mem_size, &data).unwrap();
2421
2422 hshm.write::<u64>(16, 23u64).unwrap();
2424
2425 let result: Result<RawBytes> = hshm.try_pop_buffer_into(0, mem_size);
2426 let err_msg = format!("{}", result.unwrap_err());
2427 assert!(
2428 err_msg.contains(
2429 "Corrupt buffer back-pointer: element offset 23 is outside valid range [8, 8]"
2430 ),
2431 "Unexpected error message: {}",
2432 err_msg
2433 );
2434 }
2435
2436 #[test]
2438 fn size_prefix_u32_overflow() {
2439 let mem_size = 4096;
2440 let mut hshm = make_buffer(mem_size);
2441
2442 let payload = b"test";
2443 let mut data = Vec::new();
2444 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2445 data.extend_from_slice(payload);
2446 hshm.push_buffer(0, mem_size, &data).unwrap();
2447
2448 hshm.write::<u32>(8, 0xFFFF_FFFDu32).unwrap();
2450
2451 let result: Result<RawBytes> = hshm.try_pop_buffer_into(0, mem_size);
2452 let err_msg = format!("{}", result.unwrap_err());
2453 assert!(
2454 err_msg.contains("Corrupt buffer size prefix: value 4294967293 overflows when adding 4-byte header"),
2455 "Unexpected error message: {}",
2456 err_msg
2457 );
2458 }
2459 }
2460
2461 #[cfg(target_os = "linux")]
2462 mod guard_page_crash_test {
2463 use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory};
2464
2465 const TEST_EXIT_CODE: u8 = 211; fn setup_signal_handler() {
2471 unsafe {
2472 signal_hook_registry::register_signal_unchecked(libc::SIGSEGV, || {
2473 std::process::exit(TEST_EXIT_CODE.into());
2474 })
2475 .unwrap();
2476 }
2477 }
2478
2479 #[test]
2480 #[ignore] fn read() {
2482 setup_signal_handler();
2483
2484 let eshm = ExclusiveSharedMemory::new(4096).unwrap();
2485 let (hshm, _) = eshm.build();
2486 let guard_page_ptr = hshm.raw_ptr();
2487 unsafe { std::ptr::read_volatile(guard_page_ptr) };
2488 }
2489
2490 #[test]
2491 #[ignore] fn write() {
2493 setup_signal_handler();
2494
2495 let eshm = ExclusiveSharedMemory::new(4096).unwrap();
2496 let (hshm, _) = eshm.build();
2497 let guard_page_ptr = hshm.raw_ptr();
2498 unsafe { std::ptr::write_volatile(guard_page_ptr, 0u8) };
2499 }
2500
2501 #[test]
2502 #[ignore] fn exec() {
2504 setup_signal_handler();
2505
2506 let eshm = ExclusiveSharedMemory::new(4096).unwrap();
2507 let (hshm, _) = eshm.build();
2508 let guard_page_ptr = hshm.raw_ptr();
2509 let func: fn() = unsafe { std::mem::transmute(guard_page_ptr) };
2510 func();
2511 }
2512
2513 #[test]
2515 #[cfg_attr(miri, ignore)] fn guard_page_testing_shim() {
2517 let tests = vec!["read", "write", "exec"];
2518 for test in tests {
2519 let triple = std::env::var("TARGET_TRIPLE").ok();
2520 let target_args = if let Some(triple) = triple.filter(|t| !t.is_empty()) {
2521 vec!["--target".to_string(), triple.to_string()]
2522 } else {
2523 vec![]
2524 };
2525 let output = std::process::Command::new("cargo")
2526 .args(["test", "-p", "hyperlight-host", "--lib"])
2527 .args(target_args)
2528 .args(["--", "--ignored", test])
2529 .stdin(std::process::Stdio::null())
2530 .output()
2531 .expect("Unable to launch tests");
2532 let exit_code = output.status.code();
2533 if exit_code != Some(TEST_EXIT_CODE.into()) {
2534 eprintln!("=== Guard Page test '{}' failed ===", test);
2535 eprintln!("Exit code: {:?} (expected {})", exit_code, TEST_EXIT_CODE);
2536 eprintln!("=== STDOUT ===");
2537 eprintln!("{}", String::from_utf8_lossy(&output.stdout));
2538 eprintln!("=== STDERR ===");
2539 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
2540 panic!(
2541 "Guard Page test failed: {} (exit code {:?}, expected {})",
2542 test, exit_code, TEST_EXIT_CODE
2543 );
2544 }
2545 }
2546 }
2547 }
2548
2549 #[cfg(not(miri))]
2550 mod from_file_tests {
2551 use std::io::Write;
2552
2553 use hyperlight_common::mem::PAGE_SIZE_USIZE;
2554 use tempfile::NamedTempFile;
2555
2556 use crate::mem::shared_mem::{ReadonlySharedMemory, SharedMemory};
2557
2558 pub(super) fn make_temp_file(len: usize) -> NamedTempFile {
2559 let mut f = NamedTempFile::new().expect("create temp file");
2560 if len > 0 {
2561 let mut buf = vec![0u8; len];
2562 for (i, b) in buf.iter_mut().enumerate() {
2563 *b = (i & 0xff) as u8;
2564 }
2565 f.write_all(&buf).expect("write temp file");
2566 f.flush().expect("flush temp file");
2567 }
2568 f
2569 }
2570
2571 #[test]
2572 fn from_file_success_single_page() {
2573 let tmp = make_temp_file(PAGE_SIZE_USIZE);
2574 let mut rsm = ReadonlySharedMemory::from_file(tmp.as_file(), PAGE_SIZE_USIZE)
2575 .expect("from_file should succeed");
2576 assert_eq!(rsm.mem_size(), PAGE_SIZE_USIZE);
2577 rsm.with_contents(|slice| {
2578 for (i, b) in slice.iter().enumerate() {
2579 assert_eq!(*b, (i & 0xff) as u8);
2580 }
2581 })
2582 .expect("with_contents should succeed");
2583 }
2584
2585 #[test]
2586 fn from_file_success_smaller_guest_mapped_size() {
2587 let tmp = make_temp_file(2 * PAGE_SIZE_USIZE);
2588 let rsm = ReadonlySharedMemory::from_file(tmp.as_file(), PAGE_SIZE_USIZE)
2589 .expect("from_file should succeed");
2590 assert_eq!(rsm.mem_size(), 2 * PAGE_SIZE_USIZE);
2591 }
2592
2593 #[test]
2594 fn from_file_rejects_empty_file() {
2595 let tmp = make_temp_file(0);
2596 let err = ReadonlySharedMemory::from_file(tmp.as_file(), PAGE_SIZE_USIZE)
2597 .expect_err("empty file should be rejected");
2598 assert!(format!("{}", err).contains("size 0"));
2599 }
2600
2601 #[test]
2602 fn from_file_rejects_unaligned_file_length() {
2603 let tmp = make_temp_file(PAGE_SIZE_USIZE + 1);
2604 let err = ReadonlySharedMemory::from_file(tmp.as_file(), PAGE_SIZE_USIZE)
2605 .expect_err("unaligned file length should be rejected");
2606 assert!(format!("{}", err).contains("multiple of PAGE_SIZE"));
2607 }
2608
2609 #[test]
2610 fn from_file_rejects_zero_guest_mapped_size() {
2611 let tmp = make_temp_file(PAGE_SIZE_USIZE);
2612 let err = ReadonlySharedMemory::from_file(tmp.as_file(), 0)
2613 .expect_err("zero guest_mapped_size should be rejected");
2614 assert!(format!("{}", err).contains("guest_mapped_size"));
2615 }
2616
2617 #[test]
2618 fn from_file_rejects_unaligned_guest_mapped_size() {
2619 let tmp = make_temp_file(2 * PAGE_SIZE_USIZE);
2620 let err = ReadonlySharedMemory::from_file(tmp.as_file(), PAGE_SIZE_USIZE + 1)
2621 .expect_err("unaligned guest_mapped_size should be rejected");
2622 assert!(format!("{}", err).contains("guest_mapped_size"));
2623 }
2624
2625 #[test]
2626 fn from_file_rejects_guest_mapped_size_exceeding_file() {
2627 let tmp = make_temp_file(PAGE_SIZE_USIZE);
2628 let err = ReadonlySharedMemory::from_file(tmp.as_file(), 2 * PAGE_SIZE_USIZE)
2629 .expect_err("guest_mapped_size > file length should be rejected");
2630 assert!(format!("{}", err).contains("guest_mapped_size"));
2631 }
2632
2633 mod guard_page_crash_tests {
2639 use hyperlight_common::mem::PAGE_SIZE_USIZE;
2640
2641 use super::make_temp_file;
2642 use crate::mem::shared_mem::{ReadonlySharedMemory, SharedMemory};
2643
2644 #[test]
2646 #[ignore]
2647 pub(super) fn leading_guard_page_traps() {
2648 let tmp = make_temp_file(PAGE_SIZE_USIZE);
2649 let rsm = ReadonlySharedMemory::from_file(tmp.as_file(), PAGE_SIZE_USIZE)
2650 .expect("from_file should succeed");
2651 let guard_ptr = unsafe { rsm.base_ptr().sub(PAGE_SIZE_USIZE) };
2652 println!("reached_guard");
2653 let _ = unsafe { std::ptr::read_volatile(guard_ptr) };
2654 println!("survived_guard");
2655 }
2656
2657 #[test]
2659 #[ignore]
2660 pub(super) fn trailing_guard_page_traps() {
2661 let tmp = make_temp_file(PAGE_SIZE_USIZE);
2662 let rsm = ReadonlySharedMemory::from_file(tmp.as_file(), PAGE_SIZE_USIZE)
2663 .expect("from_file should succeed");
2664 let guard_ptr = unsafe { rsm.base_ptr().add(rsm.mem_size()) };
2665 println!("reached_guard");
2666 let _ = unsafe { std::ptr::read_volatile(guard_ptr) };
2667 println!("survived_guard");
2668 }
2669 }
2670
2671 #[test]
2676 #[cfg_attr(miri, ignore)] fn from_file_guard_page_shim() {
2678 use guard_page_crash_tests::{leading_guard_page_traps, trailing_guard_page_traps};
2679 let ignored_test_paths = [
2680 test_path(leading_guard_page_traps),
2681 test_path(trailing_guard_page_traps),
2682 ];
2683
2684 let exe = std::env::current_exe().expect("current_exe");
2685 for path in &ignored_test_paths {
2686 run_guard_page_subprocess(&exe, path);
2687 }
2688 }
2689
2690 fn test_path<F: Fn()>(_: F) -> &'static str {
2694 let full = std::any::type_name::<F>();
2695 let (_, rest) = full
2696 .split_once("::")
2697 .expect("type_name of a function item is always qualified by the crate name");
2698 rest
2699 }
2700
2701 fn run_guard_page_subprocess(exe: &std::path::Path, ignored_test_path: &str) {
2702 let output = std::process::Command::new(exe)
2703 .args([
2704 "--ignored",
2705 "--nocapture",
2706 "--exact",
2707 "--test-threads=1",
2708 ignored_test_path,
2709 ])
2710 .stdin(std::process::Stdio::null())
2711 .output()
2712 .expect("Unable to launch subprocess test");
2713
2714 let stdout = String::from_utf8_lossy(&output.stdout);
2715 let stderr = String::from_utf8_lossy(&output.stderr);
2716
2717 let ran_test = stdout.contains("running 1 test");
2720 let reached = stdout.contains("reached_guard");
2721 let survived = stdout.contains("survived_guard");
2722 let by_access_violation = killed_by_access_violation(&output.status);
2723
2724 let ok = reached && !survived && by_access_violation && ran_test;
2725 if !ok {
2726 eprintln!("=== Guard page shim failed for {} ===", ignored_test_path);
2727 eprintln!(
2728 "status={:?} ran_test={} reached={} survived={} by_access_violation={}",
2729 output.status, ran_test, reached, survived, by_access_violation
2730 );
2731 eprintln!("=== STDOUT ===\n{}", stdout);
2732 eprintln!("=== STDERR ===\n{}", stderr);
2733 let hint = if !ran_test {
2734 format!(
2735 "\nHINT: ran_test=false (subprocess reported 'running 0 tests'). \
2736 Most likely cause is a stale test path in the shim. Verify that \
2737 `{}` still exists and matches the path passed via --exact above.",
2738 ignored_test_path
2739 )
2740 } else {
2741 String::new()
2742 };
2743 panic!(
2744 "Expected subprocess to run {}, print 'reached_guard', \
2745 then die from a memory access fault. ran_test={}, reached={}, \
2746 survived={}, by_access_violation={}, status={:?}{}",
2747 ignored_test_path,
2748 ran_test,
2749 reached,
2750 survived,
2751 by_access_violation,
2752 output.status,
2753 hint
2754 );
2755 }
2756
2757 println!(
2758 "guard page trap confirmed for {}: subprocess terminated with {:?}",
2759 ignored_test_path, output.status
2760 );
2761 }
2762
2763 fn killed_by_access_violation(status: &std::process::ExitStatus) -> bool {
2767 #[cfg(unix)]
2768 {
2769 use std::os::unix::process::ExitStatusExt;
2770 status.signal() == Some(libc::SIGSEGV)
2771 }
2772 #[cfg(windows)]
2773 {
2774 use windows::Win32::Foundation::STATUS_ACCESS_VIOLATION;
2775 status.code() == Some(STATUS_ACCESS_VIOLATION.0) || status.code() == Some(0xDEAD)
2777 }
2778 }
2779 }
2780}