1 //! Memory management for executable code.
2 
3 use crate::Engine;
4 use crate::prelude::*;
5 use crate::runtime::vm::MmapVec;
6 use alloc::sync::Arc;
7 use core::ops::Range;
8 use object::SectionFlags;
9 use object::endian::Endianness;
10 use object::read::{Object, ObjectSection, elf::ElfFile64};
11 use wasmtime_environ::{Trap, lookup_trap_code, obj};
12 use wasmtime_unwinder::ExceptionTable;
13 
14 /// Management of executable memory within a `MmapVec`
15 ///
16 /// This type consumes ownership of a region of memory and will manage the
17 /// executable permissions of the contained JIT code as necessary.
18 pub struct CodeMemory {
19     mmap: MmapVec,
20     #[cfg(has_host_compiler_backend)]
21     unwind_registration: Option<crate::runtime::vm::UnwindRegistration>,
22     #[cfg(feature = "debug-builtins")]
23     debug_registration: Option<crate::runtime::vm::GdbJitImageRegistration>,
24     published: bool,
25     enable_branch_protection: bool,
26     needs_executable: bool,
27     #[cfg(feature = "debug-builtins")]
28     has_native_debug_info: bool,
29     custom_code_memory: Option<Arc<dyn CustomCodeMemory>>,
30 
31     // Ranges within `self.mmap` of where the particular sections lie.
32     text: Range<usize>,
33     unwind: Range<usize>,
34     trap_data: Range<usize>,
35     wasm_data: Range<usize>,
36     address_map_data: Range<usize>,
37     stack_map_data: Range<usize>,
38     exception_data: Range<usize>,
39     func_name_data: Range<usize>,
40     info_data: Range<usize>,
41     wasm_dwarf: Range<usize>,
42 }
43 
44 impl Drop for CodeMemory {
45     fn drop(&mut self) {
46         // If there is a custom code memory handler, restore the
47         // original (non-executable) state of the memory.
48         if let Some(mem) = self.custom_code_memory.as_ref() {
49             if self.published && self.needs_executable {
50                 let text = self.text();
51                 mem.unpublish_executable(text.as_ptr(), text.len())
52                     .expect("Executable memory unpublish failed");
53             }
54         }
55 
56         // Drop the registrations before `self.mmap` since they (implicitly) refer to it.
57         #[cfg(has_host_compiler_backend)]
58         let _ = self.unwind_registration.take();
59         #[cfg(feature = "debug-builtins")]
60         let _ = self.debug_registration.take();
61     }
62 }
63 
64 fn _assert() {
65     fn _assert_send_sync<T: Send + Sync>() {}
66     _assert_send_sync::<CodeMemory>();
67 }
68 
69 /// Interface implemented by an embedder to provide custom
70 /// implementations of code-memory protection and execute permissions.
71 pub trait CustomCodeMemory: Send + Sync {
72     /// The minimal alignment granularity for an address region that
73     /// can be made executable.
74     ///
75     /// Wasmtime does not assume the system page size for this because
76     /// custom code-memory protection can be used when all other uses
77     /// of virtual memory are disabled.
78     fn required_alignment(&self) -> usize;
79 
80     /// Publish a region of memory as executable.
81     ///
82     /// This should update permissions from the default RW
83     /// (readable/writable but not executable) to RX
84     /// (readable/executable but not writable), enforcing W^X
85     /// discipline.
86     ///
87     /// If the platform requires any data/instruction coherence
88     /// action, that should be performed as part of this hook as well.
89     ///
90     /// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as
91     /// per `required_alignment()`.
92     fn publish_executable(&self, ptr: *const u8, len: usize) -> anyhow::Result<()>;
93 
94     /// Unpublish a region of memory.
95     ///
96     /// This should perform the opposite effect of `make_executable`,
97     /// switching a range of memory back from RX (readable/executable)
98     /// to RW (readable/writable). It is guaranteed that no code is
99     /// running anymore from this region.
100     ///
101     /// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as
102     /// per `required_alignment()`.
103     fn unpublish_executable(&self, ptr: *const u8, len: usize) -> anyhow::Result<()>;
104 }
105 
106 impl CodeMemory {
107     /// Creates a new `CodeMemory` by taking ownership of the provided
108     /// `MmapVec`.
109     ///
110     /// The returned `CodeMemory` manages the internal `MmapVec` and the
111     /// `publish` method is used to actually make the memory executable.
112     pub fn new(engine: &Engine, mmap: MmapVec) -> Result<Self> {
113         let obj = ElfFile64::<Endianness>::parse(&mmap[..])
114             .map_err(obj::ObjectCrateErrorWrapper)
115             .with_context(|| "failed to parse internal compilation artifact")?;
116 
117         let mut text = 0..0;
118         let mut unwind = 0..0;
119         let mut enable_branch_protection = None;
120         let mut needs_executable = true;
121         #[cfg(feature = "debug-builtins")]
122         let mut has_native_debug_info = false;
123         let mut trap_data = 0..0;
124         let mut exception_data = 0..0;
125         let mut wasm_data = 0..0;
126         let mut address_map_data = 0..0;
127         let mut stack_map_data = 0..0;
128         let mut func_name_data = 0..0;
129         let mut info_data = 0..0;
130         let mut wasm_dwarf = 0..0;
131         for section in obj.sections() {
132             let data = section.data().map_err(obj::ObjectCrateErrorWrapper)?;
133             let name = section.name().map_err(obj::ObjectCrateErrorWrapper)?;
134             let range = subslice_range(data, &mmap);
135 
136             // Double-check that sections are all aligned properly.
137             if section.align() != 0 && data.len() != 0 {
138                 if (data.as_ptr() as u64 - mmap.as_ptr() as u64) % section.align() != 0 {
139                     bail!(
140                         "section `{}` isn't aligned to {:#x}",
141                         section.name().unwrap_or("ERROR"),
142                         section.align()
143                     );
144                 }
145             }
146 
147             match name {
148                 obj::ELF_WASM_BTI => match data.len() {
149                     1 => enable_branch_protection = Some(data[0] != 0),
150                     _ => bail!("invalid `{name}` section"),
151                 },
152                 ".text" => {
153                     text = range;
154 
155                     if let SectionFlags::Elf { sh_flags } = section.flags() {
156                         if sh_flags & obj::SH_WASMTIME_NOT_EXECUTED != 0 {
157                             needs_executable = false;
158                         }
159                     }
160 
161                     // Assert that Cranelift hasn't inserted any calls that need to be
162                     // relocated. We avoid using things like Cranelift's floor/ceil/etc.
163                     // operators in the Wasm-to-Cranelift translator specifically to
164                     // avoid having to do any relocations here. This also ensures that
165                     // all builtins use the same trampoline mechanism.
166                     assert!(section.relocations().next().is_none());
167                 }
168                 #[cfg(has_host_compiler_backend)]
169                 crate::runtime::vm::UnwindRegistration::SECTION_NAME => unwind = range,
170                 obj::ELF_WASM_DATA => wasm_data = range,
171                 obj::ELF_WASMTIME_ADDRMAP => address_map_data = range,
172                 obj::ELF_WASMTIME_STACK_MAP => stack_map_data = range,
173                 obj::ELF_WASMTIME_TRAPS => trap_data = range,
174                 obj::ELF_WASMTIME_EXCEPTIONS => exception_data = range,
175                 obj::ELF_NAME_DATA => func_name_data = range,
176                 obj::ELF_WASMTIME_INFO => info_data = range,
177                 obj::ELF_WASMTIME_DWARF => wasm_dwarf = range,
178                 #[cfg(feature = "debug-builtins")]
179                 ".debug_info" => has_native_debug_info = true,
180 
181                 _ => log::debug!("ignoring section {name}"),
182             }
183         }
184 
185         // require mutability even when this is turned off
186         #[cfg(not(has_host_compiler_backend))]
187         let _ = &mut unwind;
188 
189         // Ensure that the exception table is well-formed. This parser
190         // construction is cheap: it reads the header and validates
191         // ranges but nothing else. We do this only in debug-assertion
192         // builds because we otherwise require for safety that the
193         // compiled artifact is as-produced-by this version of
194         // Wasmtime, and we should always produce a correct exception
195         // table (i.e., we are not expecting untrusted data here).
196         if cfg!(debug_assertions) {
197             let _ = ExceptionTable::parse(&mmap[exception_data.clone()])?;
198         }
199 
200         Ok(Self {
201             mmap,
202             #[cfg(has_host_compiler_backend)]
203             unwind_registration: None,
204             #[cfg(feature = "debug-builtins")]
205             debug_registration: None,
206             published: false,
207             enable_branch_protection: enable_branch_protection
208                 .ok_or_else(|| anyhow!("missing `{}` section", obj::ELF_WASM_BTI))?,
209             needs_executable,
210             #[cfg(feature = "debug-builtins")]
211             has_native_debug_info,
212             custom_code_memory: engine.custom_code_memory().cloned(),
213             text,
214             unwind,
215             trap_data,
216             address_map_data,
217             stack_map_data,
218             exception_data,
219             func_name_data,
220             wasm_dwarf,
221             info_data,
222             wasm_data,
223         })
224     }
225 
226     /// Returns a reference to the underlying `MmapVec` this memory owns.
227     #[inline]
228     pub fn mmap(&self) -> &MmapVec {
229         &self.mmap
230     }
231 
232     /// Returns the contents of the text section of the ELF executable this
233     /// represents.
234     #[inline]
235     pub fn text(&self) -> &[u8] {
236         &self.mmap[self.text.clone()]
237     }
238 
239     /// Returns the contents of the `ELF_WASMTIME_DWARF` section.
240     #[inline]
241     pub fn wasm_dwarf(&self) -> &[u8] {
242         &self.mmap[self.wasm_dwarf.clone()]
243     }
244 
245     /// Returns the data in the `ELF_NAME_DATA` section.
246     #[inline]
247     pub fn func_name_data(&self) -> &[u8] {
248         &self.mmap[self.func_name_data.clone()]
249     }
250 
251     /// Returns the concatenated list of all data associated with this wasm
252     /// module.
253     ///
254     /// This is used for initialization of memories and all data ranges stored
255     /// in a `Module` are relative to the slice returned here.
256     #[inline]
257     pub fn wasm_data(&self) -> &[u8] {
258         &self.mmap[self.wasm_data.clone()]
259     }
260 
261     /// Returns the encoded address map section used to pass to
262     /// `wasmtime_environ::lookup_file_pos`.
263     #[inline]
264     pub fn address_map_data(&self) -> &[u8] {
265         &self.mmap[self.address_map_data.clone()]
266     }
267 
268     /// Returns the encoded stack map section used to pass to
269     /// `wasmtime_environ::StackMap::lookup`.
270     pub fn stack_map_data(&self) -> &[u8] {
271         &self.mmap[self.stack_map_data.clone()]
272     }
273 
274     /// Returns the encoded exception-tables section to pass to
275     /// `wasmtime_unwinder::ExceptionTable::parse`.
276     pub fn exception_tables(&self) -> &[u8] {
277         &self.mmap[self.exception_data.clone()]
278     }
279 
280     /// Returns the contents of the `ELF_WASMTIME_INFO` section, or an empty
281     /// slice if it wasn't found.
282     #[inline]
283     pub fn wasmtime_info(&self) -> &[u8] {
284         &self.mmap[self.info_data.clone()]
285     }
286 
287     /// Returns the contents of the `ELF_WASMTIME_TRAPS` section, or an empty
288     /// slice if it wasn't found.
289     #[inline]
290     pub fn trap_data(&self) -> &[u8] {
291         &self.mmap[self.trap_data.clone()]
292     }
293 
294     /// Publishes the internal ELF image to be ready for execution.
295     ///
296     /// This method can only be called once and will panic if called twice. This
297     /// will parse the ELF image from the original `MmapVec` and do everything
298     /// necessary to get it ready for execution, including:
299     ///
300     /// * Change page protections from read/write to read/execute.
301     /// * Register unwinding information with the OS
302     /// * Register this image with the debugger if native DWARF is present
303     ///
304     /// After this function executes all JIT code should be ready to execute.
305     pub fn publish(&mut self) -> Result<()> {
306         assert!(!self.published);
307         self.published = true;
308 
309         if self.text().is_empty() {
310             return Ok(());
311         }
312 
313         // The unsafety here comes from a few things:
314         //
315         // * We're actually updating some page protections to executable memory.
316         //
317         // * We're registering unwinding information which relies on the
318         //   correctness of the information in the first place. This applies to
319         //   both the actual unwinding tables as well as the validity of the
320         //   pointers we pass in itself.
321         unsafe {
322             // Next freeze the contents of this image by making all of the
323             // memory readonly. Nothing after this point should ever be modified
324             // so commit everything. For a compiled-in-memory image this will
325             // mean IPIs to evict writable mappings from other cores. For
326             // loaded-from-disk images this shouldn't result in IPIs so long as
327             // there weren't any relocations because nothing should have
328             // otherwise written to the image at any point either.
329             //
330             // Note that if virtual memory is disabled this is skipped because
331             // we aren't able to make it readonly, but this is just a
332             // defense-in-depth measure and isn't required for correctness.
333             #[cfg(has_virtual_memory)]
334             if self.mmap.supports_virtual_memory() {
335                 self.mmap.make_readonly(0..self.mmap.len())?;
336             }
337 
338             // Switch the executable portion from readonly to read/execute.
339             if self.needs_executable {
340                 if !self.custom_publish()? {
341                     if !self.mmap.supports_virtual_memory() {
342                         bail!("this target requires virtual memory to be enabled");
343                     }
344                     if !cfg!(feature = "std") {
345                         bail!(
346                             "with the `std` feature disabled at compile time \
347                              there must be a custom implementation of publishing \
348                              code memory"
349                         );
350                     }
351 
352                     #[cfg(all(has_virtual_memory, feature = "std"))]
353                     {
354                         let text = self.text();
355 
356                         use wasmtime_jit_icache_coherence as icache_coherence;
357 
358                         // Clear the newly allocated code from cache if the processor requires it
359                         //
360                         // Do this before marking the memory as R+X, technically we should be able to do it after
361                         // but there are some CPU's that have had errata about doing this with read only memory.
362                         icache_coherence::clear_cache(text.as_ptr().cast(), text.len())
363                             .expect("Failed cache clear");
364 
365                         self.mmap
366                             .make_executable(self.text.clone(), self.enable_branch_protection)
367                             .context("unable to make memory executable")?;
368 
369                         // Flush any in-flight instructions from the pipeline
370                         icache_coherence::pipeline_flush_mt().expect("Failed pipeline flush");
371                     }
372                 }
373             }
374 
375             // With all our memory set up use the platform-specific
376             // `UnwindRegistration` implementation to inform the general
377             // runtime that there's unwinding information available for all
378             // our just-published JIT functions.
379             self.register_unwind_info()?;
380 
381             #[cfg(feature = "debug-builtins")]
382             self.register_debug_image()?;
383         }
384 
385         Ok(())
386     }
387 
388     fn custom_publish(&mut self) -> Result<bool> {
389         if let Some(mem) = self.custom_code_memory.as_ref() {
390             let text = self.text();
391             // The text section should be aligned to
392             // `custom_code_memory.required_alignment()` due to a
393             // combination of two invariants:
394             //
395             // - MmapVec aligns its start address, even in owned-Vec mode; and
396             // - The text segment inside the ELF image will be aligned according
397             //   to the platform's requirements.
398             let text_addr = text.as_ptr() as usize;
399             assert_eq!(text_addr & (mem.required_alignment() - 1), 0);
400 
401             // The custom code memory handler will ensure the
402             // memory is executable and also handle icache
403             // coherence.
404             mem.publish_executable(text.as_ptr(), text.len())?;
405             Ok(true)
406         } else {
407             Ok(false)
408         }
409     }
410 
411     unsafe fn register_unwind_info(&mut self) -> Result<()> {
412         if self.unwind.len() == 0 {
413             return Ok(());
414         }
415         #[cfg(has_host_compiler_backend)]
416         {
417             let text = self.text();
418             let unwind_info = &self.mmap[self.unwind.clone()];
419             let registration = unsafe {
420                 crate::runtime::vm::UnwindRegistration::new(
421                     text.as_ptr(),
422                     unwind_info.as_ptr(),
423                     unwind_info.len(),
424                 )
425                 .context("failed to create unwind info registration")?
426             };
427             self.unwind_registration = Some(registration);
428             return Ok(());
429         }
430         #[cfg(not(has_host_compiler_backend))]
431         {
432             bail!("should not have unwind info for non-native backend")
433         }
434     }
435 
436     #[cfg(feature = "debug-builtins")]
437     fn register_debug_image(&mut self) -> Result<()> {
438         if !self.has_native_debug_info {
439             return Ok(());
440         }
441 
442         // TODO-DebugInfo: we're copying the whole image here, which is pretty wasteful.
443         // Use the existing memory by teaching code here about relocations in DWARF sections
444         // and anything else necessary that is done in "create_gdbjit_image" right now.
445         let image = self.mmap().to_vec();
446         let text: &[u8] = self.text();
447         let bytes = crate::debug::create_gdbjit_image(image, (text.as_ptr(), text.len()))?;
448         let reg = crate::runtime::vm::GdbJitImageRegistration::register(bytes);
449         self.debug_registration = Some(reg);
450         Ok(())
451     }
452 
453     /// Looks up the given offset within this module's text section and returns
454     /// the trap code associated with that instruction, if there is one.
455     pub fn lookup_trap_code(&self, text_offset: usize) -> Option<Trap> {
456         lookup_trap_code(self.trap_data(), text_offset)
457     }
458 }
459 
460 /// Returns the range of `inner` within `outer`, such that `outer[range]` is the
461 /// same as `inner`.
462 ///
463 /// This method requires that `inner` is a sub-slice of `outer`, and if that
464 /// isn't true then this method will panic.
465 fn subslice_range(inner: &[u8], outer: &[u8]) -> Range<usize> {
466     if inner.len() == 0 {
467         return 0..0;
468     }
469 
470     assert!(outer.as_ptr() <= inner.as_ptr());
471     assert!((&inner[inner.len() - 1] as *const _) <= (&outer[outer.len() - 1] as *const _));
472 
473     let start = inner.as_ptr() as usize - outer.as_ptr() as usize;
474     start..start + inner.len()
475 }
476