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::SectionIndex;
9 use object::read::elf::SectionTable;
10 use object::{
11     elf::{FileHeader64, SectionHeader64},
12     endian::Endianness,
13     read::elf::{FileHeader as _, SectionHeader as _},
14 };
15 use wasmtime_environ::{Trap, lookup_trap_code, obj};
16 use wasmtime_unwinder::ExceptionTable;
17 
18 /// Management of executable memory within a `MmapVec`
19 ///
20 /// This type consumes ownership of a region of memory and will manage the
21 /// executable permissions of the contained JIT code as necessary.
22 pub struct CodeMemory {
23     mmap: MmapVec,
24     #[cfg(has_host_compiler_backend)]
25     unwind_registration: Option<crate::runtime::vm::UnwindRegistration>,
26     #[cfg(feature = "debug-builtins")]
27     debug_registration: Option<crate::runtime::vm::GdbJitImageRegistration>,
28     published: bool,
29     registered: bool,
30     enable_branch_protection: bool,
31     needs_executable: bool,
32     #[cfg(feature = "debug-builtins")]
33     has_native_debug_info: bool,
34     custom_code_memory: Option<Arc<dyn CustomCodeMemory>>,
35 
36     // Ranges within `self.mmap` of where the particular sections lie.
37     text: Range<usize>,
38     unwind: Range<usize>,
39     trap_data: Range<usize>,
40     wasm_data: Range<usize>,
41     address_map_data: Range<usize>,
42     stack_map_data: Range<usize>,
43     exception_data: Range<usize>,
44     frame_tables_data: Range<usize>,
45     func_name_data: Range<usize>,
46     info_data: Range<usize>,
47     wasm_dwarf: Range<usize>,
48 }
49 
50 impl Drop for CodeMemory {
51     fn drop(&mut self) {
52         // If there is a custom code memory handler, restore the
53         // original (non-executable) state of the memory.
54         //
55         // We do this rather than invoking `unpublish()` because we
56         // want to skip the mprotect() if we natively own the mmap and
57         // are going to munmap soon anyway.
58         if let Some(mem) = self.custom_code_memory.as_ref() {
59             if self.published && self.needs_executable {
60                 let text = self.text();
61                 mem.unpublish_executable(text.as_ptr(), text.len())
62                     .expect("Executable memory unpublish failed");
63             }
64         }
65 
66         // Drop the registrations before `self.mmap` since they (implicitly) refer to it.
67         #[cfg(has_host_compiler_backend)]
68         let _ = self.unwind_registration.take();
69         #[cfg(feature = "debug-builtins")]
70         let _ = self.debug_registration.take();
71     }
72 }
73 
74 fn _assert() {
75     fn _assert_send_sync<T: Send + Sync>() {}
76     _assert_send_sync::<CodeMemory>();
77 }
78 
79 /// Interface implemented by an embedder to provide custom
80 /// implementations of code-memory protection and execute permissions.
81 pub trait CustomCodeMemory: Send + Sync {
82     /// The minimal alignment granularity for an address region that
83     /// can be made executable.
84     ///
85     /// Wasmtime does not assume the system page size for this because
86     /// custom code-memory protection can be used when all other uses
87     /// of virtual memory are disabled.
88     fn required_alignment(&self) -> usize;
89 
90     /// Publish a region of memory as executable.
91     ///
92     /// This should update permissions from the default RW
93     /// (readable/writable but not executable) to RX
94     /// (readable/executable but not writable), enforcing W^X
95     /// discipline.
96     ///
97     /// If the platform requires any data/instruction coherence
98     /// action, that should be performed as part of this hook as well.
99     ///
100     /// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as
101     /// per `required_alignment()`.
102     fn publish_executable(&self, ptr: *const u8, len: usize) -> crate::Result<()>;
103 
104     /// Unpublish a region of memory.
105     ///
106     /// This should perform the opposite effect of `make_executable`,
107     /// switching a range of memory back from RX (readable/executable)
108     /// to RW (readable/writable). It is guaranteed that no code is
109     /// running anymore from this region.
110     ///
111     /// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as
112     /// per `required_alignment()`.
113     fn unpublish_executable(&self, ptr: *const u8, len: usize) -> crate::Result<()>;
114 }
115 
116 impl CodeMemory {
117     /// Creates a new `CodeMemory` by taking ownership of the provided
118     /// `MmapVec`.
119     ///
120     /// The returned `CodeMemory` manages the internal `MmapVec` and the
121     /// `publish` method is used to actually make the memory executable.
122     pub fn new(engine: &Engine, mmap: MmapVec) -> Result<Self> {
123         let mmap_data = &*mmap;
124         let header = FileHeader64::<Endianness>::parse(mmap_data)
125             .map_err(obj::ObjectCrateErrorWrapper)
126             .context("failed to parse precompiled artifact as an ELF")?;
127         let endian = header
128             .endian()
129             .context("failed to parse header endianness")?;
130 
131         let section_headers = header
132             .section_headers(endian, mmap_data)
133             .context("failed to parse section headers")?;
134         let strings = header
135             .section_strings(endian, mmap_data, section_headers)
136             .context("failed to parse strings table")?;
137         let sections = header
138             .sections(endian, mmap_data)
139             .context("failed to parse sections table")?;
140 
141         let mut text = 0..0;
142         let mut unwind = 0..0;
143         let mut enable_branch_protection = None;
144         let mut needs_executable = true;
145         #[cfg(feature = "debug-builtins")]
146         let mut has_native_debug_info = false;
147         let mut trap_data = 0..0;
148         let mut exception_data = 0..0;
149         let mut frame_tables_data = 0..0;
150         let mut wasm_data = 0..0;
151         let mut address_map_data = 0..0;
152         let mut stack_map_data = 0..0;
153         let mut func_name_data = 0..0;
154         let mut info_data = 0..0;
155         let mut wasm_dwarf = 0..0;
156         for section_header in sections.iter() {
157             let data = section_header
158                 .data(endian, mmap_data)
159                 .map_err(obj::ObjectCrateErrorWrapper)?;
160             let name = section_name(endian, strings, section_header)?;
161             let range = subslice_range(data, &mmap);
162 
163             // Double-check that sections are all aligned properly.
164             let section_align = usize::try_from(section_header.sh_addralign(endian))?;
165             if section_align != 0 && data.len() != 0 {
166                 let section_offset = data.as_ptr().addr() - mmap.as_ptr().addr();
167                 ensure!(
168                     section_offset % section_align == 0,
169                     "section {name:?} isn't aligned to {section_align:#x}",
170                 );
171             }
172 
173             // Check that we don't have any relocations, which would make
174             // loading precompiled Wasm modules slower and also force them to
175             // get paged into memory from disk.
176             //
177             // We avoid using things like Cranelift's `floor`, `ceil`,
178             // etc... operators in the Wasm-to-CLIF translator specifically to
179             // avoid having to do any relocations here. This also ensures that
180             // all builtins use the same trampoline mechanism.
181             //
182             // We do, however, allow relocations in `.debug_*` DWARF sections.
183             if let Some(target_section) = reloc_section_target(&sections, section_header, endian)? {
184                 let target_name = section_name(endian, strings, target_section)?;
185                 ensure!(
186                     target_name.starts_with(".debug_"),
187                     "section {target_name:?} has unexpected relocations \
188                      (defined in section {name:?})",
189                 );
190             }
191 
192             match name {
193                 obj::ELF_WASM_BTI => match data.len() {
194                     1 => enable_branch_protection = Some(data[0] != 0),
195                     _ => bail!("invalid {name:?} section"),
196                 },
197                 ".text" => {
198                     text = range;
199 
200                     if section_header.sh_flags(endian) & obj::SH_WASMTIME_NOT_EXECUTED != 0 {
201                         needs_executable = false;
202                     }
203                 }
204                 #[cfg(has_host_compiler_backend)]
205                 crate::runtime::vm::UnwindRegistration::SECTION_NAME => unwind = range,
206                 obj::ELF_WASM_DATA => wasm_data = range,
207                 obj::ELF_WASMTIME_ADDRMAP => address_map_data = range,
208                 obj::ELF_WASMTIME_STACK_MAP => stack_map_data = range,
209                 obj::ELF_WASMTIME_TRAPS => trap_data = range,
210                 obj::ELF_WASMTIME_EXCEPTIONS => exception_data = range,
211                 obj::ELF_WASMTIME_FRAMES => frame_tables_data = range,
212                 obj::ELF_NAME_DATA => func_name_data = range,
213                 obj::ELF_WASMTIME_INFO => info_data = range,
214                 obj::ELF_WASMTIME_DWARF => wasm_dwarf = range,
215 
216                 #[cfg(feature = "debug-builtins")]
217                 ".debug_info" => has_native_debug_info = true,
218 
219                 // These sections are expected, but we do not need to retain any
220                 // info about them.
221                 "" | ".symtab" | ".strtab" | ".shstrtab" | ".xdata" | obj::ELF_WASM_ENGINE => {
222                     log::debug!("ignoring section {name:?}")
223                 }
224                 _ if name.starts_with(".debug_") || name.starts_with(".rela.debug_") => {
225                     log::debug!("ignoring debug section {name:?}")
226                 }
227 
228                 _ => bail!("unexpected section {name:?} in Wasm compilation artifact"),
229             }
230         }
231 
232         // Silence unused `mut` warning.
233         #[cfg(not(has_host_compiler_backend))]
234         let _ = &mut unwind;
235 
236         // Ensure that the exception table is well-formed. This parser
237         // construction is cheap: it reads the header and validates
238         // ranges but nothing else. We do this only in debug-assertion
239         // builds because we otherwise require for safety that the
240         // compiled artifact is as-produced-by this version of
241         // Wasmtime, and we should always produce a correct exception
242         // table (i.e., we are not expecting untrusted data here).
243         if cfg!(debug_assertions) {
244             let _ = ExceptionTable::parse(&mmap[exception_data.clone()])?;
245         }
246 
247         Ok(Self {
248             mmap,
249             #[cfg(has_host_compiler_backend)]
250             unwind_registration: None,
251             #[cfg(feature = "debug-builtins")]
252             debug_registration: None,
253             published: false,
254             registered: false,
255             enable_branch_protection: enable_branch_protection
256                 .ok_or_else(|| format_err!("missing `{}` section", obj::ELF_WASM_BTI))?,
257             needs_executable,
258             #[cfg(feature = "debug-builtins")]
259             has_native_debug_info,
260             custom_code_memory: engine.custom_code_memory().cloned(),
261             text,
262             unwind,
263             trap_data,
264             address_map_data,
265             stack_map_data,
266             exception_data,
267             frame_tables_data,
268             func_name_data,
269             wasm_dwarf,
270             info_data,
271             wasm_data,
272         })
273     }
274 
275     /// Returns a reference to the underlying `MmapVec` this memory owns.
276     #[inline]
277     pub fn mmap(&self) -> &MmapVec {
278         &self.mmap
279     }
280 
281     /// Returns the contents of the text section of the ELF executable this
282     /// represents.
283     #[inline]
284     pub fn text(&self) -> &[u8] {
285         &self.mmap[self.text.clone()]
286     }
287 
288     /// Returns the contents of the `ELF_WASMTIME_DWARF` section.
289     #[inline]
290     pub fn wasm_dwarf(&self) -> &[u8] {
291         &self.mmap[self.wasm_dwarf.clone()]
292     }
293 
294     /// Returns the data in the `ELF_NAME_DATA` section.
295     #[inline]
296     pub fn func_name_data(&self) -> &[u8] {
297         &self.mmap[self.func_name_data.clone()]
298     }
299 
300     /// Returns the concatenated list of all data associated with this wasm
301     /// module.
302     ///
303     /// This is used for initialization of memories and all data ranges stored
304     /// in a `Module` are relative to the slice returned here.
305     #[inline]
306     pub fn wasm_data(&self) -> &[u8] {
307         &self.mmap[self.wasm_data.clone()]
308     }
309 
310     /// Returns the encoded address map section used to pass to
311     /// `wasmtime_environ::lookup_file_pos`.
312     #[inline]
313     pub fn address_map_data(&self) -> &[u8] {
314         &self.mmap[self.address_map_data.clone()]
315     }
316 
317     /// Returns the encoded stack map section used to pass to
318     /// `wasmtime_environ::StackMap::lookup`.
319     pub fn stack_map_data(&self) -> &[u8] {
320         &self.mmap[self.stack_map_data.clone()]
321     }
322 
323     /// Returns the encoded exception-tables section to pass to
324     /// `wasmtime_unwinder::ExceptionTable::parse`.
325     pub fn exception_tables(&self) -> &[u8] {
326         &self.mmap[self.exception_data.clone()]
327     }
328 
329     /// Returns the encoded frame-tables section to pass to
330     /// `wasmtime_environ::FrameTable::parse`.
331     pub fn frame_tables(&self) -> &[u8] {
332         &self.mmap[self.frame_tables_data.clone()]
333     }
334 
335     /// Returns the contents of the `ELF_WASMTIME_INFO` section, or an empty
336     /// slice if it wasn't found.
337     #[inline]
338     pub fn wasmtime_info(&self) -> &[u8] {
339         &self.mmap[self.info_data.clone()]
340     }
341 
342     /// Returns the contents of the `ELF_WASMTIME_TRAPS` section, or an empty
343     /// slice if it wasn't found.
344     #[inline]
345     pub fn trap_data(&self) -> &[u8] {
346         &self.mmap[self.trap_data.clone()]
347     }
348 
349     /// Publishes the internal ELF image to be ready for execution.
350     ///
351     /// This method can only be when the image is not published (its
352     /// default state) and will panic if called when already
353     /// published. This will parse the ELF image from the original
354     /// `MmapVec` and do everything necessary to get it ready for
355     /// execution, including:
356     ///
357     /// * Change page protections from read/write to read/execute.
358     /// * Register unwinding information with the OS
359     /// * Register this image with the debugger if native DWARF is present
360     ///
361     /// After this function executes all JIT code should be ready to execute.
362     ///
363     /// The action may be reversed by calling [`Self::unpublish`], as long
364     /// as that method's safety requirements are upheld.
365     pub fn publish(&mut self) -> Result<()> {
366         assert!(!self.published);
367         self.published = true;
368 
369         if self.text().is_empty() {
370             return Ok(());
371         }
372 
373         // The unsafety here comes from a few things:
374         //
375         // * We're actually updating some page protections to executable memory.
376         //
377         // * We're registering unwinding information which relies on the
378         //   correctness of the information in the first place. This applies to
379         //   both the actual unwinding tables as well as the validity of the
380         //   pointers we pass in itself.
381         unsafe {
382             // Next freeze the contents of this image by making all of the
383             // memory readonly. Nothing after this point should ever be modified
384             // so commit everything. For a compiled-in-memory image this will
385             // mean IPIs to evict writable mappings from other cores. For
386             // loaded-from-disk images this shouldn't result in IPIs so long as
387             // there weren't any relocations because nothing should have
388             // otherwise written to the image at any point either.
389             //
390             // Note that if virtual memory is disabled this is skipped because
391             // we aren't able to make it readonly, but this is just a
392             // defense-in-depth measure and isn't required for correctness.
393             #[cfg(has_virtual_memory)]
394             if self.mmap.supports_virtual_memory() {
395                 self.mmap.make_readonly(0..self.mmap.len())?;
396             }
397 
398             // Switch the executable portion from readonly to read/execute.
399             if self.needs_executable {
400                 if !self.custom_publish()? {
401                     if !self.mmap.supports_virtual_memory() {
402                         bail!("this target requires virtual memory to be enabled");
403                     }
404                     #[cfg(has_virtual_memory)]
405                     self.mmap
406                         .make_executable(self.text.clone(), self.enable_branch_protection)
407                         .context("unable to make memory executable")?;
408                 }
409             }
410 
411             if !self.registered {
412                 // With all our memory set up use the platform-specific
413                 // `UnwindRegistration` implementation to inform the general
414                 // runtime that there's unwinding information available for all
415                 // our just-published JIT functions.
416                 self.register_unwind_info()?;
417 
418                 #[cfg(feature = "debug-builtins")]
419                 self.register_debug_image()?;
420                 self.registered = true;
421             }
422         }
423 
424         Ok(())
425     }
426 
427     fn custom_publish(&mut self) -> Result<bool> {
428         if let Some(mem) = self.custom_code_memory.as_ref() {
429             let text = self.text();
430             // The text section should be aligned to
431             // `custom_code_memory.required_alignment()` due to a
432             // combination of two invariants:
433             //
434             // - MmapVec aligns its start address, even in owned-Vec mode; and
435             // - The text segment inside the ELF image will be aligned according
436             //   to the platform's requirements.
437             let text_addr = text.as_ptr() as usize;
438             assert_eq!(text_addr & (mem.required_alignment() - 1), 0);
439 
440             // The custom code memory handler will ensure the
441             // memory is executable and also handle icache
442             // coherence.
443             mem.publish_executable(text.as_ptr(), text.len())?;
444             Ok(true)
445         } else {
446             Ok(false)
447         }
448     }
449 
450     /// "Unpublish" code memory (transition it from executable to read/writable).
451     ///
452     /// This may be used to edit the code image, as long as the
453     /// overall size of the memory remains the same. Note the hazards
454     /// inherent in editing code that may have been executed: any
455     /// stack frames with PC still active in this code must be
456     /// suspended (e.g., called into a hostcall that is then invoking
457     /// this method, or async-yielded) and any active PC values must
458     /// point to valid instructions. Thus this is mostly useful for
459     /// patching in-place at particular sites, such as by the use of
460     /// Cranelift's `patchable_call` instruction.
461     ///
462     /// If this fails, then the memory remains executable.
463     pub fn unpublish(&mut self) -> Result<()> {
464         assert!(self.published);
465         self.published = false;
466 
467         if self.text().is_empty() {
468             return Ok(());
469         }
470 
471         if self.custom_unpublish()? {
472             return Ok(());
473         }
474 
475         if !self.mmap.supports_virtual_memory() {
476             bail!("this target requires virtual memory to be enabled");
477         }
478 
479         // SAFETY: we are guaranteed by our own safety conditions that
480         // we have exclusive access to this code and can change its
481         // permissions (removing the execute bit) without causing
482         // problems.
483         #[cfg(has_virtual_memory)]
484         unsafe {
485             self.mmap.make_readwrite(0..self.mmap.len())?;
486         }
487 
488         // Note that we do *not* unregister: we expect unpublish
489         // to be used for temporary edits, so we want the
490         // registration to "stick" after the initial publish and
491         // not toggle in subsequent unpublish/publish cycles.
492 
493         Ok(())
494     }
495 
496     fn custom_unpublish(&mut self) -> Result<bool> {
497         if let Some(mem) = self.custom_code_memory.as_ref() {
498             let text = self.text();
499             mem.unpublish_executable(text.as_ptr(), text.len())?;
500             Ok(true)
501         } else {
502             Ok(false)
503         }
504     }
505 
506     /// Return a mutable borrow to the code, suitable for editing.
507     ///
508     /// Must not be published.
509     ///
510     /// # Panics
511     ///
512     /// This method panics if the code has been published (and not
513     /// subsequently unpublished).
514     pub fn text_mut(&mut self) -> &mut [u8] {
515         assert!(!self.published);
516         // SAFETY: we assert !published, which means we either have
517         // not yet applied readonly + execute permissinos, or we have
518         // undone that and flipped back to read-write via unpublish.
519         unsafe { &mut self.mmap.as_mut_slice()[self.text.clone()] }
520     }
521 
522     unsafe fn register_unwind_info(&mut self) -> Result<()> {
523         if self.unwind.len() == 0 {
524             return Ok(());
525         }
526         #[cfg(has_host_compiler_backend)]
527         {
528             let text = self.text();
529             let unwind_info = &self.mmap[self.unwind.clone()];
530             let registration = unsafe {
531                 crate::runtime::vm::UnwindRegistration::new(
532                     text.as_ptr(),
533                     unwind_info.as_ptr(),
534                     unwind_info.len(),
535                 )
536                 .context("failed to create unwind info registration")?
537             };
538             self.unwind_registration = Some(registration);
539             return Ok(());
540         }
541         #[cfg(not(has_host_compiler_backend))]
542         {
543             bail!("should not have unwind info for non-native backend")
544         }
545     }
546 
547     #[cfg(feature = "debug-builtins")]
548     fn register_debug_image(&mut self) -> Result<()> {
549         if !self.has_native_debug_info {
550             return Ok(());
551         }
552 
553         // TODO-DebugInfo: we're copying the whole image here, which is pretty wasteful.
554         // Use the existing memory by teaching code here about relocations in DWARF sections
555         // and anything else necessary that is done in "create_gdbjit_image" right now.
556         let image = self.mmap().to_vec();
557         let text: &[u8] = self.text();
558         let bytes = crate::native_debug::create_gdbjit_image(image, (text.as_ptr(), text.len()))?;
559         let reg = crate::runtime::vm::GdbJitImageRegistration::register(bytes);
560         self.debug_registration = Some(reg);
561         Ok(())
562     }
563 
564     /// Looks up the given offset within this module's text section and returns
565     /// the trap code associated with that instruction, if there is one.
566     pub fn lookup_trap_code(&self, text_offset: usize) -> Option<Trap> {
567         lookup_trap_code(self.trap_data(), text_offset)
568     }
569 
570     /// Get the raw address range of this CodeMemory.
571     pub(crate) fn raw_addr_range(&self) -> Range<usize> {
572         let start = self.text().as_ptr().addr();
573         let end = start + self.text().len();
574         start..end
575     }
576 
577     /// Create a "deep clone": a separate CodeMemory for the same code
578     /// that can be patched or mutated independently. Also returns a
579     /// "metadata and location" handle that can be registered with the
580     /// global module registry and used for trap metadata lookups.
581     #[cfg(feature = "debug")]
582     pub(crate) fn deep_clone(self: &Arc<Self>, engine: &Engine) -> Result<CodeMemory> {
583         let mmap = self.mmap.deep_clone()?;
584         Self::new(engine, mmap)
585     }
586 }
587 
588 fn section_name<'a>(
589     endian: Endianness,
590     strings: object::StringTable<'a>,
591     section_header: &SectionHeader64<Endianness>,
592 ) -> Result<&'a str> {
593     let name = section_header
594         .name(endian, strings)
595         .map_err(obj::ObjectCrateErrorWrapper)?;
596     Ok(str::from_utf8(name).context("invalid section name in Wasm compilation artifact")?)
597 }
598 
599 fn is_reloc_section(section_header: &SectionHeader64<Endianness>, endian: Endianness) -> bool {
600     let sh_type = section_header.sh_type(endian);
601     matches!(
602         sh_type,
603         object::elf::SHT_REL | object::elf::SHT_RELA | object::elf::SHT_CREL
604     )
605 }
606 
607 fn reloc_section_target<'a>(
608     sections: &'a SectionTable<'a, FileHeader64<Endianness>, &'a [u8]>,
609     section: &'a SectionHeader64<Endianness>,
610     endian: Endianness,
611 ) -> Result<Option<&'a SectionHeader64<Endianness>>> {
612     if !is_reloc_section(&section, endian) {
613         return Ok(None);
614     }
615 
616     let sh_info = section.info_link(endian);
617 
618     // Dynamic relocation.
619     if sh_info == SectionIndex(0) {
620         return Ok(None);
621     }
622 
623     ensure!(
624         sh_info.0 < sections.len(),
625         "invalid ELF `sh_info` for relocation section",
626     );
627 
628     Ok(Some(sections.section(sh_info)?))
629 }
630 
631 /// Returns the range of `inner` within `outer`, such that `outer[range]` is the
632 /// same as `inner`.
633 ///
634 /// This method requires that `inner` is a sub-slice of `outer`, and if that
635 /// isn't true then this method will panic.
636 fn subslice_range(inner: &[u8], outer: &[u8]) -> Range<usize> {
637     if inner.len() == 0 {
638         return 0..0;
639     }
640 
641     assert!(outer.as_ptr() <= inner.as_ptr());
642     assert!((&inner[inner.len() - 1] as *const _) <= (&outer[outer.len() - 1] as *const _));
643 
644     let start = inner.as_ptr() as usize - outer.as_ptr() as usize;
645     start..start + inner.len()
646 }
647