1 //! This module implements serialization and deserialization of `Engine`
2 //! configuration data which is embedded into compiled artifacts of Wasmtime.
3 //!
4 //! The data serialized here is used to double-check that when a module is
5 //! loaded from one host onto another that it's compatible with the target host.
6 //! Additionally though this data is the first data read from a precompiled
7 //! artifact so it's "extra hardened" to provide reasonable-ish error messages
8 //! for mismatching wasmtime versions. Once something successfully deserializes
9 //! here it's assumed it's meant for this wasmtime so error messages are in
10 //! general much worse afterwards.
11 //!
12 //! Wasmtime AOT artifacts are ELF files so the data for the engine here is
13 //! stored into a section of the output file. The structure of this section is:
14 //!
15 //! 1. A version byte, currently `VERSION`.
16 //! 2. A byte indicating how long the next field is.
17 //! 3. A version string of the length of the previous byte value.
18 //! 4. A `bincode`-encoded `Metadata` structure.
19 //!
20 //! This is hoped to help distinguish easily Wasmtime-based ELF files from
21 //! other random ELF files, as well as provide better error messages for
22 //! using wasmtime artifacts across versions.
23 
24 use crate::{Engine, ModuleVersionStrategy};
25 use anyhow::{anyhow, bail, Context, Result};
26 use object::write::{Object, StandardSegment};
27 use object::{File, FileFlags, Object as _, ObjectSection, SectionKind};
28 use serde::{Deserialize, Serialize};
29 use std::collections::BTreeMap;
30 use std::str::FromStr;
31 use wasmtime_environ::obj;
32 use wasmtime_environ::{FlagValue, ObjectKind, Tunables};
33 use wasmtime_runtime::MmapVec;
34 
35 const VERSION: u8 = 0;
36 
37 /// Produces a blob of bytes by serializing the `engine`'s configuration data to
38 /// be checked, perhaps in a different process, with the `check_compatible`
39 /// method below.
40 ///
41 /// The blob of bytes is inserted into the object file specified to become part
42 /// of the final compiled artifact.
43 #[cfg(compiler)]
44 pub fn append_compiler_info(engine: &Engine, obj: &mut Object<'_>) {
45     let section = obj.add_section(
46         obj.segment_name(StandardSegment::Data).to_vec(),
47         obj::ELF_WASM_ENGINE.as_bytes().to_vec(),
48         SectionKind::ReadOnlyData,
49     );
50     let mut data = Vec::new();
51     data.push(VERSION);
52     let version = match &engine.config().module_version {
53         ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION"),
54         ModuleVersionStrategy::Custom(c) => c,
55         ModuleVersionStrategy::None => "",
56     };
57     // This precondition is checked in Config::module_version:
58     assert!(
59         version.len() < 256,
60         "package version must be less than 256 bytes"
61     );
62     data.push(version.len() as u8);
63     data.extend_from_slice(version.as_bytes());
64     bincode::serialize_into(&mut data, &Metadata::new(engine)).unwrap();
65     obj.set_section_data(section, data, 1);
66 }
67 
68 /// Verifies that the serialized engine in `mmap` is compatible with the
69 /// `engine` provided.
70 ///
71 /// This function will verify that the `mmap` provided can be deserialized
72 /// successfully and that the contents are all compatible with the `engine`
73 /// provided here, notably compatible wasm features are enabled, compatible
74 /// compiler options, etc. If a mismatch is found and the compilation metadata
75 /// specified is incompatible then an error is returned.
76 pub fn check_compatible(engine: &Engine, mmap: &MmapVec, expected: ObjectKind) -> Result<()> {
77     // Parse the input `mmap` as an ELF file and see if the header matches the
78     // Wasmtime-generated header. This includes a Wasmtime-specific `os_abi` and
79     // the `e_flags` field should indicate whether `expected` matches or not.
80     //
81     // Note that errors generated here could mean that a precompiled module was
82     // loaded as a component, or vice versa, both of which aren't supposed to
83     // work.
84     //
85     // Ideally we'd only `File::parse` once and avoid the linear
86     // `section_by_name` search here but the general serialization code isn't
87     // structured well enough to make this easy and additionally it's not really
88     // a perf issue right now so doing that is left for another day's
89     // refactoring.
90     let obj = File::parse(&mmap[..]).context("failed to parse precompiled artifact as an ELF")?;
91     let expected_e_flags = match expected {
92         ObjectKind::Module => obj::EF_WASMTIME_MODULE,
93         ObjectKind::Component => obj::EF_WASMTIME_COMPONENT,
94     };
95     match obj.flags() {
96         FileFlags::Elf {
97             os_abi: obj::ELFOSABI_WASMTIME,
98             abi_version: 0,
99             e_flags,
100         } if e_flags == expected_e_flags => {}
101         _ => bail!("incompatible object file format"),
102     }
103 
104     let data = obj
105         .section_by_name(obj::ELF_WASM_ENGINE)
106         .ok_or_else(|| anyhow!("failed to find section `{}`", obj::ELF_WASM_ENGINE))?
107         .data()?;
108     let (first, data) = data
109         .split_first()
110         .ok_or_else(|| anyhow!("invalid engine section"))?;
111     if *first != VERSION {
112         bail!("mismatched version in engine section");
113     }
114     let (len, data) = data
115         .split_first()
116         .ok_or_else(|| anyhow!("invalid engine section"))?;
117     let len = usize::from(*len);
118     let (version, data) = if data.len() < len + 1 {
119         bail!("engine section too small")
120     } else {
121         data.split_at(len)
122     };
123 
124     match &engine.config().module_version {
125         ModuleVersionStrategy::WasmtimeVersion => {
126             let version = std::str::from_utf8(version)?;
127             if version != env!("CARGO_PKG_VERSION") {
128                 bail!(
129                     "Module was compiled with incompatible Wasmtime version '{}'",
130                     version
131                 );
132             }
133         }
134         ModuleVersionStrategy::Custom(v) => {
135             let version = std::str::from_utf8(&version)?;
136             if version != v {
137                 bail!(
138                     "Module was compiled with incompatible version '{}'",
139                     version
140                 );
141             }
142         }
143         ModuleVersionStrategy::None => { /* ignore the version info, accept all */ }
144     }
145     bincode::deserialize::<Metadata>(data)?.check_compatible(engine)
146 }
147 
148 #[derive(Serialize, Deserialize)]
149 struct Metadata {
150     target: String,
151     shared_flags: BTreeMap<String, FlagValue>,
152     isa_flags: BTreeMap<String, FlagValue>,
153     tunables: Tunables,
154     features: WasmFeatures,
155 }
156 
157 // This exists because `wasmparser::WasmFeatures` isn't serializable
158 #[derive(Debug, Copy, Clone, Serialize, Deserialize)]
159 struct WasmFeatures {
160     reference_types: bool,
161     multi_value: bool,
162     bulk_memory: bool,
163     component_model: bool,
164     simd: bool,
165     threads: bool,
166     tail_call: bool,
167     multi_memory: bool,
168     exceptions: bool,
169     memory64: bool,
170     relaxed_simd: bool,
171     extended_const: bool,
172 }
173 
174 impl Metadata {
175     #[cfg(compiler)]
176     fn new(engine: &Engine) -> Metadata {
177         let wasmparser::WasmFeatures {
178             reference_types,
179             multi_value,
180             bulk_memory,
181             component_model,
182             simd,
183             threads,
184             tail_call,
185             multi_memory,
186             exceptions,
187             memory64,
188             relaxed_simd,
189             extended_const,
190 
191             // Always on; we don't currently have knobs for these.
192             mutable_global: _,
193             saturating_float_to_int: _,
194             sign_extension: _,
195             floats: _,
196         } = engine.config().features;
197 
198         Metadata {
199             target: engine.compiler().triple().to_string(),
200             shared_flags: engine.compiler().flags(),
201             isa_flags: engine.compiler().isa_flags(),
202             tunables: engine.config().tunables.clone(),
203             features: WasmFeatures {
204                 reference_types,
205                 multi_value,
206                 bulk_memory,
207                 component_model,
208                 simd,
209                 threads,
210                 tail_call,
211                 multi_memory,
212                 exceptions,
213                 memory64,
214                 relaxed_simd,
215                 extended_const,
216             },
217         }
218     }
219 
220     fn check_compatible(mut self, engine: &Engine) -> Result<()> {
221         self.check_triple(engine)?;
222         self.check_shared_flags(engine)?;
223         self.check_isa_flags(engine)?;
224         self.check_tunables(&engine.config().tunables)?;
225         self.check_features(&engine.config().features)?;
226         Ok(())
227     }
228 
229     fn check_triple(&self, engine: &Engine) -> Result<()> {
230         let engine_target = engine.target();
231         let module_target =
232             target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?;
233 
234         if module_target.architecture != engine_target.architecture {
235             bail!(
236                 "Module was compiled for architecture '{}'",
237                 module_target.architecture
238             );
239         }
240 
241         if module_target.operating_system != engine_target.operating_system {
242             bail!(
243                 "Module was compiled for operating system '{}'",
244                 module_target.operating_system
245             );
246         }
247 
248         Ok(())
249     }
250 
251     fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> {
252         for (name, val) in self.shared_flags.iter() {
253             engine
254                 .check_compatible_with_shared_flag(name, val)
255                 .map_err(|s| anyhow::Error::msg(s))
256                 .context("compilation settings of module incompatible with native host")?;
257         }
258         Ok(())
259     }
260 
261     fn check_isa_flags(&mut self, engine: &Engine) -> Result<()> {
262         for (name, val) in self.isa_flags.iter() {
263             engine
264                 .check_compatible_with_isa_flag(name, val)
265                 .map_err(|s| anyhow::Error::msg(s))
266                 .context("compilation settings of module incompatible with native host")?;
267         }
268         Ok(())
269     }
270 
271     fn check_int<T: Eq + std::fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> {
272         if found == expected {
273             return Ok(());
274         }
275 
276         bail!(
277             "Module was compiled with a {} of '{}' but '{}' is expected for the host",
278             feature,
279             found,
280             expected
281         );
282     }
283 
284     fn check_bool(found: bool, expected: bool, feature: &str) -> Result<()> {
285         if found == expected {
286             return Ok(());
287         }
288 
289         bail!(
290             "Module was compiled {} {} but it {} enabled for the host",
291             if found { "with" } else { "without" },
292             feature,
293             if expected { "is" } else { "is not" }
294         );
295     }
296 
297     fn check_tunables(&mut self, other: &Tunables) -> Result<()> {
298         let Tunables {
299             static_memory_bound,
300             static_memory_offset_guard_size,
301             dynamic_memory_offset_guard_size,
302             generate_native_debuginfo,
303             parse_wasm_debuginfo,
304             consume_fuel,
305             epoch_interruption,
306             static_memory_bound_is_maximum,
307             guard_before_linear_memory,
308 
309             // This doesn't affect compilation, it's just a runtime setting.
310             dynamic_memory_growth_reserve: _,
311 
312             // This does technically affect compilation but modules with/without
313             // trap information can be loaded into engines with the opposite
314             // setting just fine (it's just a section in the compiled file and
315             // whether it's present or not)
316             generate_address_map: _,
317 
318             // Just a debugging aid, doesn't affect functionality at all.
319             debug_adapter_modules: _,
320         } = self.tunables;
321 
322         Self::check_int(
323             static_memory_bound,
324             other.static_memory_bound,
325             "static memory bound",
326         )?;
327         Self::check_int(
328             static_memory_offset_guard_size,
329             other.static_memory_offset_guard_size,
330             "static memory guard size",
331         )?;
332         Self::check_int(
333             dynamic_memory_offset_guard_size,
334             other.dynamic_memory_offset_guard_size,
335             "dynamic memory guard size",
336         )?;
337         Self::check_bool(
338             generate_native_debuginfo,
339             other.generate_native_debuginfo,
340             "debug information support",
341         )?;
342         Self::check_bool(
343             parse_wasm_debuginfo,
344             other.parse_wasm_debuginfo,
345             "WebAssembly backtrace support",
346         )?;
347         Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
348         Self::check_bool(
349             epoch_interruption,
350             other.epoch_interruption,
351             "epoch interruption",
352         )?;
353         Self::check_bool(
354             static_memory_bound_is_maximum,
355             other.static_memory_bound_is_maximum,
356             "pooling allocation support",
357         )?;
358         Self::check_bool(
359             guard_before_linear_memory,
360             other.guard_before_linear_memory,
361             "guard before linear memory",
362         )?;
363 
364         Ok(())
365     }
366 
367     fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> {
368         let WasmFeatures {
369             reference_types,
370             multi_value,
371             bulk_memory,
372             component_model,
373             simd,
374             threads,
375             tail_call,
376             multi_memory,
377             exceptions,
378             memory64,
379             relaxed_simd,
380             extended_const,
381         } = self.features;
382 
383         Self::check_bool(
384             reference_types,
385             other.reference_types,
386             "WebAssembly reference types support",
387         )?;
388         Self::check_bool(
389             multi_value,
390             other.multi_value,
391             "WebAssembly multi-value support",
392         )?;
393         Self::check_bool(
394             bulk_memory,
395             other.bulk_memory,
396             "WebAssembly bulk memory support",
397         )?;
398         Self::check_bool(
399             component_model,
400             other.component_model,
401             "WebAssembly component model support",
402         )?;
403         Self::check_bool(simd, other.simd, "WebAssembly SIMD support")?;
404         Self::check_bool(threads, other.threads, "WebAssembly threads support")?;
405         Self::check_bool(tail_call, other.tail_call, "WebAssembly tail-call support")?;
406         Self::check_bool(
407             multi_memory,
408             other.multi_memory,
409             "WebAssembly multi-memory support",
410         )?;
411         Self::check_bool(
412             exceptions,
413             other.exceptions,
414             "WebAssembly exceptions support",
415         )?;
416         Self::check_bool(
417             memory64,
418             other.memory64,
419             "WebAssembly 64-bit memory support",
420         )?;
421         Self::check_bool(
422             extended_const,
423             other.extended_const,
424             "WebAssembly extended-const support",
425         )?;
426         Self::check_bool(
427             relaxed_simd,
428             other.relaxed_simd,
429             "WebAssembly relaxed-simd support",
430         )?;
431 
432         Ok(())
433     }
434 }
435 
436 #[cfg(test)]
437 mod test {
438     use super::*;
439     use crate::Config;
440 
441     #[test]
442     fn test_architecture_mismatch() -> Result<()> {
443         let engine = Engine::default();
444         let mut metadata = Metadata::new(&engine);
445         metadata.target = "unknown-generic-linux".to_string();
446 
447         match metadata.check_compatible(&engine) {
448             Ok(_) => unreachable!(),
449             Err(e) => assert_eq!(
450                 e.to_string(),
451                 "Module was compiled for architecture 'unknown'",
452             ),
453         }
454 
455         Ok(())
456     }
457 
458     #[test]
459     fn test_os_mismatch() -> Result<()> {
460         let engine = Engine::default();
461         let mut metadata = Metadata::new(&engine);
462 
463         metadata.target = format!(
464             "{}-generic-unknown",
465             target_lexicon::Triple::host().architecture
466         );
467 
468         match metadata.check_compatible(&engine) {
469             Ok(_) => unreachable!(),
470             Err(e) => assert_eq!(
471                 e.to_string(),
472                 "Module was compiled for operating system 'unknown'",
473             ),
474         }
475 
476         Ok(())
477     }
478 
479     #[test]
480     fn test_cranelift_flags_mismatch() -> Result<()> {
481         let engine = Engine::default();
482         let mut metadata = Metadata::new(&engine);
483 
484         metadata
485             .shared_flags
486             .insert("avoid_div_traps".to_string(), FlagValue::Bool(false));
487 
488         match metadata.check_compatible(&engine) {
489             Ok(_) => unreachable!(),
490             Err(e) => assert!(format!("{:?}", e).starts_with(
491                 "\
492 compilation settings of module incompatible with native host
493 
494 Caused by:
495     setting \"avoid_div_traps\" is configured to Bool(false) which is not supported"
496             )),
497         }
498 
499         Ok(())
500     }
501 
502     #[test]
503     fn test_isa_flags_mismatch() -> Result<()> {
504         let engine = Engine::default();
505         let mut metadata = Metadata::new(&engine);
506 
507         metadata
508             .isa_flags
509             .insert("not_a_flag".to_string(), FlagValue::Bool(true));
510 
511         match metadata.check_compatible(&engine) {
512             Ok(_) => unreachable!(),
513             Err(e) => assert!(format!("{:?}", e).starts_with(
514                 "\
515 compilation settings of module incompatible with native host
516 
517 Caused by:
518     cannot test if target-specific flag \"not_a_flag\" is available at runtime",
519             )),
520         }
521 
522         Ok(())
523     }
524 
525     #[test]
526     fn test_tunables_int_mismatch() -> Result<()> {
527         let engine = Engine::default();
528         let mut metadata = Metadata::new(&engine);
529 
530         metadata.tunables.static_memory_offset_guard_size = 0;
531 
532         match metadata.check_compatible(&engine) {
533             Ok(_) => unreachable!(),
534             Err(e) => assert_eq!(e.to_string(), "Module was compiled with a static memory guard size of '0' but '2147483648' is expected for the host"),
535         }
536 
537         Ok(())
538     }
539 
540     #[test]
541     fn test_tunables_bool_mismatch() -> Result<()> {
542         let mut config = Config::new();
543         config.epoch_interruption(true);
544 
545         let engine = Engine::new(&config)?;
546         let mut metadata = Metadata::new(&engine);
547         metadata.tunables.epoch_interruption = false;
548 
549         match metadata.check_compatible(&engine) {
550             Ok(_) => unreachable!(),
551             Err(e) => assert_eq!(
552                 e.to_string(),
553                 "Module was compiled without epoch interruption but it is enabled for the host"
554             ),
555         }
556 
557         let mut config = Config::new();
558         config.epoch_interruption(false);
559 
560         let engine = Engine::new(&config)?;
561         let mut metadata = Metadata::new(&engine);
562         metadata.tunables.epoch_interruption = true;
563 
564         match metadata.check_compatible(&engine) {
565             Ok(_) => unreachable!(),
566             Err(e) => assert_eq!(
567                 e.to_string(),
568                 "Module was compiled with epoch interruption but it is not enabled for the host"
569             ),
570         }
571 
572         Ok(())
573     }
574 
575     #[test]
576     fn test_feature_mismatch() -> Result<()> {
577         let mut config = Config::new();
578         config.wasm_simd(true);
579 
580         let engine = Engine::new(&config)?;
581         let mut metadata = Metadata::new(&engine);
582         metadata.features.simd = false;
583 
584         match metadata.check_compatible(&engine) {
585             Ok(_) => unreachable!(),
586             Err(e) => assert_eq!(e.to_string(), "Module was compiled without WebAssembly SIMD support but it is enabled for the host"),
587         }
588 
589         let mut config = Config::new();
590         config.wasm_simd(false);
591 
592         let engine = Engine::new(&config)?;
593         let mut metadata = Metadata::new(&engine);
594         metadata.features.simd = true;
595 
596         match metadata.check_compatible(&engine) {
597             Ok(_) => unreachable!(),
598             Err(e) => assert_eq!(e.to_string(), "Module was compiled with WebAssembly SIMD support but it is not enabled for the host"),
599         }
600 
601         Ok(())
602     }
603 }
604