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