1use std::any::type_name;
5use std::ffi::c_void;
6use std::io::Error;
7use std::mem::{align_of, size_of};
8#[cfg(unix)]
9use std::ptr::null_mut;
10use std::sync::{Arc, RwLock};
11
12use bytemuck::Pod;
13use thiserror::Error;
14use tracing::{Span, instrument};
15#[cfg(target_os = "windows")]
16use windows::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE};
17#[cfg(target_os = "windows")]
18use windows::Win32::System::Memory::PAGE_READWRITE;
19#[cfg(target_os = "windows")]
20use windows::Win32::System::Memory::{
21 CreateFileMappingA, FILE_MAP_ALL_ACCESS, MEM_PRESERVE_PLACEHOLDER, MEM_RELEASE,
22 MEM_REPLACE_PLACEHOLDER, MEM_RESERVE, MEM_RESERVE_PLACEHOLDER, MEMORY_MAPPED_VIEW_ADDRESS,
23 MapViewOfFile, MapViewOfFile3, PAGE_NOACCESS, PAGE_PROTECTION_FLAGS, PAGE_READONLY,
24 UnmapViewOfFile, VIRTUAL_ALLOCATION_TYPE, VIRTUAL_FREE_TYPE, VirtualAlloc2, VirtualFree,
25 VirtualProtect,
26};
27#[cfg(target_os = "windows")]
28use windows::core::PCSTR;
29
30use super::memory_region::{
31 HostGuestMemoryRegion, MemoryRegion, MemoryRegionFlags, MemoryRegionKind, MemoryRegionType,
32};
33use crate::log_then_return;
34
35type Result<T> = core::result::Result<T, SharedMemoryError>;
36
37#[derive(Debug)]
40pub enum StackOp {
41 Push,
43 Pop,
45}
46#[derive(Error, Debug)]
48pub enum StackError {
49 #[error(
52 "Unable to {0:?} data from buffer: Stack pointer is out of bounds. Stack pointer: {1}, Buffer size: {2}"
53 )]
54 SpOob(StackOp, usize, usize),
55
56 #[error("Corrupt buffer back-pointer: element offset {0} is outside valid range [8, {1}].")]
58 CorruptBackPointer(usize, usize),
59
60 #[error("Corrupt buffer size prefix: value {0} overflows when adding 4-byte header.")]
63 OverflowingPrefix(u32),
64
65 #[error("Prefix too large: {0}")]
69 PrefixTooLarge(std::num::TryFromIntError),
70
71 #[error(
74 "Corrupt buffer size prefix: flatbuffer claims {0} bytes but the element slot is only {1} bytes."
75 )]
76 CorruptPrefix(usize, usize),
77
78 #[error("pop_buffer_into: failed to convert buffer to {0}")]
81 ConvertError(String),
82
83 #[error("Not enough space in buffer to push data. Required: {0}, Available: {1}")]
86 BufferFullError(usize, usize),
87}
88type ThisErrorHackBacktrace = std::backtrace::Backtrace;
92
93#[derive(Error, Debug)]
95pub enum SharedMemoryError {
96 #[error("Cannot access a value with size {0} at offset {1} in memory of size {2}")]
99 Bounds(usize, usize, usize),
100
101 #[error("Could not access metadata for file: {0}")]
104 FileMetadata(std::io::Error),
105
106 #[error("File size exceeded usize: {0}")]
109 FileTooLarge(std::num::TryFromIntError),
110
111 #[error("Could not acquire memory lock: {0} at {1}")]
114 LockError(String, ThisErrorHackBacktrace),
115
116 #[error("Memory Allocation Failed with OS Error {0:?}.")]
119 MemoryAllocationFailed(Option<i32>),
120
121 #[error(
124 "Memory request does not satisfy constraints: 0x{1:x} < 0x{0:x} <= 0x{2:x} && 0x{0:x} % 0x{3:x} = 0"
125 )]
126 MemoryRequest(usize, usize, usize, usize),
127
128 #[error("mmap failed with os error {0:?}")]
131 MmapFailed(Option<i32>),
132
133 #[error("mprotect failed with os error {0:?}")]
137 MprotectFailed(Option<i32>),
138
139 #[cfg(target_os = "windows")]
141 #[error("Windows API Error Result {0:?}")]
142 WindowsAPIError(#[from] windows_result::Error),
143
144 #[error("Cannot take exclusive access to a ReadonlySharedMemory")]
147 ReadonlySharedMemoryExclusiveRequest,
148
149 #[error("{0}")]
151 Stack(#[from] StackError),
152
153 #[error("Error reading slice {0}")]
156 TryFromSlice(#[from] std::array::TryFromSliceError),
157
158 #[error("Error reading int: {0}")]
161 TryFromInt(#[from] std::num::TryFromIntError),
162}
163impl<T> From<std::sync::TryLockError<T>> for SharedMemoryError {
164 fn from(e: std::sync::TryLockError<T>) -> SharedMemoryError {
165 SharedMemoryError::LockError(format!("{:?}", e), std::backtrace::Backtrace::capture())
166 }
167}
168
169macro_rules! bounds_check {
171 ($offset:expr, $size:expr, $mem_size:expr) => {
172 if $offset.checked_add($size).is_none_or(|end| end > $mem_size) {
173 return Err(SharedMemoryError::Bounds($offset, $size, $mem_size));
174 }
175 };
176}
177
178macro_rules! generate_reader {
180 ($fname:ident, $ty:ty) => {
181 #[allow(dead_code)]
183 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
184 pub(crate) fn $fname(&self, offset: usize) -> Result<$ty> {
185 let data = self.as_slice();
186 bounds_check!(offset, std::mem::size_of::<$ty>(), data.len());
187 Ok(<$ty>::from_le_bytes(
188 data[offset..offset + std::mem::size_of::<$ty>()].try_into()?,
189 ))
190 }
191 };
192}
193
194macro_rules! generate_writer {
196 ($fname:ident, $ty:ty) => {
197 #[allow(dead_code)]
199 pub(crate) fn $fname(&mut self, offset: usize, value: $ty) -> Result<()> {
200 let data = self.as_mut_slice();
201 bounds_check!(offset, std::mem::size_of::<$ty>(), data.len());
202 data[offset..offset + std::mem::size_of::<$ty>()].copy_from_slice(&value.to_le_bytes());
203 Ok(())
204 }
205 };
206}
207
208#[derive(Debug)]
213pub struct HostMapping {
214 #[cfg(not(target_os = "windows"))]
215 mmap: Mmap,
216 #[cfg(target_os = "windows")]
217 mapping: WindowsMapping,
218}
219
220#[cfg(target_os = "windows")]
222#[derive(Debug)]
223enum WindowsMapping {
224 Anonymous {
233 view: MappedView,
234 file_mapping: FileMapping,
235 },
236 FileBacked {
245 leading: Placeholder,
246 view: MappedView,
247 trailing: Placeholder,
248 file_mapping: FileMapping,
249 },
250}
251
252impl HostMapping {
253 pub(crate) fn ptr(&self) -> *mut u8 {
255 #[cfg(not(target_os = "windows"))]
256 {
257 self.mmap.base as *mut u8
258 }
259 #[cfg(target_os = "windows")]
260 match &self.mapping {
261 WindowsMapping::Anonymous { view, .. } => view.addr as *mut u8,
262 WindowsMapping::FileBacked { leading, .. } => leading.addr as *mut u8,
263 }
264 }
265
266 pub(crate) fn size(&self) -> usize {
268 #[cfg(not(target_os = "windows"))]
269 {
270 self.mmap.len
271 }
272 #[cfg(target_os = "windows")]
273 match &self.mapping {
274 WindowsMapping::Anonymous { view, .. } => view.len,
275 WindowsMapping::FileBacked {
276 leading,
277 view,
278 trailing,
279 ..
280 } => leading.size + view.len + trailing.size,
281 }
282 }
283
284 #[cfg(target_os = "windows")]
286 pub(crate) fn file_mapping_handle(&self) -> HANDLE {
287 match &self.mapping {
288 WindowsMapping::Anonymous { file_mapping, .. }
289 | WindowsMapping::FileBacked { file_mapping, .. } => file_mapping.0,
290 }
291 }
292}
293
294#[cfg(unix)]
296#[derive(Debug)]
297struct Mmap {
298 base: *mut c_void,
299 len: usize,
300}
301
302#[cfg(unix)]
303impl Drop for Mmap {
304 fn drop(&mut self) {
305 unsafe {
309 if libc::munmap(self.base, self.len) != 0 {
310 tracing::error!(
311 "Mmap::drop: munmap failed: {:?}",
312 std::io::Error::last_os_error()
313 );
314 }
315 }
316 }
317}
318
319#[cfg(target_os = "windows")]
321#[derive(Debug)]
322struct MappedView {
323 addr: *mut c_void,
324 len: usize,
325}
326
327#[cfg(target_os = "windows")]
328impl Drop for MappedView {
329 fn drop(&mut self) {
330 let view = MEMORY_MAPPED_VIEW_ADDRESS { Value: self.addr };
331 if let Err(e) = unsafe { UnmapViewOfFile(view) } {
340 tracing::error!(
341 "MappedView::drop(addr={:?}, len={}) UnmapViewOfFile failed: {:?}",
342 self.addr,
343 self.len,
344 e
345 );
346 }
347 }
348}
349
350#[cfg(target_os = "windows")]
352#[derive(Debug)]
353struct FileMapping(HANDLE);
354
355#[cfg(target_os = "windows")]
356impl Drop for FileMapping {
357 fn drop(&mut self) {
358 unsafe {
361 if let Err(e) = CloseHandle(self.0) {
362 tracing::error!(
363 "FileMapping::drop(handle={:?}) CloseHandle failed: {:?}",
364 self.0,
365 e
366 );
367 }
368 }
369 }
370}
371
372#[cfg(target_os = "windows")]
377#[derive(Debug)]
378pub(crate) struct Placeholder {
379 addr: *mut c_void,
380 size: usize,
381}
382
383#[cfg(target_os = "windows")]
384impl Placeholder {
385 fn reserve(size: usize) -> Result<Self> {
386 let addr = unsafe {
392 VirtualAlloc2(
393 None,
394 None,
395 size,
396 VIRTUAL_ALLOCATION_TYPE(MEM_RESERVE.0 | MEM_RESERVE_PLACEHOLDER.0),
397 PAGE_NOACCESS.0,
398 None,
399 )
400 };
401 if addr.is_null() {
402 log_then_return!(SharedMemoryError::MemoryAllocationFailed(
403 Error::last_os_error().raw_os_error()
404 ));
405 }
406 Ok(Placeholder { addr, size })
407 }
408
409 fn split_front(self, front_size: usize) -> Result<(Placeholder, Placeholder)> {
410 debug_assert!(front_size > 0 && front_size < self.size);
411 debug_assert!(front_size.is_multiple_of(page_size::get()));
412 if let Err(e) = unsafe {
418 VirtualFree(
419 self.addr,
420 front_size,
421 VIRTUAL_FREE_TYPE(MEM_RELEASE.0 | MEM_PRESERVE_PLACEHOLDER.0),
422 )
423 } {
424 log_then_return!(SharedMemoryError::WindowsAPIError(e.clone()));
426 }
427 let addr = self.addr;
428 let total = self.size;
429 std::mem::forget(self);
432 let front = Placeholder {
433 addr,
434 size: front_size,
435 };
436 let back = Placeholder {
437 addr: unsafe { (addr as *mut u8).add(front_size) as *mut c_void },
440 size: total - front_size,
441 };
442 Ok((front, back))
443 }
444
445 fn split_into_three(
446 self,
447 front_size: usize,
448 middle_size: usize,
449 ) -> Result<(Placeholder, Placeholder, Placeholder)> {
450 let (front, rest) = self.split_front(front_size)?;
451 let (middle, back) = rest.split_front(middle_size)?;
452 Ok((front, middle, back))
453 }
454
455 fn map_file_view(self, file_mapping: HANDLE) -> Result<MappedView> {
456 let mapped = unsafe {
464 MapViewOfFile3(
465 file_mapping,
466 None,
467 Some(self.addr),
468 0,
469 self.size,
470 MEM_REPLACE_PLACEHOLDER,
471 PAGE_READONLY.0,
472 None,
473 )
474 };
475 if mapped.Value.is_null() {
476 log_then_return!(SharedMemoryError::MemoryAllocationFailed(
478 Error::last_os_error().raw_os_error()
479 ));
480 }
481 let addr = self.addr;
482 let len = self.size;
483 std::mem::forget(self);
484 Ok(MappedView { addr, len })
485 }
486}
487
488#[cfg(target_os = "windows")]
489impl Drop for Placeholder {
490 fn drop(&mut self) {
491 if let Err(e) = unsafe { VirtualFree(self.addr, 0, VIRTUAL_FREE_TYPE(MEM_RELEASE.0)) } {
495 tracing::error!(
496 "Placeholder::drop(addr={:?}, size={}) VirtualFree failed: {:?}",
497 self.addr,
498 self.size,
499 e
500 );
501 }
502 }
503}
504
505pub trait SharedMemory {
510 fn region(&self) -> &HostMapping;
512
513 fn base_addr(&self) -> usize {
518 self.region().ptr() as usize + page_size::get()
519 }
520
521 fn base_ptr(&self) -> *mut u8 {
526 self.region().ptr().wrapping_add(page_size::get())
527 }
528
529 fn mem_size(&self) -> usize {
533 self.region().size() - 2 * page_size::get()
534 }
535
536 fn raw_ptr(&self) -> *mut u8 {
539 self.region().ptr()
540 }
541
542 fn raw_mem_size(&self) -> usize {
545 self.region().size()
546 }
547
548 fn host_region_base(&self) -> <HostGuestMemoryRegion as MemoryRegionKind>::HostBaseType {
555 #[cfg(not(windows))]
556 {
557 self.base_addr()
558 }
559 #[cfg(windows)]
560 {
561 super::memory_region::HostRegionBase {
562 from_handle: self.region().file_mapping_handle().into(),
563 handle_base: self.region().ptr() as usize,
564 handle_size: self.region().size(),
565 offset: page_size::get(),
566 }
567 }
568 }
569
570 fn host_region_end(&self) -> <HostGuestMemoryRegion as MemoryRegionKind>::HostBaseType {
572 <HostGuestMemoryRegion as MemoryRegionKind>::add(self.host_region_base(), self.mem_size())
573 }
574
575 fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
581 &mut self,
582 f: F,
583 ) -> Result<T>;
584
585 fn with_contents<T, F: FnOnce(&[u8]) -> T>(&mut self, f: F) -> Result<T> {
591 self.with_exclusivity(|m| f(m.as_slice()))
592 }
593}
594
595fn mapping_at(
596 s: &impl SharedMemory,
597 gpa: u64,
598 size: usize,
599 region_type: MemoryRegionType,
600 flags: MemoryRegionFlags,
601) -> MemoryRegion {
602 let guest_base = gpa as usize;
603
604 MemoryRegion {
605 guest_region: guest_base..(guest_base + size),
606 host_region: s.host_region_base()
607 ..<HostGuestMemoryRegion as MemoryRegionKind>::add(s.host_region_base(), size),
608 region_type,
609 flags,
610 }
611}
612
613#[derive(Debug)]
619pub struct ExclusiveSharedMemory {
620 region: Arc<HostMapping>,
621}
622unsafe impl Send for ExclusiveSharedMemory {}
623
624impl ExclusiveSharedMemory {
625 fn total_size(min_size_bytes: usize) -> Result<usize> {
628 if min_size_bytes > 0 &&
629 let Some(total_size) = min_size_bytes.checked_add(2 * page_size::get()) &&
631 total_size % page_size::get() == 0 &&
632 total_size <= isize::MAX as usize
636 {
637 Ok(total_size)
638 } else {
639 Err(SharedMemoryError::MemoryRequest(
640 min_size_bytes,
641 2,
642 isize::MAX as usize - 2 * page_size::get(),
643 page_size::get(),
644 ))
645 }
646 }
647
648 #[cfg(unix)]
653 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
654 pub fn new(min_size_bytes: usize) -> Result<Self> {
655 use libc::{
656 MAP_ANONYMOUS, MAP_FAILED, MAP_PRIVATE, PROT_READ, PROT_WRITE, c_int, mmap, off_t,
657 size_t,
658 };
659
660 let total_size = Self::total_size(min_size_bytes)?;
661
662 #[cfg(not(miri))]
663 use libc::{MAP_NORESERVE, PROT_NONE, mprotect};
664
665 #[cfg(not(miri))]
667 let flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE;
668 #[cfg(miri)]
669 let flags = MAP_ANONYMOUS | MAP_PRIVATE;
670
671 let addr = unsafe {
672 mmap(
673 null_mut(),
674 total_size as size_t,
675 PROT_READ | PROT_WRITE,
676 flags,
677 -1 as c_int,
678 0 as off_t,
679 )
680 };
681 if addr == MAP_FAILED {
682 log_then_return!(SharedMemoryError::MmapFailed(
683 Error::last_os_error().raw_os_error()
684 ));
685 }
686 let mmap = Mmap {
687 base: addr,
688 len: total_size,
689 };
690
691 #[cfg(not(miri))]
693 {
694 let res = unsafe { mprotect(mmap.base, page_size::get(), PROT_NONE) };
695 if res != 0 {
696 return Err(SharedMemoryError::MprotectFailed(
697 Error::last_os_error().raw_os_error(),
698 ));
699 }
700 let res = unsafe {
701 mprotect(
702 (mmap.base as *const u8).add(total_size - page_size::get()) as *mut c_void,
703 page_size::get(),
704 PROT_NONE,
705 )
706 };
707 if res != 0 {
708 return Err(SharedMemoryError::MprotectFailed(
709 Error::last_os_error().raw_os_error(),
710 ));
711 }
712 }
713
714 Ok(Self {
715 #[allow(clippy::arc_with_non_send_sync)]
724 region: Arc::new(HostMapping { mmap }),
725 })
726 }
727
728 #[cfg(target_os = "windows")]
733 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
734 pub fn new(min_size_bytes: usize) -> Result<Self> {
735 let total_size = Self::total_size(min_size_bytes)?;
736
737 let mut dwmaximumsizehigh = 0;
738 let mut dwmaximumsizelow = 0;
739
740 if std::mem::size_of::<usize>() == 8 {
741 dwmaximumsizehigh = (total_size >> 32) as u32;
742 dwmaximumsizelow = (total_size & 0xFFFFFFFF) as u32;
743 }
744
745 let flags = PAGE_READWRITE;
749
750 let handle = unsafe {
751 CreateFileMappingA(
752 INVALID_HANDLE_VALUE,
753 None,
754 flags,
755 dwmaximumsizehigh,
756 dwmaximumsizelow,
757 PCSTR::null(),
758 )?
759 };
760
761 if handle.is_invalid() {
762 log_then_return!(SharedMemoryError::MemoryAllocationFailed(
763 Error::last_os_error().raw_os_error()
764 ));
765 }
766 let file_mapping = FileMapping(handle);
767
768 let file_map = FILE_MAP_ALL_ACCESS;
769 let addr = unsafe { MapViewOfFile(file_mapping.0, file_map, 0, 0, 0) };
770
771 if addr.Value.is_null() {
772 log_then_return!(SharedMemoryError::MemoryAllocationFailed(
773 Error::last_os_error().raw_os_error()
774 ));
775 }
776 let view = MappedView {
777 addr: addr.Value,
778 len: total_size,
779 };
780
781 let mut unused_out_old_prot_flags = PAGE_PROTECTION_FLAGS(0);
784
785 let first_guard_page_start = view.addr;
788 if let Err(e) = unsafe {
789 VirtualProtect(
790 first_guard_page_start,
791 page_size::get(),
792 PAGE_NOACCESS,
793 &mut unused_out_old_prot_flags,
794 )
795 } {
796 log_then_return!(SharedMemoryError::WindowsAPIError(e.clone()));
797 }
798
799 let last_guard_page_start = unsafe { view.addr.add(total_size - page_size::get()) };
800 if let Err(e) = unsafe {
801 VirtualProtect(
802 last_guard_page_start,
803 page_size::get(),
804 PAGE_NOACCESS,
805 &mut unused_out_old_prot_flags,
806 )
807 } {
808 log_then_return!(SharedMemoryError::WindowsAPIError(e.clone()));
809 }
810
811 Ok(Self {
812 #[allow(clippy::arc_with_non_send_sync)]
821 region: Arc::new(HostMapping {
822 mapping: WindowsMapping::Anonymous { view, file_mapping },
823 }),
824 })
825 }
826
827 pub(super) fn as_mut_slice(&mut self) -> &mut [u8] {
863 unsafe { std::slice::from_raw_parts_mut(self.base_ptr(), self.mem_size()) }
864 }
865
866 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
879 pub fn as_slice<'a>(&'a self) -> &'a [u8] {
880 unsafe { std::slice::from_raw_parts(self.base_ptr(), self.mem_size()) }
881 }
882
883 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
885 #[cfg(test)]
886 pub(crate) fn copy_all_to_vec(&self) -> Result<Vec<u8>> {
887 let data = self.as_slice();
888 Ok(data.to_vec())
889 }
890
891 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
893 pub fn copy_from_slice(&mut self, src: &[u8], offset: usize) -> Result<()> {
894 let data = self.as_mut_slice();
895 bounds_check!(offset, src.len(), data.len());
896 data[offset..offset + src.len()].copy_from_slice(src);
897 Ok(())
898 }
899
900 generate_reader!(read_u8, u8);
901 generate_reader!(read_i8, i8);
902 generate_reader!(read_u16, u16);
903 generate_reader!(read_i16, i16);
904 generate_reader!(read_u32, u32);
905 generate_reader!(read_i32, i32);
906 generate_reader!(read_u64, u64);
907 generate_reader!(read_i64, i64);
908 generate_reader!(read_usize, usize);
909 generate_reader!(read_isize, isize);
910
911 generate_writer!(write_u8, u8);
912 generate_writer!(write_i8, i8);
913 generate_writer!(write_u16, u16);
914 generate_writer!(write_i16, i16);
915 generate_writer!(write_u32, u32);
916 generate_writer!(write_i32, i32);
917 generate_writer!(write_u64, u64);
918 generate_writer!(write_i64, i64);
919 generate_writer!(write_usize, usize);
920 generate_writer!(write_isize, isize);
921
922 pub fn build(self) -> (HostSharedMemory, GuestSharedMemory) {
929 let lock = Arc::new(RwLock::new(()));
930 let hshm = HostSharedMemory {
931 region: self.region.clone(),
932 lock: lock.clone(),
933 };
934 (
935 hshm,
936 GuestSharedMemory {
937 region: self.region.clone(),
938 lock,
939 },
940 )
941 }
942
943 #[cfg(target_os = "windows")]
945 pub fn get_mmap_file_handle(&self) -> HANDLE {
946 self.region.file_mapping_handle()
947 }
948}
949
950impl SharedMemory for ExclusiveSharedMemory {
951 fn region(&self) -> &HostMapping {
952 &self.region
953 }
954 fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
955 &mut self,
956 f: F,
957 ) -> Result<T> {
958 Ok(f(self))
959 }
960}
961
962#[derive(Debug)]
971pub struct GuestSharedMemory {
972 region: Arc<HostMapping>,
973 pub lock: Arc<RwLock<()>>,
984}
985unsafe impl Send for GuestSharedMemory {}
986
987impl GuestSharedMemory {
988 pub(crate) fn mapping_at(
991 &self,
992 guest_base: u64,
993 region_type: MemoryRegionType,
994 ) -> MemoryRegion {
995 let flags = match region_type {
996 MemoryRegionType::Scratch => {
997 MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE
998 }
999 #[cfg(unshared_snapshot_mem)]
1000 MemoryRegionType::Snapshot => {
1001 MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE
1002 }
1003 #[allow(clippy::panic)]
1004 _ => panic!(
1013 "GuestSharedMemory::mapping_at should only be used for Scratch or Snapshot regions"
1014 ),
1015 };
1016 mapping_at(self, guest_base, self.mem_size(), region_type, flags)
1017 }
1018}
1019
1020impl SharedMemory for GuestSharedMemory {
1021 fn region(&self) -> &HostMapping {
1022 &self.region
1023 }
1024 fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
1025 &mut self,
1026 f: F,
1027 ) -> Result<T> {
1028 let guard = self.lock.try_write()?;
1029 let mut excl = ExclusiveSharedMemory {
1030 region: self.region.clone(),
1031 };
1032 let ret = f(&mut excl);
1033 drop(excl);
1034 drop(guard);
1035 Ok(ret)
1036 }
1037}
1038
1039#[derive(Clone, Debug)]
1194pub struct HostSharedMemory {
1195 region: Arc<HostMapping>,
1196 lock: Arc<RwLock<()>>,
1197}
1198unsafe impl Send for HostSharedMemory {}
1199
1200impl HostSharedMemory {
1201 pub fn read<T: Pod>(&self, offset: usize) -> Result<T> {
1204 bounds_check!(offset, std::mem::size_of::<T>(), self.mem_size());
1205 let mut ret = T::zeroed();
1206 self.copy_to_slice(bytemuck::bytes_of_mut(&mut ret), offset)?;
1207 Ok(ret)
1208 }
1209
1210 pub fn write<T: Pod>(&self, offset: usize, data: T) -> Result<()> {
1213 bounds_check!(offset, std::mem::size_of::<T>(), self.mem_size());
1214 self.copy_from_slice(bytemuck::bytes_of(&data), offset)
1215 }
1216
1217 pub fn copy_to_slice(&self, slice: &mut [u8], offset: usize) -> Result<()> {
1220 bounds_check!(offset, slice.len(), self.mem_size());
1221 let base = self.base_ptr().wrapping_add(offset);
1222 let guard = self.lock.try_read()?;
1223
1224 const CHUNK: usize = size_of::<u128>();
1225 let len = slice.len();
1226 let mut i = 0;
1227
1228 let align_offset = base.align_offset(align_of::<u128>());
1233 let head_len = align_offset.min(len);
1234 while i < head_len {
1235 unsafe {
1236 slice[i] = base.add(i).read_volatile();
1237 }
1238 i += 1;
1239 }
1240
1241 let dst = slice.as_mut_ptr();
1245 while i + CHUNK <= len {
1246 unsafe {
1247 let value = (base.add(i) as *const u128).read_volatile();
1248 std::ptr::write_unaligned(dst.add(i) as *mut u128, value);
1249 }
1250 i += CHUNK;
1251 }
1252
1253 while i < len {
1255 unsafe {
1256 slice[i] = base.add(i).read_volatile();
1257 }
1258 i += 1;
1259 }
1260
1261 drop(guard);
1262 Ok(())
1263 }
1264
1265 pub fn copy_from_slice(&self, slice: &[u8], offset: usize) -> Result<()> {
1268 bounds_check!(offset, slice.len(), self.mem_size());
1269 let base = self.base_ptr().wrapping_add(offset);
1270 let guard = self.lock.try_read()?;
1271
1272 const CHUNK: usize = size_of::<u128>();
1273 let len = slice.len();
1274 let mut i = 0;
1275
1276 let align_offset = base.align_offset(align_of::<u128>());
1281 let head_len = align_offset.min(len);
1282 while i < head_len {
1283 unsafe {
1284 base.add(i).write_volatile(slice[i]);
1285 }
1286 i += 1;
1287 }
1288
1289 let src = slice.as_ptr();
1293 while i + CHUNK <= len {
1294 unsafe {
1295 let value = std::ptr::read_unaligned(src.add(i) as *const u128);
1296 (base.add(i) as *mut u128).write_volatile(value);
1297 }
1298 i += CHUNK;
1299 }
1300
1301 while i < len {
1303 unsafe {
1304 base.add(i).write_volatile(slice[i]);
1305 }
1306 i += 1;
1307 }
1308
1309 drop(guard);
1310 Ok(())
1311 }
1312
1313 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
1315 pub fn fill(&mut self, value: u8, offset: usize, len: usize) -> Result<()> {
1316 bounds_check!(offset, len, self.mem_size());
1317 let base = self.base_ptr().wrapping_add(offset);
1318 let guard = self.lock.try_read()?;
1319
1320 const CHUNK: usize = size_of::<u128>();
1321 let value_u128 = u128::from_ne_bytes([value; CHUNK]);
1322 let mut i = 0;
1323
1324 let align_offset = base.align_offset(align_of::<u128>());
1329 let head_len = align_offset.min(len);
1330 while i < head_len {
1331 unsafe {
1332 base.add(i).write_volatile(value);
1333 }
1334 i += 1;
1335 }
1336
1337 while i + CHUNK <= len {
1340 unsafe {
1341 (base.add(i) as *mut u128).write_volatile(value_u128);
1342 }
1343 i += CHUNK;
1344 }
1345
1346 while i < len {
1348 unsafe {
1349 base.add(i).write_volatile(value);
1350 }
1351 i += 1;
1352 }
1353
1354 drop(guard);
1355 Ok(())
1356 }
1357
1358 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
1361 pub fn push_buffer(
1362 &mut self,
1363 buffer_start_offset: usize,
1364 buffer_size: usize,
1365 data: &[u8],
1366 ) -> Result<()> {
1367 let stack_pointer_rel = self.read::<u64>(buffer_start_offset)? as usize;
1368
1369 if stack_pointer_rel > buffer_size || stack_pointer_rel < 8 {
1370 Err(StackError::SpOob(
1371 StackOp::Push,
1372 stack_pointer_rel,
1373 buffer_size,
1374 ))?;
1375 }
1376
1377 let size_required = data.len() + 8;
1378 let size_available = buffer_size - stack_pointer_rel;
1379
1380 if size_required > size_available {
1381 Err(StackError::BufferFullError(size_required, size_available))?;
1382 }
1383
1384 let stack_pointer_abs = stack_pointer_rel + buffer_start_offset;
1386
1387 self.copy_from_slice(data, stack_pointer_abs)?;
1389
1390 self.write::<u64>(stack_pointer_abs + data.len(), stack_pointer_rel as u64)?;
1393
1394 self.write::<u64>(
1396 buffer_start_offset,
1397 (stack_pointer_rel + data.len() + 8) as u64,
1398 )?;
1399 Ok(())
1400 }
1401
1402 pub fn try_pop_buffer_into<T>(
1406 &mut self,
1407 buffer_start_offset: usize,
1408 buffer_size: usize,
1409 ) -> Result<T>
1410 where
1411 T: for<'b> TryFrom<&'b [u8]>,
1412 {
1413 let stack_pointer_rel = self.read::<u64>(buffer_start_offset)? as usize;
1415
1416 if stack_pointer_rel > buffer_size || stack_pointer_rel < 16 {
1417 Err(StackError::SpOob(
1418 StackOp::Pop,
1419 stack_pointer_rel,
1420 buffer_size,
1421 ))?;
1422 }
1423
1424 let last_element_offset_abs = stack_pointer_rel + buffer_start_offset;
1426
1427 let last_element_offset_rel: usize =
1429 self.read::<u64>(last_element_offset_abs - 8)? as usize;
1430
1431 if last_element_offset_rel > stack_pointer_rel.saturating_sub(16)
1435 || last_element_offset_rel < 8
1436 {
1437 Err(StackError::CorruptBackPointer(
1438 last_element_offset_rel,
1439 stack_pointer_rel.saturating_sub(16),
1440 ))?;
1441 }
1442
1443 let last_element_offset_abs = last_element_offset_rel + buffer_start_offset;
1445
1446 let max_element_size = stack_pointer_rel - last_element_offset_rel - 8;
1448
1449 let fb_buffer_size = {
1451 let raw_prefix = self.read::<u32>(last_element_offset_abs)?;
1452 let total = raw_prefix
1455 .checked_add(4)
1456 .ok_or(StackError::OverflowingPrefix(raw_prefix))?;
1457 usize::try_from(total).map_err(StackError::PrefixTooLarge)?
1458 };
1459
1460 if fb_buffer_size > max_element_size {
1461 Err(StackError::CorruptPrefix(fb_buffer_size, max_element_size))?;
1462 }
1463
1464 let mut result_buffer = vec![0; fb_buffer_size];
1465
1466 self.copy_to_slice(&mut result_buffer, last_element_offset_abs)?;
1467 let to_return = T::try_from(result_buffer.as_slice())
1468 .map_err(|_| StackError::ConvertError(type_name::<T>().to_string()))?;
1469
1470 self.write::<u64>(buffer_start_offset, last_element_offset_rel as u64)?;
1472
1473 let num_bytes_to_zero = stack_pointer_rel - last_element_offset_rel;
1475 self.fill(0, last_element_offset_abs, num_bytes_to_zero)?;
1476
1477 Ok(to_return)
1478 }
1479}
1480
1481impl HostSharedMemory {
1482 pub(crate) fn zero_or_replace(&mut self) -> Result<Option<GuestSharedMemory>> {
1499 #[cfg(target_os = "windows")]
1500 {
1501 let new_mem = ExclusiveSharedMemory::new(self.mem_size())?;
1502 let (hscratch, gscratch) = new_mem.build();
1503 *self = hscratch;
1504 Ok(Some(gscratch))
1505 }
1506 #[cfg(not(target_os = "windows"))]
1507 {
1508 self.with_exclusivity(|e| {
1509 #[allow(unused_mut)]
1510 let mut do_copy = true;
1511 #[cfg(all(feature = "kvm", not(any(feature = "mshv3"))))]
1514 unsafe {
1515 let ret = libc::madvise(
1516 e.region.ptr() as *mut libc::c_void,
1517 e.region.size(),
1518 libc::MADV_DONTNEED,
1519 );
1520 if ret == 0 {
1521 do_copy = false;
1522 }
1523 }
1524 if do_copy {
1525 e.as_mut_slice().fill(0);
1526 }
1527 })?;
1528 Ok(None)
1529 }
1530 }
1531}
1532
1533impl SharedMemory for HostSharedMemory {
1534 fn region(&self) -> &HostMapping {
1535 &self.region
1536 }
1537 fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
1538 &mut self,
1539 f: F,
1540 ) -> Result<T> {
1541 let guard = self.lock.try_write()?;
1542 let mut excl = ExclusiveSharedMemory {
1543 region: self.region.clone(),
1544 };
1545 let ret = f(&mut excl);
1546 drop(excl);
1547 drop(guard);
1548 Ok(ret)
1549 }
1550}
1551
1552#[derive(Clone, Debug)]
1557pub struct ReadonlySharedMemory {
1558 region: Arc<HostMapping>,
1559 #[cfg_attr(unshared_snapshot_mem, allow(dead_code))]
1564 guest_mapped_size: usize,
1565}
1566unsafe impl Send for ReadonlySharedMemory {}
1577unsafe impl Sync for ReadonlySharedMemory {}
1578
1579impl ReadonlySharedMemory {
1580 pub(crate) fn from_bytes(contents: &[u8], guest_mapped_size: usize) -> Result<Self> {
1581 if guest_mapped_size == 0
1582 || guest_mapped_size > contents.len()
1583 || !guest_mapped_size.is_multiple_of(page_size::get())
1584 {
1585 return Err(SharedMemoryError::MemoryRequest(
1586 guest_mapped_size,
1587 0,
1588 contents.len(),
1589 page_size::get(),
1590 ));
1591 }
1592 let mut anon =
1593 ExclusiveSharedMemory::new(contents.len().next_multiple_of(page_size::get()))?;
1594 anon.copy_from_slice(contents, 0)?;
1595 Ok(ReadonlySharedMemory {
1596 region: anon.region,
1597 guest_mapped_size,
1598 })
1599 }
1600
1601 #[cfg(not(unshared_snapshot_mem))]
1603 pub(crate) fn guest_mapped_size(&self) -> usize {
1604 self.guest_mapped_size
1605 }
1606
1607 pub(crate) fn from_file(file: &std::fs::File, guest_mapped_size: usize) -> Result<Self> {
1613 let len: usize = file
1614 .metadata()
1615 .map_err(SharedMemoryError::FileMetadata)?
1616 .len()
1617 .try_into()
1618 .map_err(SharedMemoryError::FileTooLarge)?;
1619
1620 if len == 0 || !len.is_multiple_of(page_size::get()) {
1621 return Err(SharedMemoryError::MemoryRequest(
1622 len,
1623 0,
1624 usize::MAX,
1625 page_size::get(),
1626 ));
1627 }
1628
1629 if guest_mapped_size == 0
1630 || guest_mapped_size > len
1631 || !guest_mapped_size.is_multiple_of(page_size::get())
1632 {
1633 return Err(SharedMemoryError::MemoryRequest(
1634 guest_mapped_size,
1635 0,
1636 len,
1637 page_size::get(),
1638 ));
1639 }
1640
1641 let region = Self::map_file(file, len)?;
1642 Ok(ReadonlySharedMemory {
1643 region,
1644 guest_mapped_size,
1645 })
1646 }
1647
1648 #[cfg(unix)]
1652 fn map_file(file: &std::fs::File, len: usize) -> Result<Arc<HostMapping>> {
1653 use std::os::unix::io::AsRawFd;
1654
1655 #[cfg(mshv3)]
1656 use libc::PROT_WRITE;
1657 use libc::{
1658 MAP_ANONYMOUS, MAP_FAILED, MAP_FIXED, MAP_NORESERVE, MAP_PRIVATE, PROT_NONE, PROT_READ,
1659 mmap, off_t, size_t,
1660 };
1661
1662 let total_size =
1663 len.checked_add(2 * page_size::get())
1664 .ok_or(SharedMemoryError::MemoryRequest(
1665 len,
1666 0,
1667 usize::MAX - 2 * page_size::get(),
1668 1,
1669 ))?;
1670
1671 let fd = file.as_raw_fd();
1672
1673 let base = unsafe {
1679 mmap(
1680 null_mut(),
1681 total_size as size_t,
1682 PROT_NONE,
1683 MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE,
1684 -1,
1685 0 as off_t,
1686 )
1687 };
1688 if base == MAP_FAILED {
1689 return Err(SharedMemoryError::MmapFailed(
1690 std::io::Error::last_os_error().raw_os_error(),
1691 ));
1692 }
1693 let reservation = Mmap {
1694 base,
1695 len: total_size,
1696 };
1697
1698 #[cfg(mshv3)]
1707 let file_prot = PROT_READ | PROT_WRITE;
1708 #[cfg(not(mshv3))]
1709 let file_prot = PROT_READ;
1710 let usable_ptr = unsafe { (base as *mut u8).add(page_size::get()) };
1713 let mapped = unsafe {
1719 mmap(
1720 usable_ptr as *mut c_void,
1721 len as size_t,
1722 file_prot,
1723 MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE,
1724 fd,
1725 0 as off_t,
1726 )
1727 };
1728 if mapped == MAP_FAILED {
1729 return Err(SharedMemoryError::MmapFailed(
1730 std::io::Error::last_os_error().raw_os_error(),
1731 ));
1732 }
1733
1734 #[allow(clippy::arc_with_non_send_sync)]
1738 Ok(Arc::new(HostMapping { mmap: reservation }))
1739 }
1740
1741 #[cfg(target_os = "windows")]
1745 fn map_file(file: &std::fs::File, len: usize) -> Result<Arc<HostMapping>> {
1746 use std::os::windows::io::AsRawHandle;
1747
1748 let total_size =
1749 len.checked_add(2 * page_size::get())
1750 .ok_or(SharedMemoryError::MemoryRequest(
1751 len,
1752 0,
1753 usize::MAX - 2 * page_size::get(),
1754 1,
1755 ))?;
1756
1757 let file_handle = HANDLE(file.as_raw_handle());
1758
1759 let whole = Placeholder::reserve(total_size)?;
1762
1763 let (leading, middle, trailing) = whole.split_into_three(page_size::get(), len)?;
1767
1768 let raw_handle =
1773 unsafe { CreateFileMappingA(file_handle, None, PAGE_READONLY, 0, 0, PCSTR::null()) }?;
1774 if raw_handle.is_invalid() {
1775 log_then_return!(SharedMemoryError::MemoryAllocationFailed(
1776 Error::last_os_error().raw_os_error()
1777 ));
1778 }
1779 let file_mapping = FileMapping(raw_handle);
1780
1781 let view = middle.map_file_view(raw_handle)?;
1784
1785 #[allow(clippy::arc_with_non_send_sync)]
1786 Ok(Arc::new(HostMapping {
1787 mapping: WindowsMapping::FileBacked {
1788 leading,
1789 view,
1790 trailing,
1791 file_mapping,
1792 },
1793 }))
1794 }
1795
1796 pub(crate) fn as_slice(&self) -> &[u8] {
1797 unsafe { std::slice::from_raw_parts(self.base_ptr(), self.mem_size()) }
1798 }
1799
1800 #[cfg(unshared_snapshot_mem)]
1801 pub(crate) fn copy_to_writable(&self) -> Result<ExclusiveSharedMemory> {
1802 let mut writable = ExclusiveSharedMemory::new(self.mem_size())?;
1803 writable.copy_from_slice(self.as_slice(), 0)?;
1804 Ok(writable)
1805 }
1806
1807 #[cfg(not(unshared_snapshot_mem))]
1808 pub(crate) fn build(self) -> (Self, Self) {
1809 (self.clone(), self)
1810 }
1811
1812 #[cfg(not(unshared_snapshot_mem))]
1813 pub(crate) fn mapping_at(
1814 &self,
1815 guest_base: u64,
1816 region_type: MemoryRegionType,
1817 ) -> MemoryRegion {
1818 #[allow(clippy::panic)]
1819 if region_type != MemoryRegionType::Snapshot {
1823 panic!("ReadonlySharedMemory::mapping_at should only be used for Snapshot regions");
1824 }
1825 mapping_at(
1826 self,
1827 guest_base,
1828 self.guest_mapped_size(),
1829 region_type,
1830 MemoryRegionFlags::READ | MemoryRegionFlags::EXECUTE,
1831 )
1832 }
1833}
1834
1835impl SharedMemory for ReadonlySharedMemory {
1836 fn region(&self) -> &HostMapping {
1837 &self.region
1838 }
1839 #[cfg(windows)]
1849 fn host_region_base(&self) -> <HostGuestMemoryRegion as MemoryRegionKind>::HostBaseType {
1850 match &self.region().mapping {
1851 WindowsMapping::Anonymous { .. } => super::memory_region::HostRegionBase {
1852 from_handle: self.region().file_mapping_handle().into(),
1853 handle_base: self.region().ptr() as usize,
1854 handle_size: self.region().size(),
1855 offset: page_size::get(),
1856 },
1857 WindowsMapping::FileBacked { .. } => super::memory_region::HostRegionBase {
1858 from_handle: self.region().file_mapping_handle().into(),
1859 handle_base: self.base_ptr() as usize,
1860 handle_size: self.mem_size(),
1861 offset: 0,
1862 },
1863 }
1864 }
1865 fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
1868 &mut self,
1869 _: F,
1870 ) -> Result<T> {
1871 Err(SharedMemoryError::ReadonlySharedMemoryExclusiveRequest)
1872 }
1873 fn with_contents<T, F: FnOnce(&[u8]) -> T>(&mut self, f: F) -> Result<T> {
1875 Ok(f(self.as_slice()))
1876 }
1877}
1878
1879impl<S: SharedMemory> PartialEq<S> for ReadonlySharedMemory {
1880 fn eq(&self, other: &S) -> bool {
1881 self.raw_ptr() == other.raw_ptr()
1882 }
1883}
1884
1885#[cfg(test)]
1886mod tests {
1887 #[cfg(not(miri))]
1888 use proptest::prelude::*;
1889
1890 #[cfg(not(miri))]
1891 use super::HostSharedMemory;
1892 use super::{ExclusiveSharedMemory, Result, SharedMemory};
1893 #[cfg(not(miri))]
1894 use crate::mem::shared_mem_tests::read_write_test_suite;
1895
1896 #[test]
1897 fn fill() {
1898 let mem_size: usize = page_size::get();
1899 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
1900 let (mut hshm, _) = eshm.build();
1901
1902 hshm.fill(1, 0, 1024).unwrap();
1903 hshm.fill(2, 1024, 1024).unwrap();
1904 hshm.fill(3, 2048, 1024).unwrap();
1905 hshm.fill(4, 3072, 1024).unwrap();
1906
1907 let vec = hshm
1908 .with_exclusivity(|e| e.copy_all_to_vec().unwrap())
1909 .unwrap();
1910
1911 assert!(vec[0..1024].iter().all(|&x| x == 1));
1912 assert!(vec[1024..2048].iter().all(|&x| x == 2));
1913 assert!(vec[2048..3072].iter().all(|&x| x == 3));
1914 assert!(vec[3072..4096].iter().all(|&x| x == 4));
1915
1916 hshm.fill(5, 0, mem_size).unwrap();
1917
1918 let vec2 = hshm
1919 .with_exclusivity(|e| e.copy_all_to_vec().unwrap())
1920 .unwrap();
1921 assert!(vec2.iter().all(|&x| x == 5));
1922
1923 assert!(hshm.fill(0, 0, mem_size + 1).is_err());
1924 assert!(hshm.fill(0, mem_size, 1).is_err());
1925 }
1926
1927 #[test]
1930 fn bounds_check_overflow() {
1931 let mem_size: usize = page_size::get();
1932 let mut eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
1933
1934 assert!(eshm.read_i32(usize::MAX).is_err());
1936 assert!(eshm.write_i32(usize::MAX, 0).is_err());
1937 assert!(eshm.copy_from_slice(&[0u8; 1], usize::MAX).is_err());
1938
1939 let (mut hshm, _) = eshm.build();
1941
1942 assert!(hshm.read::<u8>(usize::MAX).is_err());
1943 assert!(hshm.read::<u64>(usize::MAX - 3).is_err());
1944 assert!(hshm.write::<u8>(usize::MAX, 0).is_err());
1945 assert!(hshm.write::<u64>(usize::MAX - 3, 0).is_err());
1946
1947 let mut buf = [0u8; 1];
1948 assert!(hshm.copy_to_slice(&mut buf, usize::MAX).is_err());
1949 assert!(hshm.copy_from_slice(&[0u8; 1], usize::MAX).is_err());
1950
1951 assert!(hshm.fill(0, usize::MAX, 1).is_err());
1952 assert!(hshm.fill(0, 1, usize::MAX).is_err());
1953 }
1954
1955 #[test]
1956 fn copy_into_from() -> Result<()> {
1957 let mem_size: usize = page_size::get();
1958 let vec_len = 10;
1959 let eshm = ExclusiveSharedMemory::new(mem_size)?;
1960 let (hshm, _) = eshm.build();
1961 let vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
1962 hshm.copy_from_slice(&vec, 0)?;
1964
1965 let mut vec2 = vec![0; vec_len];
1966 hshm.copy_to_slice(vec2.as_mut_slice(), 0)?;
1968 assert_eq!(vec, vec2);
1969
1970 let offset = mem_size - vec.len();
1971 hshm.copy_from_slice(&vec, offset)?;
1973
1974 let mut vec3 = vec![0; vec_len];
1975 hshm.copy_to_slice(&mut vec3, offset)?;
1977 assert_eq!(vec, vec3);
1978
1979 let offset = mem_size / 2;
1980 hshm.copy_from_slice(&vec, offset)?;
1982
1983 let mut vec4 = vec![0; vec_len];
1984 hshm.copy_to_slice(&mut vec4, offset)?;
1986 assert_eq!(vec, vec4);
1987
1988 let mut vec5 = vec![0; vec_len];
1990 assert!(hshm.copy_to_slice(&mut vec5, mem_size).is_err());
1991
1992 assert!(hshm.copy_from_slice(&vec5, mem_size).is_err());
1994
1995 let mut vec6 = vec![0; vec_len];
1997 assert!(hshm.copy_to_slice(&mut vec6, mem_size * 2).is_err());
1998
1999 assert!(hshm.copy_from_slice(&vec6, mem_size * 2).is_err());
2001
2002 let mut vec7 = vec![0; mem_size * 2];
2004 assert!(hshm.copy_to_slice(&mut vec7, 0).is_err());
2005
2006 assert!(hshm.copy_from_slice(&vec7, 0).is_err());
2008
2009 Ok(())
2010 }
2011
2012 #[cfg(not(miri))]
2014 proptest! {
2015 #[test]
2016 fn read_write_i32(val in -0x1000_i32..0x1000_i32) {
2017 read_write_test_suite(
2018 val,
2019 ExclusiveSharedMemory::new,
2020 Box::new(ExclusiveSharedMemory::read_i32),
2021 Box::new(ExclusiveSharedMemory::write_i32),
2022 )
2023 .unwrap();
2024 read_write_test_suite(
2025 val,
2026 |s| {
2027 let e = ExclusiveSharedMemory::new(s)?;
2028 let (h, _) = e.build();
2029 Ok(h)
2030 },
2031 Box::new(HostSharedMemory::read::<i32>),
2032 Box::new(|h, o, v| h.write::<i32>(o, v)),
2033 )
2034 .unwrap();
2035 }
2036 }
2037
2038 #[test]
2039 fn alloc_fail() {
2040 let gm = ExclusiveSharedMemory::new(0);
2041 assert!(gm.is_err());
2042 let gm = ExclusiveSharedMemory::new(usize::MAX);
2043 assert!(gm.is_err());
2044 }
2045
2046 #[test]
2047 fn clone() {
2048 let eshm = ExclusiveSharedMemory::new(page_size::get()).unwrap();
2049 let (hshm1, _) = eshm.build();
2050 let hshm2 = hshm1.clone();
2051
2052 assert_eq!(hshm1.mem_size(), hshm2.mem_size());
2055 assert_eq!(hshm1.base_addr(), hshm2.base_addr());
2056
2057 hshm1.copy_from_slice(b"a", 0).unwrap();
2060 hshm2.copy_from_slice(b"b", 1).unwrap();
2061
2062 for (raw_offset, expected) in &[(0, b'a'), (1, b'b')] {
2065 assert_eq!(hshm1.read::<u8>(*raw_offset).unwrap(), *expected);
2066 assert_eq!(hshm2.read::<u8>(*raw_offset).unwrap(), *expected);
2067 }
2068
2069 drop(hshm1);
2072
2073 for (raw_offset, expected) in &[(0, b'a'), (1, b'b')] {
2075 assert_eq!(hshm2.read::<u8>(*raw_offset).unwrap(), *expected);
2076 }
2077 hshm2.copy_from_slice(b"c", 2).unwrap();
2078 assert_eq!(hshm2.read::<u8>(2).unwrap(), b'c');
2079 drop(hshm2);
2080 }
2081
2082 #[test]
2083 fn copy_all_to_vec() {
2084 let mut data = vec![b'a', b'b', b'c'];
2085 data.resize(page_size::get(), 0);
2086 let mut eshm = ExclusiveSharedMemory::new(data.len()).unwrap();
2087 eshm.copy_from_slice(data.as_slice(), 0).unwrap();
2088 let ret_vec = eshm.copy_all_to_vec().unwrap();
2089 assert_eq!(data, ret_vec);
2090 }
2091
2092 #[test]
2095 #[cfg(all(target_os = "linux", not(miri)))]
2096 fn test_drop() {
2097 use proc_maps::get_process_maps;
2098
2099 let unique_size: usize = page_size::get() * 17;
2109
2110 let pid = std::process::id();
2111
2112 let eshm = ExclusiveSharedMemory::new(unique_size).unwrap();
2113 let (hshm1, gshm) = eshm.build();
2114 let hshm2 = hshm1.clone();
2115
2116 let base_ptr = hshm1.base_ptr() as usize;
2118 let mem_size = hshm1.mem_size();
2119
2120 let has_exact_mapping = |ptr: usize, size: usize| -> bool {
2122 get_process_maps(pid.try_into().unwrap())
2123 .unwrap()
2124 .iter()
2125 .any(|m| m.start() == ptr && m.size() == size)
2126 };
2127
2128 assert!(
2130 has_exact_mapping(base_ptr, mem_size),
2131 "shared memory mapping not found at {:#x} with size {}",
2132 base_ptr,
2133 mem_size
2134 );
2135
2136 drop(hshm1);
2138 drop(hshm2);
2139 drop(gshm);
2140
2141 assert!(
2143 !has_exact_mapping(base_ptr, mem_size),
2144 "shared memory mapping still exists at {:#x} with size {} after drop",
2145 base_ptr,
2146 mem_size
2147 );
2148 }
2149
2150 mod alignment_tests {
2154 use super::*;
2155
2156 const CHUNK_SIZE: usize = size_of::<u128>();
2157
2158 #[test]
2160 fn copy_with_various_alignments() {
2161 let mem_size: usize = page_size::get();
2163 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2164 let (hshm, _) = eshm.build();
2165
2166 for start_offset in 0..CHUNK_SIZE {
2168 let test_len = 64; let test_data: Vec<u8> = (0..test_len).map(|i| (i + start_offset) as u8).collect();
2170
2171 hshm.copy_from_slice(&test_data, start_offset).unwrap();
2173
2174 let mut read_buf = vec![0u8; test_len];
2176 hshm.copy_to_slice(&mut read_buf, start_offset).unwrap();
2177
2178 assert_eq!(
2179 test_data, read_buf,
2180 "Mismatch at alignment offset {}",
2181 start_offset
2182 );
2183 }
2184 }
2185
2186 #[test]
2188 fn copy_small_lengths() {
2189 let mem_size: usize = page_size::get();
2190 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2191 let (hshm, _) = eshm.build();
2192
2193 for len in 0..CHUNK_SIZE {
2194 let test_data: Vec<u8> = (0..len).map(|i| i as u8).collect();
2195
2196 hshm.copy_from_slice(&test_data, 0).unwrap();
2197
2198 let mut read_buf = vec![0u8; len];
2199 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2200
2201 assert_eq!(test_data, read_buf, "Mismatch for length {}", len);
2202 }
2203 }
2204
2205 #[test]
2207 fn copy_non_aligned_lengths() {
2208 let mem_size: usize = page_size::get();
2209 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2210 let (hshm, _) = eshm.build();
2211
2212 let test_lengths = [17, 31, 33, 47, 63, 65, 100, 127, 129, 255, 257];
2214
2215 for &len in &test_lengths {
2216 let test_data: Vec<u8> = (0..len).map(|i| (i % 256) as u8).collect();
2217
2218 hshm.copy_from_slice(&test_data, 0).unwrap();
2219
2220 let mut read_buf = vec![0u8; len];
2221 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2222
2223 assert_eq!(test_data, read_buf, "Mismatch for length {}", len);
2224 }
2225 }
2226
2227 #[test]
2229 fn copy_exact_chunk_size() {
2230 let mem_size: usize = page_size::get();
2231 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2232 let (hshm, _) = eshm.build();
2233
2234 let test_data: Vec<u8> = (0..CHUNK_SIZE).map(|i| i as u8).collect();
2235
2236 hshm.copy_from_slice(&test_data, 0).unwrap();
2237
2238 let mut read_buf = vec![0u8; CHUNK_SIZE];
2239 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2240
2241 assert_eq!(test_data, read_buf);
2242 }
2243
2244 #[test]
2246 fn fill_with_various_alignments() {
2247 let mem_size: usize = page_size::get();
2248 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2249 let (mut hshm, _) = eshm.build();
2250
2251 for start_offset in 0..CHUNK_SIZE {
2252 let fill_len = 64;
2253 let fill_value = (start_offset % 256) as u8;
2254
2255 hshm.fill(0, 0, mem_size).unwrap();
2257
2258 hshm.fill(fill_value, start_offset, fill_len).unwrap();
2260
2261 let mut read_buf = vec![0u8; fill_len];
2263 hshm.copy_to_slice(&mut read_buf, start_offset).unwrap();
2264
2265 assert!(
2266 read_buf.iter().all(|&b| b == fill_value),
2267 "Fill mismatch at alignment offset {}",
2268 start_offset
2269 );
2270 }
2271 }
2272
2273 #[test]
2275 fn fill_small_lengths() {
2276 let mem_size: usize = page_size::get();
2277 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2278 let (mut hshm, _) = eshm.build();
2279
2280 for len in 0..CHUNK_SIZE {
2281 let fill_value = 0xAB;
2282
2283 hshm.fill(0, 0, mem_size).unwrap(); hshm.fill(fill_value, 0, len).unwrap();
2285
2286 let mut read_buf = vec![0u8; len];
2287 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2288
2289 assert!(
2290 read_buf.iter().all(|&b| b == fill_value),
2291 "Fill mismatch for length {}",
2292 len
2293 );
2294 }
2295 }
2296
2297 #[test]
2299 fn fill_non_aligned_lengths() {
2300 let mem_size: usize = page_size::get();
2301 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2302 let (mut hshm, _) = eshm.build();
2303
2304 let test_lengths = [17, 31, 33, 47, 63, 65, 100, 127, 129, 255, 257];
2305
2306 for &len in &test_lengths {
2307 let fill_value = 0xCD;
2308
2309 hshm.fill(0, 0, mem_size).unwrap(); hshm.fill(fill_value, 0, len).unwrap();
2311
2312 let mut read_buf = vec![0u8; len];
2313 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2314
2315 assert!(
2316 read_buf.iter().all(|&b| b == fill_value),
2317 "Fill mismatch for length {}",
2318 len
2319 );
2320 }
2321 }
2322
2323 #[test]
2325 fn copy_edge_cases() {
2326 let mem_size: usize = page_size::get();
2327 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2328 let (hshm, _) = eshm.build();
2329
2330 let empty: Vec<u8> = vec![];
2332 hshm.copy_from_slice(&empty, 0).unwrap();
2333 let mut read_buf: Vec<u8> = vec![];
2334 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2335 assert!(read_buf.is_empty());
2336
2337 let single = vec![0x42u8];
2339 hshm.copy_from_slice(&single, 0).unwrap();
2340 let mut read_buf = vec![0u8; 1];
2341 hshm.copy_to_slice(&mut read_buf, 0).unwrap();
2342 assert_eq!(single, read_buf);
2343 }
2344
2345 #[test]
2347 fn copy_unaligned_start_and_length() {
2348 let mem_size: usize = page_size::get();
2349 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2350 let (hshm, _) = eshm.build();
2351
2352 let start_offset = 7;
2354 let len = 37;
2355 let test_data: Vec<u8> = (0..len).map(|i| (i * 3) as u8).collect();
2356
2357 hshm.copy_from_slice(&test_data, start_offset).unwrap();
2358
2359 let mut read_buf = vec![0u8; len];
2360 hshm.copy_to_slice(&mut read_buf, start_offset).unwrap();
2361
2362 assert_eq!(test_data, read_buf);
2363 }
2364 }
2365
2366 mod try_pop_buffer_bounds {
2368 use super::*;
2369
2370 #[derive(Debug, PartialEq)]
2371 struct RawBytes(Vec<u8>);
2372
2373 impl TryFrom<&[u8]> for RawBytes {
2374 type Error = String;
2375 fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
2376 Ok(RawBytes(value.to_vec()))
2377 }
2378 }
2379
2380 fn make_buffer(mem_size: usize) -> super::super::HostSharedMemory {
2382 let eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
2383 let (hshm, _) = eshm.build();
2384 hshm.write::<u64>(0, 8u64).unwrap();
2385 hshm
2386 }
2387
2388 #[test]
2389 fn normal_push_pop_roundtrip() {
2390 let mem_size = page_size::get();
2391 let mut hshm = make_buffer(mem_size);
2392
2393 let payload = b"hello";
2395 let mut data = Vec::new();
2396 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2397 data.extend_from_slice(payload);
2398
2399 hshm.push_buffer(0, mem_size, &data).unwrap();
2400 let result: RawBytes = hshm.try_pop_buffer_into(0, mem_size).unwrap();
2401 assert_eq!(result.0, data);
2402 }
2403
2404 #[test]
2405 fn malicious_flatbuffer_size_prefix() {
2406 let mem_size = page_size::get();
2407 let mut hshm = make_buffer(mem_size);
2408
2409 let payload = b"small";
2410 let mut data = Vec::new();
2411 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2412 data.extend_from_slice(payload);
2413 hshm.push_buffer(0, mem_size, &data).unwrap();
2414
2415 hshm.write::<u32>(8, 0xFFFF_FFFBu32).unwrap(); let result: Result<RawBytes> = hshm.try_pop_buffer_into(0, mem_size);
2419 let err_msg = format!("{}", result.unwrap_err());
2420 assert!(
2421 err_msg.contains("Corrupt buffer size prefix: flatbuffer claims 4294967295 bytes but the element slot is only 9 bytes"),
2422 "Unexpected error message: {}",
2423 err_msg
2424 );
2425 }
2426
2427 #[test]
2428 fn malicious_element_offset_too_small() {
2429 let mem_size = page_size::get();
2430 let mut hshm = make_buffer(mem_size);
2431
2432 let payload = b"test";
2433 let mut data = Vec::new();
2434 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2435 data.extend_from_slice(payload);
2436 hshm.push_buffer(0, mem_size, &data).unwrap();
2437
2438 hshm.write::<u64>(16, 0u64).unwrap();
2440
2441 let result: Result<RawBytes> = hshm.try_pop_buffer_into(0, mem_size);
2442 let err_msg = format!("{}", result.unwrap_err());
2443 assert!(
2444 err_msg.contains(
2445 "Corrupt buffer back-pointer: element offset 0 is outside valid range [8, 8]"
2446 ),
2447 "Unexpected error message: {}",
2448 err_msg
2449 );
2450 }
2451
2452 #[test]
2453 fn malicious_element_offset_past_stack_pointer() {
2454 let mem_size = page_size::get();
2455 let mut hshm = make_buffer(mem_size);
2456
2457 let payload = b"test";
2458 let mut data = Vec::new();
2459 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2460 data.extend_from_slice(payload);
2461 hshm.push_buffer(0, mem_size, &data).unwrap();
2462
2463 hshm.write::<u64>(16, 9999u64).unwrap();
2465
2466 let result: Result<RawBytes> = hshm.try_pop_buffer_into(0, mem_size);
2467 let err_msg = format!("{}", result.unwrap_err());
2468 assert!(
2469 err_msg.contains(
2470 "Corrupt buffer back-pointer: element offset 9999 is outside valid range [8, 8]"
2471 ),
2472 "Unexpected error message: {}",
2473 err_msg
2474 );
2475 }
2476
2477 #[test]
2478 fn malicious_flatbuffer_size_off_by_one() {
2479 let mem_size = page_size::get();
2480 let mut hshm = make_buffer(mem_size);
2481
2482 let payload = b"abcd";
2483 let mut data = Vec::new();
2484 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2485 data.extend_from_slice(payload);
2486 hshm.push_buffer(0, mem_size, &data).unwrap();
2487
2488 hshm.write::<u32>(8, 5u32).unwrap(); let result: Result<RawBytes> = hshm.try_pop_buffer_into(0, mem_size);
2492 let err_msg = format!("{}", result.unwrap_err());
2493 assert!(
2494 err_msg.contains("Corrupt buffer size prefix: flatbuffer claims 9 bytes but the element slot is only 8 bytes"),
2495 "Unexpected error message: {}",
2496 err_msg
2497 );
2498 }
2499
2500 #[test]
2503 fn back_pointer_near_stack_pointer_underflow() {
2504 let mem_size = page_size::get();
2505 let mut hshm = make_buffer(mem_size);
2506
2507 let payload = b"test";
2508 let mut data = Vec::new();
2509 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2510 data.extend_from_slice(payload);
2511 hshm.push_buffer(0, mem_size, &data).unwrap();
2512
2513 hshm.write::<u64>(16, 23u64).unwrap();
2515
2516 let result: Result<RawBytes> = hshm.try_pop_buffer_into(0, mem_size);
2517 let err_msg = format!("{}", result.unwrap_err());
2518 assert!(
2519 err_msg.contains(
2520 "Corrupt buffer back-pointer: element offset 23 is outside valid range [8, 8]"
2521 ),
2522 "Unexpected error message: {}",
2523 err_msg
2524 );
2525 }
2526
2527 #[test]
2529 fn size_prefix_u32_overflow() {
2530 let mem_size = page_size::get();
2531 let mut hshm = make_buffer(mem_size);
2532
2533 let payload = b"test";
2534 let mut data = Vec::new();
2535 data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2536 data.extend_from_slice(payload);
2537 hshm.push_buffer(0, mem_size, &data).unwrap();
2538
2539 hshm.write::<u32>(8, 0xFFFF_FFFDu32).unwrap();
2541
2542 let result: Result<RawBytes> = hshm.try_pop_buffer_into(0, mem_size);
2543 let err_msg = format!("{}", result.unwrap_err());
2544 assert!(
2545 err_msg.contains("Corrupt buffer size prefix: value 4294967293 overflows when adding 4-byte header"),
2546 "Unexpected error message: {}",
2547 err_msg
2548 );
2549 }
2550 }
2551
2552 #[cfg(target_os = "linux")]
2553 mod guard_page_crash_test {
2554 use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory};
2555
2556 const TEST_EXIT_CODE: u8 = 211; fn setup_signal_handler() {
2562 unsafe {
2563 signal_hook_registry::register_signal_unchecked(libc::SIGSEGV, || {
2564 std::process::exit(TEST_EXIT_CODE.into());
2565 })
2566 .unwrap();
2567 }
2568 }
2569
2570 #[test]
2571 #[ignore] fn read() {
2573 setup_signal_handler();
2574
2575 let eshm = ExclusiveSharedMemory::new(page_size::get()).unwrap();
2576 let (hshm, _) = eshm.build();
2577 let guard_page_ptr = hshm.raw_ptr();
2578 unsafe { std::ptr::read_volatile(guard_page_ptr) };
2579 }
2580
2581 #[test]
2582 #[ignore] fn write() {
2584 setup_signal_handler();
2585
2586 let eshm = ExclusiveSharedMemory::new(page_size::get()).unwrap();
2587 let (hshm, _) = eshm.build();
2588 let guard_page_ptr = hshm.raw_ptr();
2589 unsafe { std::ptr::write_volatile(guard_page_ptr, 0u8) };
2590 }
2591
2592 #[test]
2593 #[ignore] fn exec() {
2595 setup_signal_handler();
2596
2597 let eshm = ExclusiveSharedMemory::new(page_size::get()).unwrap();
2598 let (hshm, _) = eshm.build();
2599 let guard_page_ptr = hshm.raw_ptr();
2600 let func: fn() = unsafe { std::mem::transmute(guard_page_ptr) };
2601 func();
2602 }
2603
2604 #[test]
2606 #[cfg_attr(miri, ignore)] fn guard_page_testing_shim() {
2608 let tests = vec!["read", "write", "exec"];
2609 for test in tests {
2610 let triple = std::env::var("TARGET_TRIPLE").ok();
2611 let target_args = if let Some(triple) = triple.filter(|t| !t.is_empty()) {
2612 vec!["--target".to_string(), triple.to_string()]
2613 } else {
2614 vec![]
2615 };
2616 let output = std::process::Command::new("cargo")
2617 .args(["test", "-p", "hyperlight-host", "--lib"])
2618 .args(target_args)
2619 .args(["--", "--ignored", test])
2620 .stdin(std::process::Stdio::null())
2621 .output()
2622 .expect("Unable to launch tests");
2623 let exit_code = output.status.code();
2624 if exit_code != Some(TEST_EXIT_CODE.into()) {
2625 eprintln!("=== Guard Page test '{}' failed ===", test);
2626 eprintln!("Exit code: {:?} (expected {})", exit_code, TEST_EXIT_CODE);
2627 eprintln!("=== STDOUT ===");
2628 eprintln!("{}", String::from_utf8_lossy(&output.stdout));
2629 eprintln!("=== STDERR ===");
2630 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
2631 panic!(
2632 "Guard Page test failed: {} (exit code {:?}, expected {})",
2633 test, exit_code, TEST_EXIT_CODE
2634 );
2635 }
2636 }
2637 }
2638 }
2639
2640 #[cfg(not(miri))]
2641 mod from_file_tests {
2642 use std::io::Write;
2643
2644 use tempfile::NamedTempFile;
2645
2646 use crate::mem::shared_mem::{ReadonlySharedMemory, SharedMemory};
2647
2648 pub(super) fn make_temp_file(len: usize) -> NamedTempFile {
2649 let mut f = NamedTempFile::new().expect("create temp file");
2650 if len > 0 {
2651 let mut buf = vec![0u8; len];
2652 for (i, b) in buf.iter_mut().enumerate() {
2653 *b = (i & 0xff) as u8;
2654 }
2655 f.write_all(&buf).expect("write temp file");
2656 f.flush().expect("flush temp file");
2657 }
2658 f
2659 }
2660
2661 #[test]
2662 fn from_file_success_single_page() {
2663 let tmp = make_temp_file(page_size::get());
2664 let mut rsm = ReadonlySharedMemory::from_file(tmp.as_file(), page_size::get())
2665 .expect("from_file should succeed");
2666 assert_eq!(rsm.mem_size(), page_size::get());
2667 rsm.with_contents(|slice| {
2668 for (i, b) in slice.iter().enumerate() {
2669 assert_eq!(*b, (i & 0xff) as u8);
2670 }
2671 })
2672 .expect("with_contents should succeed");
2673 }
2674
2675 #[test]
2676 fn from_file_success_smaller_guest_mapped_size() {
2677 let tmp = make_temp_file(2 * page_size::get());
2678 let rsm = ReadonlySharedMemory::from_file(tmp.as_file(), page_size::get())
2679 .expect("from_file should succeed");
2680 assert_eq!(rsm.mem_size(), 2 * page_size::get());
2681 }
2682
2683 #[test]
2684 fn from_file_rejects_empty_file() {
2685 let tmp = make_temp_file(0);
2686 let err = ReadonlySharedMemory::from_file(tmp.as_file(), page_size::get())
2687 .expect_err("empty file should be rejected");
2688 assert!(format!("{}", err).contains("0x0 < 0x0"));
2689 }
2690
2691 #[test]
2692 fn from_file_rejects_unaligned_file_length() {
2693 let tmp = make_temp_file(page_size::get() + 1);
2694 let err = ReadonlySharedMemory::from_file(tmp.as_file(), page_size::get())
2695 .expect_err("unaligned file length should be rejected");
2696 assert!(format!("{}", err).contains(&format!(
2697 "0x{:x} % 0x{:x} = 0",
2698 page_size::get() + 1,
2699 page_size::get()
2700 )));
2701 }
2702
2703 #[test]
2704 fn from_file_rejects_zero_guest_mapped_size() {
2705 let tmp = make_temp_file(page_size::get());
2706 let err = ReadonlySharedMemory::from_file(tmp.as_file(), 0)
2707 .expect_err("zero guest_mapped_size should be rejected");
2708 assert!(format!("{}", err).contains("0x0 < 0x0"));
2709 }
2710
2711 #[test]
2712 fn from_file_rejects_unaligned_guest_mapped_size() {
2713 let tmp = make_temp_file(2 * page_size::get());
2714 let err = ReadonlySharedMemory::from_file(tmp.as_file(), page_size::get() + 1)
2715 .expect_err("unaligned guest_mapped_size should be rejected");
2716 assert!(format!("{}", err).contains(&format!(
2717 "0x{:x} % 0x{:x} = ",
2718 page_size::get() + 1,
2719 page_size::get()
2720 )));
2721 }
2722
2723 #[test]
2724 fn from_file_rejects_guest_mapped_size_exceeding_file() {
2725 let tmp = make_temp_file(page_size::get());
2726 let err = ReadonlySharedMemory::from_file(tmp.as_file(), 2 * page_size::get())
2727 .expect_err("guest_mapped_size > file length should be rejected");
2728 assert!(format!("{}", err).contains(&format!(
2729 "0x{:x} <= 0x{:x}",
2730 2 * page_size::get(),
2731 page_size::get()
2732 )));
2733 }
2734
2735 mod guard_page_crash_tests {
2741 use super::make_temp_file;
2742 use crate::mem::shared_mem::{ReadonlySharedMemory, SharedMemory};
2743
2744 #[test]
2746 #[ignore]
2747 pub(super) fn leading_guard_page_traps() {
2748 let tmp = make_temp_file(page_size::get());
2749 let rsm = ReadonlySharedMemory::from_file(tmp.as_file(), page_size::get())
2750 .expect("from_file should succeed");
2751 let guard_ptr = unsafe { rsm.base_ptr().sub(page_size::get()) };
2752 println!("reached_guard");
2753 let _ = unsafe { std::ptr::read_volatile(guard_ptr) };
2754 println!("survived_guard");
2755 }
2756
2757 #[test]
2759 #[ignore]
2760 pub(super) fn trailing_guard_page_traps() {
2761 let tmp = make_temp_file(page_size::get());
2762 let rsm = ReadonlySharedMemory::from_file(tmp.as_file(), page_size::get())
2763 .expect("from_file should succeed");
2764 let guard_ptr = unsafe { rsm.base_ptr().add(rsm.mem_size()) };
2765 println!("reached_guard");
2766 let _ = unsafe { std::ptr::read_volatile(guard_ptr) };
2767 println!("survived_guard");
2768 }
2769 }
2770
2771 #[test]
2776 #[cfg_attr(miri, ignore)] fn from_file_guard_page_shim() {
2778 use guard_page_crash_tests::{leading_guard_page_traps, trailing_guard_page_traps};
2779 let ignored_test_paths = [
2780 test_path(leading_guard_page_traps),
2781 test_path(trailing_guard_page_traps),
2782 ];
2783
2784 let exe = std::env::current_exe().expect("current_exe");
2785 for path in &ignored_test_paths {
2786 run_guard_page_subprocess(&exe, path);
2787 }
2788 }
2789
2790 fn test_path<F: Fn()>(_: F) -> &'static str {
2794 let full = std::any::type_name::<F>();
2795 let (_, rest) = full
2796 .split_once("::")
2797 .expect("type_name of a function item is always qualified by the crate name");
2798 rest
2799 }
2800
2801 fn run_guard_page_subprocess(exe: &std::path::Path, ignored_test_path: &str) {
2802 let output = std::process::Command::new(exe)
2803 .args([
2804 "--ignored",
2805 "--nocapture",
2806 "--exact",
2807 "--test-threads=1",
2808 ignored_test_path,
2809 ])
2810 .stdin(std::process::Stdio::null())
2811 .output()
2812 .expect("Unable to launch subprocess test");
2813
2814 let stdout = String::from_utf8_lossy(&output.stdout);
2815 let stderr = String::from_utf8_lossy(&output.stderr);
2816
2817 let ran_test = stdout.contains("running 1 test");
2820 let reached = stdout.contains("reached_guard");
2821 let survived = stdout.contains("survived_guard");
2822 let by_access_violation = killed_by_access_violation(&output.status);
2823
2824 let ok = reached && !survived && by_access_violation && ran_test;
2825 if !ok {
2826 eprintln!("=== Guard page shim failed for {} ===", ignored_test_path);
2827 eprintln!(
2828 "status={:?} ran_test={} reached={} survived={} by_access_violation={}",
2829 output.status, ran_test, reached, survived, by_access_violation
2830 );
2831 eprintln!("=== STDOUT ===\n{}", stdout);
2832 eprintln!("=== STDERR ===\n{}", stderr);
2833 let hint = if !ran_test {
2834 format!(
2835 "\nHINT: ran_test=false (subprocess reported 'running 0 tests'). \
2836 Most likely cause is a stale test path in the shim. Verify that \
2837 `{}` still exists and matches the path passed via --exact above.",
2838 ignored_test_path
2839 )
2840 } else {
2841 String::new()
2842 };
2843 panic!(
2844 "Expected subprocess to run {}, print 'reached_guard', \
2845 then die from a memory access fault. ran_test={}, reached={}, \
2846 survived={}, by_access_violation={}, status={:?}{}",
2847 ignored_test_path,
2848 ran_test,
2849 reached,
2850 survived,
2851 by_access_violation,
2852 output.status,
2853 hint
2854 );
2855 }
2856
2857 println!(
2858 "guard page trap confirmed for {}: subprocess terminated with {:?}",
2859 ignored_test_path, output.status
2860 );
2861 }
2862
2863 fn killed_by_access_violation(status: &std::process::ExitStatus) -> bool {
2867 #[cfg(unix)]
2868 {
2869 use std::os::unix::process::ExitStatusExt;
2870 let expected_signal = if cfg!(target_os = "macos") {
2871 libc::SIGBUS
2872 } else {
2873 libc::SIGSEGV
2874 };
2875 status.signal() == Some(expected_signal)
2876 }
2877 #[cfg(windows)]
2878 {
2879 use windows::Win32::Foundation::STATUS_ACCESS_VIOLATION;
2880 status.code() == Some(STATUS_ACCESS_VIOLATION.0) || status.code() == Some(0xDEAD)
2882 }
2883 }
2884 }
2885}