wasmer_wasm_interface/
validate.rs

1//! Validate a wasm module given a interface.
2//!
3//! This checks that all imports are specified in the interface and that their types
4//! are correct, as well as that all exports that the interface expects are exported
5//! by the module and that their types are correct.
6
7use crate::{Export, Import, Interface, WasmType};
8use std::collections::HashMap;
9use wasmparser::{CompositeType, ExternalKind, FuncType, GlobalType, Payload, TypeRef};
10
11pub fn validate_wasm_and_report_errors(
12    wasm: &[u8],
13    interface: &Interface,
14) -> Result<(), WasmValidationError> {
15    let mut errors: Vec<String> = vec![];
16    let mut import_fns: HashMap<(String, String), u32> = HashMap::new();
17    let mut export_fns: HashMap<String, u32> = HashMap::new();
18    let mut export_globals: HashMap<String, u32> = HashMap::new();
19    let mut type_defs: Vec<FuncType> = vec![];
20    let mut global_types: Vec<GlobalType> = vec![];
21    let mut fn_sigs: Vec<u32> = vec![];
22
23    let mut val = wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
24        threads: true,
25        reference_types: true,
26        simd: true,
27        bulk_memory: true,
28        multi_value: true,
29        ..Default::default()
30    });
31
32    val.validate_all(wasm)
33        .map_err(|e| WasmValidationError::InvalidWasm {
34            error: format!("{}", e),
35        })?;
36
37    let parser = wasmparser::Parser::new(0).parse_all(wasm);
38
39    for res in parser {
40        let payload = res.map_err(|e| WasmValidationError::InvalidWasm {
41            error: format!("{}", e),
42        })?;
43        match payload {
44            Payload::End(_) => break,
45            Payload::ImportSection(reader) => {
46                for item in reader.into_iter_with_offsets() {
47                    let (_offset, import) = item.map_err(|e| WasmValidationError::InvalidWasm {
48                        error: format!("{}", e),
49                    })?;
50
51                    match import.ty {
52                        TypeRef::Func(idx) => {
53                            import_fns.insert(Import::format_key(import.module, import.name), idx);
54                            fn_sigs.push(idx);
55                        }
56                        TypeRef::Global(GlobalType { content_type, .. }) => {
57                            let global_type = wasmparser_type_into_wasm_type(content_type)
58                                .map_err(|err| WasmValidationError::UnsupportedType {
59                                    error: format!(
60                                        "Invalid type found in import \"{}\" \"{}\": {}",
61                                        import.module, import.name, err
62                                    ),
63                                })?;
64                            if let Some(val) = interface
65                                .imports
66                                .get(&Import::format_key(import.module, import.name))
67                            {
68                                if let Import::Global { var_type, .. } = val {
69                                    if *var_type != global_type {
70                                        errors.push(format!(
71                                            "Invalid type on Global \"{}\". Expected {} found {}",
72                                            import.name, var_type, global_type
73                                        ));
74                                    }
75                                } else {
76                                    errors.push(format!(
77                                        "Invalid import type. Expected Global, found {:?}",
78                                        val
79                                    ));
80                                }
81                            } else {
82                                errors.push(format!(
83                                    "Global import \"{}\" not found in the specified interface",
84                                    import.name
85                                ));
86                            }
87                        }
88                        _ => (),
89                    }
90                }
91            }
92            Payload::ExportSection(reader) => {
93                for res in reader.into_iter() {
94                    let export = res.map_err(|e| WasmValidationError::InvalidWasm {
95                        error: format!("{}", e),
96                    })?;
97
98                    match export.kind {
99                        ExternalKind::Func => {
100                            export_fns.insert(Export::format_key(export.name), export.index);
101                        }
102                        ExternalKind::Global => {
103                            export_globals.insert(Export::format_key(export.name), export.index);
104                        }
105                        _ => (),
106                    }
107                }
108            }
109            Payload::GlobalSection(reader) => {
110                for res in reader.into_iter() {
111                    let global = res.map_err(|e| WasmValidationError::InvalidWasm {
112                        error: format!("{}", e),
113                    })?;
114
115                    global_types.push(global.ty);
116                }
117            }
118            Payload::TypeSection(reader) => {
119                for res in reader.into_iter() {
120                    let group = res.map_err(|e| WasmValidationError::InvalidWasm {
121                        error: format!("{}", e),
122                    })?;
123
124                    for ty in group.into_types() {
125                        if let CompositeType::Func(ft) = ty.composite_type {
126                            type_defs.push(ft);
127                        }
128                    }
129                }
130            }
131            Payload::FunctionSection(reader) => {
132                for res in reader.into_iter() {
133                    let func = res.map_err(|e| WasmValidationError::InvalidWasm {
134                        error: format!("{}", e),
135                    })?;
136
137                    fn_sigs.push(func);
138                }
139            }
140            _ => {}
141        }
142    }
143
144    validate_imports(&import_fns, &type_defs, interface, &mut errors);
145    validate_export_fns(&export_fns, &type_defs, &fn_sigs, interface, &mut errors);
146    validate_export_globals(&export_globals, &global_types, interface, &mut errors);
147
148    if errors.is_empty() {
149        Ok(())
150    } else {
151        Err(WasmValidationError::InterfaceViolated { errors })
152    }
153}
154
155/// Validates the import functions, checking the name and type against the given
156/// `Interface`
157fn validate_imports(
158    import_fns: &HashMap<(String, String), u32>,
159    type_defs: &[FuncType],
160    interface: &Interface,
161    errors: &mut Vec<String>,
162) {
163    for (key, val) in import_fns.iter() {
164        if let Some(interface_def) = interface.imports.get(key) {
165            let type_sig = if let Some(v) = type_defs.get(*val as usize) {
166                v
167            } else {
168                errors.push(format!(
169                    "Use of undeclared function reference \"{}\" in import function \"{}\" \"{}\"",
170                    val, key.0, key.1
171                ));
172                continue;
173            };
174            if let Import::Func { params, result, .. } = interface_def {
175                for (i, param) in type_sig
176                    .params()
177                    .iter()
178                    .cloned()
179                    .map(wasmparser_type_into_wasm_type)
180                    .enumerate()
181                {
182                    match param {
183                        Ok(t) => {
184                            if params.get(i).is_none() {
185                                errors.push(format!("Found {} args but the interface only expects {} for imported function \"{}\" \"{}\"", i, params.len(), &key.0, &key.1));
186                                continue;
187                            }
188                            if t != params[i] {
189                                errors.push(format!(
190                                    "Type mismatch in params in imported func \"{}\" \"{}\": argument {}, expected {} found {}",
191                                    &key.0, &key.1, i + 1, params[i], t
192                                ));
193                            }
194                        }
195                        Err(e) => errors.push(format!(
196                            "Invalid type in func \"{}\" \"{}\": {}",
197                            &key.0, &key.1, e
198                        )),
199                    }
200                }
201                for (i, ret) in type_sig
202                    .results()
203                    .iter()
204                    .cloned()
205                    .map(wasmparser_type_into_wasm_type)
206                    .enumerate()
207                {
208                    match ret {
209                        Ok(t) => {
210                            if result.get(i).is_none() {
211                                errors.push(format!("Found {} returns but the interface only expects {} for imported function \"{}\" \"{}\"", i, params.len(), &key.0, &key.1));
212                                continue;
213                            }
214
215                            if t != result[i] {
216                                errors.push(format!(
217                                    "Type mismatch in returns in func \"{}\" \"{}\", return {}, expected {} found {}",
218                                    &key.0, &key.1, i + 1, params[i], t
219                                ));
220                            }
221                        }
222                        Err(e) => errors.push(format!(
223                            "Invalid type in func \"{}\" \"{}\": {}",
224                            &key.0, &key.1, e
225                        )),
226                    }
227                }
228            }
229        } else {
230            // we didn't find the import at all in the interface
231            // TODO: improve error messages by including type information
232            errors.push(format!("Missing import \"{}\" \"{}\"", key.0, key.1));
233        }
234    }
235}
236
237/// Validates the export functions, checking the name and type against the given
238/// `Interface`
239fn validate_export_fns(
240    export_fns: &HashMap<String, u32>,
241    type_defs: &[FuncType],
242    fn_sigs: &Vec<u32>,
243    interface: &Interface,
244    errors: &mut Vec<String>,
245) {
246    'export_loop: for (key, val) in export_fns.iter() {
247        if let Some(interface_def) = interface.exports.get(key) {
248            let type_sig = if let Some(type_idx) = fn_sigs.get(*val as usize) {
249                if let Some(v) = type_defs.get(*type_idx as usize) {
250                    v
251                } else {
252                    errors.push(format!(
253                        "Export \"{}\" refers to type \"{}\" but only {} types were found",
254                        &key,
255                        type_idx,
256                        fn_sigs.len()
257                    ));
258                    continue;
259                }
260            } else {
261                errors.push(format!(
262                    "Use of undeclared function reference \"{}\" in export \"{}\"",
263                    val, &key
264                ));
265                continue;
266            };
267            if let Export::Func { params, result, .. } = interface_def {
268                for (i, param) in type_sig
269                    .params()
270                    .iter()
271                    .cloned()
272                    .map(wasmparser_type_into_wasm_type)
273                    .enumerate()
274                {
275                    match param {
276                        Ok(t) => {
277                            if params.get(i).is_none() {
278                                errors.push(format!("Found {} args but the interface only expects {} for exported function \"{}\"", type_sig.params().len(), params.len(), &key));
279                                continue 'export_loop;
280                            }
281                            if t != params[i] {
282                                errors.push(format!(
283                                    "Type mismatch in params in exported func \"{}\": in argument {}, expected {} found {}",
284                                    &key, i + 1, params[i], t
285                                ));
286                            }
287                        }
288                        Err(e) => errors
289                            .push(format!("Invalid type in exported func \"{}\": {}", &key, e)),
290                    }
291                }
292                for (i, ret) in type_sig
293                    .results()
294                    .iter()
295                    .cloned()
296                    .map(wasmparser_type_into_wasm_type)
297                    .enumerate()
298                {
299                    match ret {
300                        Ok(t) => {
301                            if result.get(i).is_none() {
302                                errors.push(format!("Found {} returns but the interface only expects {} for exported function \"{}\"", i, params.len(), &key));
303                                continue 'export_loop;
304                            }
305
306                            if t != result[i] {
307                                errors.push(format!(
308                                    "Type mismatch in returns in exported func \"{}\": in return {}, expected {} found {}",
309                                    &key, i + 1, result[i], t
310                                ));
311                            }
312                        }
313                        Err(e) => errors
314                            .push(format!("Invalid type in exported func \"{}\": {}", &key, e)),
315                    }
316                }
317            }
318        }
319    }
320}
321
322/// Validates the export globals, checking the name and type against the given
323/// `Interface`
324fn validate_export_globals(
325    export_globals: &HashMap<String, u32>,
326    global_types: &Vec<GlobalType>,
327    interface: &Interface,
328    errors: &mut Vec<String>,
329) {
330    for (key, val) in export_globals.iter() {
331        if let Some(Export::Global { var_type, .. }) = interface.exports.get(key) {
332            if global_types.get(*val as usize).is_none() {
333                errors.push(format!(
334                    "Invalid wasm, expected {} global types, found {}",
335                    val,
336                    global_types.len()
337                ));
338            }
339            match wasmparser_type_into_wasm_type(global_types[*val as usize].content_type) {
340                Ok(t) => {
341                    if *var_type != t {
342                        errors.push(format!(
343                            "Type mismatch in global export {}: expected {} found {}",
344                            &key, var_type, t
345                        ));
346                    }
347                }
348                Err(e) => errors.push(format!("In global export {}: {}", &key, e)),
349            }
350        }
351    }
352}
353
354/// Converts Wasmparser's type enum into wasm-interface's type enum
355/// wasmparser's enum contains things which are invalid in many situations
356///
357/// Additionally wasmerparser containers more advanced types like references that
358/// wasm-interface does not yet support
359fn wasmparser_type_into_wasm_type(ty: wasmparser::ValType) -> Result<WasmType, String> {
360    use wasmparser::ValType;
361    Ok(match ty {
362        ValType::I32 => WasmType::I32,
363        ValType::I64 => WasmType::I64,
364        ValType::F32 => WasmType::F32,
365        ValType::F64 => WasmType::F64,
366        e => {
367            return Err(format!("Invalid type found: {:?}", e));
368        }
369    })
370}
371
372#[cfg(test)]
373mod validation_tests {
374    use super::*;
375    use crate::parser;
376
377    #[test]
378    fn global_imports() {
379        const WAT: &str = r#"(module
380(type $t0 (func (param i32 i64)))
381(global $length (import "env" "length") i32)
382(import "env" "do_panic" (func $do_panic (type $t0)))
383)"#;
384        let wasm = wat::parse_str(WAT).unwrap();
385
386        let interface_src = r#"
387(interface
388(func (import "env" "do_panic") (param i32 i64))
389(global (import "env" "length") (type i32)))"#;
390        let interface = parser::parse_interface(interface_src).unwrap();
391
392        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
393
394        assert!(result.is_ok());
395
396        // Now set the global import type to mismatch the wasm
397        let interface_src = r#"
398(interface
399(func (import "env" "do_panic") (param i32 i64))
400(global (import "env" "length") (type i64)))"#;
401        let interface = parser::parse_interface(interface_src).unwrap();
402
403        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
404
405        assert!(
406            result.is_err(),
407            "global import type mismatch causes an error"
408        );
409
410        // Now set the function import type to mismatch the wasm
411        let interface_src = r#"
412(interface
413(func (import "env" "do_panic") (param i64))
414(global (import "env" "length") (type i32)))"#;
415        let interface = parser::parse_interface(interface_src).unwrap();
416
417        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
418
419        assert!(
420            result.is_err(),
421            "function import type mismatch causes an error"
422        );
423
424        // Now try with a module that has an import that the interface doesn't have
425        let interface_src = r#"
426(interface
427(func (import "env" "do_panic") (param i64))
428(global (import "env" "length_plus_plus") (type i32)))"#;
429        let interface = parser::parse_interface(interface_src).unwrap();
430
431        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
432
433        assert!(
434            result.is_err(),
435            "all imports must be covered by the interface"
436        );
437    }
438
439    #[test]
440    fn global_exports() {
441        const WAT: &str = r#"(module
442(func (export "as-set_local-first") (param i32) (result i32)
443  (nop) (i32.const 2) (set_local 0) (get_local 0))
444(global (export "num_tries") i64 (i64.const 0))
445)"#;
446        let wasm = wat::parse_str(WAT).unwrap();
447
448        let interface_src = r#"
449(interface
450(func (export "as-set_local-first") (param i32) (result i32))
451(global (export "num_tries") (type i64)))"#;
452        let interface = parser::parse_interface(interface_src).unwrap();
453
454        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
455
456        assert!(result.is_ok());
457
458        // Now set the global export type to mismatch the wasm
459        let interface_src = r#"
460(interface
461(func (export "as-set_local-first") (param i32) (result i32))
462(global (export "num_tries") (type f32)))"#;
463        let interface = parser::parse_interface(interface_src).unwrap();
464
465        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
466
467        assert!(
468            result.is_err(),
469            "global export type mismatch causes an error"
470        );
471
472        // Now set the function export type to mismatch the wasm
473        let interface_src = r#"
474(interface
475(func (export "as-set_local-first") (param i64) (result i64))
476(global (export "num_tries") (type i64)))"#;
477        let interface = parser::parse_interface(interface_src).unwrap();
478
479        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
480
481        assert!(
482            result.is_err(),
483            "function export type mismatch causes an error"
484        );
485
486        // Now try a interface that requires an export that the module doesn't have
487        let interface_src = r#"
488(interface
489(func (export "as-set_local-first") (param i64) (result i64))
490(global (export "numb_trees") (type i64)))"#;
491        let interface = parser::parse_interface(interface_src).unwrap();
492
493        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
494
495        assert!(result.is_err(), "missing a required export is an error");
496    }
497}
498
499#[derive(Debug)]
500pub enum WasmValidationError {
501    InvalidWasm { error: String },
502    InterfaceViolated { errors: Vec<String> },
503    UnsupportedType { error: String },
504}