1 use crate::prelude::*;
2 use crate::runtime::vm::sys::vm::MemoryImageSource;
3 use crate::runtime::vm::{HostAlignedByteCount, SendSyncPtr};
4 use std::fs::{File, OpenOptions};
5 use std::io;
6 use std::ops::Range;
7 use std::os::windows::prelude::*;
8 use std::path::Path;
9 use std::ptr::{self, NonNull};
10 use windows_sys::Win32::Foundation::*;
11 use windows_sys::Win32::Storage::FileSystem::*;
12 use windows_sys::Win32::System::Memory::*;
13 
14 /// Open a file so that it can be mmap'd for executing.
15 ///
16 /// Open the file with read/execute access and only share for
17 /// read. This will enable us to perform the proper mmap below
18 /// while also disallowing other processes modifying the file
19 /// and having those modifications show up in our address space.
open_file_for_mmap(path: &Path) -> Result<File>20 pub fn open_file_for_mmap(path: &Path) -> Result<File> {
21     OpenOptions::new()
22         .read(true)
23         .access_mode(FILE_GENERIC_READ | FILE_GENERIC_EXECUTE)
24         .share_mode(FILE_SHARE_READ)
25         .open(path)
26         .context("failed to open file")
27 }
28 
29 #[derive(Debug)]
30 pub struct Mmap {
31     memory: SendSyncPtr<[u8]>,
32     is_file: bool,
33 }
34 
35 impl Mmap {
new_empty() -> Mmap36     pub fn new_empty() -> Mmap {
37         Mmap {
38             memory: crate::vm::sys::empty_mmap(),
39             is_file: false,
40         }
41     }
42 
new(size: HostAlignedByteCount) -> Result<Self>43     pub fn new(size: HostAlignedByteCount) -> Result<Self> {
44         let ptr = unsafe {
45             VirtualAlloc(
46                 ptr::null_mut(),
47                 size.byte_count(),
48                 MEM_RESERVE | MEM_COMMIT,
49                 PAGE_READWRITE,
50             )
51         };
52         if ptr.is_null() {
53             bail!(io::Error::last_os_error())
54         }
55 
56         let memory = std::ptr::slice_from_raw_parts_mut(ptr.cast(), size.byte_count());
57         let memory = SendSyncPtr::new(NonNull::new(memory).unwrap());
58         Ok(Self {
59             memory,
60             is_file: false,
61         })
62     }
63 
reserve(size: HostAlignedByteCount) -> Result<Self>64     pub fn reserve(size: HostAlignedByteCount) -> Result<Self> {
65         let ptr = unsafe {
66             VirtualAlloc(
67                 ptr::null_mut(),
68                 size.byte_count(),
69                 MEM_RESERVE,
70                 PAGE_NOACCESS,
71             )
72         };
73         if ptr.is_null() {
74             bail!(io::Error::last_os_error())
75         }
76         let memory = std::ptr::slice_from_raw_parts_mut(ptr.cast(), size.byte_count());
77         let memory = SendSyncPtr::new(NonNull::new(memory).unwrap());
78         Ok(Self {
79             memory,
80             is_file: false,
81         })
82     }
83 
from_file(file: &File) -> Result<Self>84     pub fn from_file(file: &File) -> Result<Self> {
85         unsafe {
86             let len = file
87                 .metadata()
88                 .context("failed to get file metadata")?
89                 .len();
90             let len = usize::try_from(len).map_err(|_| format_err!("file too large to map"))?;
91 
92             // Create a file mapping that allows PAGE_EXECUTE_WRITECOPY.
93             // This enables up-to these permissions but we won't leave all
94             // of these permissions active at all times. Execution is
95             // necessary for the generated code from Cranelift and the
96             // WRITECOPY part is needed for possibly resolving relocations,
97             // but otherwise writes don't happen.
98             let mapping = CreateFileMappingW(
99                 file.as_raw_handle(),
100                 ptr::null_mut(),
101                 PAGE_EXECUTE_WRITECOPY,
102                 0,
103                 0,
104                 ptr::null(),
105             );
106             if mapping == INVALID_HANDLE_VALUE {
107                 return Err(io::Error::last_os_error()).context("failed to create file mapping");
108             }
109 
110             // Create a view for the entire file using all our requisite
111             // permissions so that we can change the virtual permissions
112             // later on.
113             let ptr = MapViewOfFile(
114                 mapping,
115                 FILE_MAP_READ | FILE_MAP_EXECUTE | FILE_MAP_COPY,
116                 0,
117                 0,
118                 len,
119             )
120             .Value;
121             let err = io::Error::last_os_error();
122             CloseHandle(mapping);
123             if ptr.is_null() {
124                 return Err(err).context(format!("failed to create map view of {len:#x} bytes"));
125             }
126 
127             let memory = std::ptr::slice_from_raw_parts_mut(ptr.cast(), len);
128             let memory = SendSyncPtr::new(NonNull::new(memory).unwrap());
129             let ret = Self {
130                 memory,
131                 is_file: true,
132             };
133 
134             // Protect the entire file as PAGE_WRITECOPY to start (i.e.
135             // remove the execute bit)
136             let mut old = 0;
137             if VirtualProtect(
138                 ret.as_send_sync_ptr().as_ptr().cast(),
139                 ret.len(),
140                 PAGE_WRITECOPY,
141                 &mut old,
142             ) == 0
143             {
144                 return Err(io::Error::last_os_error())
145                     .context("failed change pages to `PAGE_READONLY`");
146             }
147 
148             Ok(ret)
149         }
150     }
151 
make_accessible( &self, start: HostAlignedByteCount, len: HostAlignedByteCount, ) -> Result<()>152     pub unsafe fn make_accessible(
153         &self,
154         start: HostAlignedByteCount,
155         len: HostAlignedByteCount,
156     ) -> Result<()> {
157         if unsafe {
158             VirtualAlloc(
159                 self.as_send_sync_ptr()
160                     .as_ptr()
161                     .add(start.byte_count())
162                     .cast(),
163                 len.byte_count(),
164                 MEM_COMMIT,
165                 PAGE_READWRITE,
166             )
167         }
168         .is_null()
169         {
170             bail!(io::Error::last_os_error())
171         }
172 
173         Ok(())
174     }
175 
176     #[inline]
as_send_sync_ptr(&self) -> SendSyncPtr<u8>177     pub fn as_send_sync_ptr(&self) -> SendSyncPtr<u8> {
178         self.memory.cast()
179     }
180 
181     #[inline]
len(&self) -> usize182     pub fn len(&self) -> usize {
183         self.memory.as_ptr().len()
184     }
185 
make_executable( &self, range: Range<usize>, enable_branch_protection: bool, ) -> Result<()>186     pub unsafe fn make_executable(
187         &self,
188         range: Range<usize>,
189         enable_branch_protection: bool,
190     ) -> Result<()> {
191         let base = self
192             .as_send_sync_ptr()
193             .as_ptr()
194             .wrapping_add(range.start)
195             .cast();
196         let len = range.end - range.start;
197 
198         if !cfg!(feature = "std") {
199             bail!(
200                 "with the `std` feature disabled at compile time \
201                  there must be a custom implementation of publishing \
202                  code memory, otherwise it's unknown how to do icache \
203                  management"
204             );
205         }
206 
207         // Clear the newly allocated code from cache if the processor requires
208         // it
209         //
210         // Do this before marking the memory as R+X, technically we should be
211         // able to do it after but there are some CPU's that have had errata
212         // about doing this with read only memory.
213         #[cfg(feature = "std")]
214         unsafe {
215             wasmtime_jit_icache_coherence::clear_cache(base, len).context("failed cache clear")?;
216         }
217 
218         let flags = if enable_branch_protection {
219             // TODO: We use this check to avoid an unused variable warning,
220             // but some of the CFG-related flags might be applicable
221             PAGE_EXECUTE_READ
222         } else {
223             PAGE_EXECUTE_READ
224         };
225         let mut old = 0;
226         unsafe {
227             let result = VirtualProtect(base, len, flags, &mut old);
228             if result == 0 {
229                 bail!(io::Error::last_os_error());
230             }
231         }
232 
233         // Flush any in-flight instructions from the pipeline
234         #[cfg(feature = "std")]
235         wasmtime_jit_icache_coherence::pipeline_flush_mt().context("Failed pipeline flush")?;
236         Ok(())
237     }
238 
make_readonly(&self, range: Range<usize>) -> Result<()>239     pub unsafe fn make_readonly(&self, range: Range<usize>) -> Result<()> {
240         let mut old = 0;
241         unsafe {
242             let base = self.as_send_sync_ptr().as_ptr().add(range.start).cast();
243             let result = VirtualProtect(base, range.end - range.start, PAGE_READONLY, &mut old);
244             if result == 0 {
245                 bail!(io::Error::last_os_error());
246             }
247         }
248         Ok(())
249     }
250 
make_readwrite(&self, range: Range<usize>) -> Result<()>251     pub unsafe fn make_readwrite(&self, range: Range<usize>) -> Result<()> {
252         let mut old = 0;
253         unsafe {
254             let base = self.as_send_sync_ptr().as_ptr().add(range.start).cast();
255             let result = VirtualProtect(base, range.end - range.start, PAGE_READWRITE, &mut old);
256             if result == 0 {
257                 bail!(io::Error::last_os_error());
258             }
259         }
260         Ok(())
261     }
262 
map_image_at( &self, image_source: &MemoryImageSource, _source_offset: u64, _memory_offset: HostAlignedByteCount, _memory_len: HostAlignedByteCount, ) -> Result<()>263     pub unsafe fn map_image_at(
264         &self,
265         image_source: &MemoryImageSource,
266         _source_offset: u64,
267         _memory_offset: HostAlignedByteCount,
268         _memory_len: HostAlignedByteCount,
269     ) -> Result<()> {
270         match *image_source {}
271     }
272 }
273 
274 impl Drop for Mmap {
drop(&mut self)275     fn drop(&mut self) {
276         if self.len() == 0 {
277             return;
278         }
279 
280         if self.is_file {
281             let r = unsafe {
282                 UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
283                     Value: self.memory.as_ptr().cast(),
284                 })
285             };
286             assert_ne!(r, 0);
287         } else {
288             let r = unsafe { VirtualFree(self.memory.as_ptr().cast(), 0, MEM_RELEASE) };
289             assert_ne!(r, 0);
290         }
291     }
292 }
293