1 use cranelift_module::{ModuleError, ModuleResult}; 2 3 #[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))] 4 use memmap2::MmapMut; 5 6 #[cfg(not(any(feature = "selinux-fix", windows)))] 7 use std::alloc; 8 use std::io; 9 use std::mem; 10 use std::ptr; 11 12 use super::{BranchProtection, JITMemoryKind, JITMemoryProvider}; 13 14 /// A simple struct consisting of a pointer and length. 15 struct PtrLen { 16 #[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))] 17 map: Option<MmapMut>, 18 19 ptr: *mut u8, 20 len: usize, 21 } 22 23 impl PtrLen { 24 /// Create a new empty `PtrLen`. new() -> Self25 fn new() -> Self { 26 Self { 27 #[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))] 28 map: None, 29 30 ptr: ptr::null_mut(), 31 len: 0, 32 } 33 } 34 35 /// Create a new `PtrLen` pointing to at least `size` bytes of memory, 36 /// suitably sized and aligned for memory protection. 37 #[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))] with_size(size: usize) -> io::Result<Self>38 fn with_size(size: usize) -> io::Result<Self> { 39 let alloc_size = region::page::ceil(size as *const ()) as usize; 40 MmapMut::map_anon(alloc_size).map(|mut mmap| { 41 // The order here is important; we assign the pointer first to get 42 // around compile time borrow errors. 43 Self { 44 ptr: mmap.as_mut_ptr(), 45 map: Some(mmap), 46 len: alloc_size, 47 } 48 }) 49 } 50 51 #[cfg(all(not(target_os = "windows"), not(feature = "selinux-fix")))] with_size(size: usize) -> io::Result<Self>52 fn with_size(size: usize) -> io::Result<Self> { 53 assert_ne!(size, 0); 54 let page_size = region::page::size(); 55 let alloc_size = region::page::ceil(size as *const ()) as usize; 56 let layout = alloc::Layout::from_size_align(alloc_size, page_size).unwrap(); 57 // Safety: We assert that the size is non-zero above. 58 let ptr = unsafe { alloc::alloc(layout) }; 59 60 if !ptr.is_null() { 61 Ok(Self { 62 ptr, 63 len: alloc_size, 64 }) 65 } else { 66 Err(io::Error::from(io::ErrorKind::OutOfMemory)) 67 } 68 } 69 70 #[cfg(target_os = "windows")] with_size(size: usize) -> io::Result<Self>71 fn with_size(size: usize) -> io::Result<Self> { 72 use windows_sys::Win32::System::Memory::{ 73 MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE, VirtualAlloc, 74 }; 75 76 // VirtualAlloc always rounds up to the next multiple of the page size 77 let ptr = unsafe { 78 VirtualAlloc( 79 ptr::null_mut(), 80 size, 81 MEM_COMMIT | MEM_RESERVE, 82 PAGE_READWRITE, 83 ) 84 }; 85 if !ptr.is_null() { 86 Ok(Self { 87 ptr: ptr as *mut u8, 88 len: region::page::ceil(size as *const ()) as usize, 89 }) 90 } else { 91 Err(io::Error::last_os_error()) 92 } 93 } 94 } 95 96 // `MMapMut` from `cfg(feature = "selinux-fix")` already deallocates properly. 97 #[cfg(all(not(target_os = "windows"), not(feature = "selinux-fix")))] 98 impl Drop for PtrLen { drop(&mut self)99 fn drop(&mut self) { 100 if !self.ptr.is_null() { 101 let page_size = region::page::size(); 102 let layout = alloc::Layout::from_size_align(self.len, page_size).unwrap(); 103 unsafe { 104 region::protect(self.ptr, self.len, region::Protection::READ_WRITE) 105 .expect("unable to unprotect memory"); 106 alloc::dealloc(self.ptr, layout) 107 } 108 } 109 } 110 } 111 112 // TODO: add a `Drop` impl for `cfg(target_os = "windows")` 113 114 /// JIT memory manager. This manages pages of suitably aligned and 115 /// accessible memory. Memory will be leaked by default to have 116 /// function pointers remain valid for the remainder of the 117 /// program's life. 118 pub(crate) struct Memory { 119 allocations: Vec<PtrLen>, 120 already_protected: usize, 121 current: PtrLen, 122 position: usize, 123 } 124 125 unsafe impl Send for Memory {} 126 127 impl Memory { new() -> Self128 pub(crate) fn new() -> Self { 129 Self { 130 allocations: Vec::new(), 131 already_protected: 0, 132 current: PtrLen::new(), 133 position: 0, 134 } 135 } 136 finish_current(&mut self)137 fn finish_current(&mut self) { 138 self.allocations 139 .push(mem::replace(&mut self.current, PtrLen::new())); 140 self.position = 0; 141 } 142 allocate(&mut self, size: usize, align: u64) -> io::Result<*mut u8>143 pub(crate) fn allocate(&mut self, size: usize, align: u64) -> io::Result<*mut u8> { 144 let align = usize::try_from(align).expect("alignment too big"); 145 if self.position % align != 0 { 146 self.position += align - self.position % align; 147 debug_assert!(self.position % align == 0); 148 } 149 150 if size <= self.current.len - self.position { 151 // TODO: Ensure overflow is not possible. 152 let ptr = unsafe { self.current.ptr.add(self.position) }; 153 self.position += size; 154 return Ok(ptr); 155 } 156 157 self.finish_current(); 158 159 // TODO: Allocate more at a time. 160 self.current = PtrLen::with_size(size)?; 161 self.position = size; 162 163 Ok(self.current.ptr) 164 } 165 166 /// Set all memory allocated in this `Memory` up to now as readable and executable. set_readable_and_executable( &mut self, branch_protection: BranchProtection, ) -> ModuleResult<()>167 pub(crate) fn set_readable_and_executable( 168 &mut self, 169 branch_protection: BranchProtection, 170 ) -> ModuleResult<()> { 171 self.finish_current(); 172 173 for &PtrLen { ptr, len, .. } in self.non_protected_allocations_iter() { 174 super::set_readable_and_executable(ptr, len, branch_protection)?; 175 } 176 177 // Flush any in-flight instructions from the pipeline 178 wasmtime_jit_icache_coherence::pipeline_flush_mt().expect("Failed pipeline flush"); 179 180 self.already_protected = self.allocations.len(); 181 Ok(()) 182 } 183 184 /// Set all memory allocated in this `Memory` up to now as readonly. set_readonly(&mut self) -> ModuleResult<()>185 pub(crate) fn set_readonly(&mut self) -> ModuleResult<()> { 186 self.finish_current(); 187 188 for &PtrLen { ptr, len, .. } in self.non_protected_allocations_iter() { 189 unsafe { 190 region::protect(ptr, len, region::Protection::READ).map_err(|e| { 191 ModuleError::Backend( 192 anyhow::Error::new(e).context("unable to make memory readonly"), 193 ) 194 })?; 195 } 196 } 197 198 self.already_protected = self.allocations.len(); 199 Ok(()) 200 } 201 202 /// Iterates non protected memory allocations that are of not zero bytes in size. non_protected_allocations_iter(&self) -> impl Iterator<Item = &PtrLen>203 fn non_protected_allocations_iter(&self) -> impl Iterator<Item = &PtrLen> { 204 let iter = self.allocations[self.already_protected..].iter(); 205 206 #[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))] 207 return iter.filter(|&PtrLen { map, len, .. }| *len != 0 && map.is_some()); 208 209 #[cfg(any(target_os = "windows", not(feature = "selinux-fix")))] 210 return iter.filter(|&PtrLen { len, .. }| *len != 0); 211 } 212 213 /// Frees all allocated memory regions that would be leaked otherwise. 214 /// Likely to invalidate existing function pointers, causing unsafety. free_memory(&mut self)215 pub(crate) unsafe fn free_memory(&mut self) { 216 self.allocations.clear(); 217 self.already_protected = 0; 218 } 219 } 220 221 impl Drop for Memory { drop(&mut self)222 fn drop(&mut self) { 223 // leak memory to guarantee validity of function pointers 224 mem::replace(&mut self.allocations, Vec::new()) 225 .into_iter() 226 .for_each(mem::forget); 227 } 228 } 229 230 /// A memory provider that allocates memory on-demand using the system 231 /// allocator. 232 /// 233 /// Note: Memory will be leaked by default unless 234 /// [`JITMemoryProvider::free_memory`] is called to ensure function pointers 235 /// remain valid for the remainder of the program's life. 236 pub struct SystemMemoryProvider { 237 code: Memory, 238 readonly: Memory, 239 writable: Memory, 240 } 241 242 impl SystemMemoryProvider { 243 /// Create a new memory handle with the given branch protection. new() -> Self244 pub fn new() -> Self { 245 Self { 246 code: Memory::new(), 247 readonly: Memory::new(), 248 writable: Memory::new(), 249 } 250 } 251 } 252 253 impl JITMemoryProvider for SystemMemoryProvider { free_memory(&mut self)254 unsafe fn free_memory(&mut self) { 255 self.code.free_memory(); 256 self.readonly.free_memory(); 257 self.writable.free_memory(); 258 } 259 finalize(&mut self, branch_protection: BranchProtection) -> ModuleResult<()>260 fn finalize(&mut self, branch_protection: BranchProtection) -> ModuleResult<()> { 261 self.readonly.set_readonly()?; 262 self.code.set_readable_and_executable(branch_protection) 263 } 264 allocate(&mut self, size: usize, align: u64, kind: JITMemoryKind) -> io::Result<*mut u8>265 fn allocate(&mut self, size: usize, align: u64, kind: JITMemoryKind) -> io::Result<*mut u8> { 266 match kind { 267 JITMemoryKind::Executable => self.code.allocate(size, align), 268 JITMemoryKind::Writable => self.writable.allocate(size, align), 269 JITMemoryKind::ReadOnly => self.readonly.allocate(size, align), 270 } 271 } 272 } 273