wasm/validation/
mod.rs

1use alloc::{
2    collections::btree_set::{self, BTreeSet},
3    vec::Vec,
4};
5use core::iter::Map;
6
7use crate::{
8    core::{
9        decoding::{
10            modules::sections::{decode_section_if_ty_matches, SectionTy},
11            reader::{span::Span, WasmDecoder},
12        },
13        sidetable::Sidetable,
14        structure::{
15            modules::{
16                data_segments::DataSegment,
17                element_segments::ElemType,
18                exports::{Export, ExportDesc},
19                globals::Global,
20                imports::{Import, ImportDesc},
21                indices::{
22                    DataIdx, ElemIdx, ExtendedIdxVec, FuncIdx, GlobalIdx, IdxVec,
23                    IdxVecOverflowError, MemIdx, TableIdx, TypeIdx,
24                },
25            },
26            types::{ExternType, FuncType, GlobalType, MemType, ResultType, TableType},
27        },
28        utils::ToUsizeExt,
29    },
30    validation::{config::ValidationConfig, modules::functions::decode_and_validate_code_section},
31    CustomSection, DecodingError, ValidationError,
32};
33
34pub mod error;
35pub mod instructions;
36pub mod modules;
37pub mod types;
38pub mod validation_stack;
39
40pub mod config;
41
42/// Information collected from validating a module.
43///
44/// This can be used to instantiate a new module instance in some
45/// [`Store`](crate::Store) thorugh
46/// [`Store::module_instantiate`](crate::Store::module_instantiate)
47#[derive(Clone, Debug)]
48pub struct Module<'bytecode> {
49    pub(crate) wasm: &'bytecode [u8],
50    pub(crate) types: IdxVec<TypeIdx, FuncType>,
51    pub(crate) imports: Vec<Import<'bytecode>>,
52    pub(crate) functions: ExtendedIdxVec<FuncIdx, TypeIdx>,
53    pub(crate) tables: ExtendedIdxVec<TableIdx, TableType>,
54    pub(crate) memories: ExtendedIdxVec<MemIdx, MemType>,
55    pub(crate) globals: ExtendedIdxVec<GlobalIdx, Global>,
56    pub(crate) exports: Vec<Export<'bytecode>>,
57    pub(crate) elements: IdxVec<ElemIdx, ElemType>,
58    pub(crate) data: IdxVec<DataIdx, DataSegment>,
59    /// Each block contains the validated code section and the stp corresponding to
60    /// the beginning of that code section
61    pub(crate) func_blocks_stps: Vec<(Span, usize)>,
62    pub(crate) sidetable: Sidetable,
63    /// The start function which is automatically executed during instantiation
64    pub(crate) start: Option<FuncIdx>,
65    pub(crate) custom_sections: Vec<CustomSection<'bytecode>>,
66    // pub(crate) exports_length: Exported,
67}
68
69fn validate_no_duplicate_exports(validation_info: &Module) -> Result<(), ValidationError> {
70    let mut found_export_names: btree_set::BTreeSet<&str> = btree_set::BTreeSet::new();
71    for export in &validation_info.exports {
72        if found_export_names.contains(export.name) {
73            return Err(ValidationError::DuplicateExportName);
74        }
75        found_export_names.insert(export.name);
76    }
77    Ok(())
78}
79
80pub fn decode_and_validate<'wasm, T: ValidationConfig>(
81    wasm: &'wasm [u8],
82    user_data: &mut T,
83) -> Result<Module<'wasm>, ValidationError> {
84    let mut wasm = WasmDecoder::new(wasm);
85
86    // represents C.refs in https://webassembly.github.io/spec/core/valid/conventions.html#context
87    // A func.ref instruction is onlv valid if it has an immediate that is a member of C.refs.
88    // this list holds all the func_idx's occurring in the module, except in its functions or start function.
89    // I make an exception here by not including func_idx's occuring within data segments in C.refs as well, so that single pass validation is possible.
90    // If there is a func_idx within the data segment, this would ultimately mean that data segment cannot be validated,
91    // therefore this hack is acceptable.
92    // https://webassembly.github.io/spec/core/valid/modules.html#data-segments
93    // https://webassembly.github.io/spec/core/valid/modules.html#valid-module
94
95    let mut validation_context_refs: BTreeSet<FuncIdx> = BTreeSet::new();
96
97    trace!("Starting validation of bytecode");
98
99    trace!("Validating magic value");
100    let [0x00, 0x61, 0x73, 0x6d] = wasm.strip_bytes::<4>()? else {
101        return Err(DecodingError::InvalidMagic.into());
102    };
103
104    trace!("Validating version number");
105    let [0x01, 0x00, 0x00, 0x00] = wasm.strip_bytes::<4>()? else {
106        return Err(DecodingError::InvalidBinaryFormatVersion.into());
107    };
108    debug!("Header ok");
109
110    let mut custom_sections = Vec::new();
111    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
112
113    let types = decode_section_if_ty_matches(&mut wasm, SectionTy::Type, |wasm, _| {
114        wasm.decode_vec(FuncType::decode).map(|types| IdxVec::new(types).expect("that index space creation never fails because the length of the types vector is encoded as a 32-bit integer in the bytecode"))
115    }) ?
116    .unwrap_or_default();
117
118    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
119
120    let imports = decode_section_if_ty_matches(&mut wasm, SectionTy::Import, |wasm, _| {
121        wasm.decode_vec(|wasm| Import::decode_and_validate(wasm, &types))
122    })?
123    .unwrap_or_default();
124
125    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
126
127    // The `Function` section only covers module-level (or "local") functions.
128    // Imported functions have their types known in the `import` section. Both
129    // local and imported functions share the same index space.
130    //
131    // Imported functions are given priority and have the first indicies, and
132    // only after that do the local functions get assigned their indices.
133    let local_functions =
134        decode_section_if_ty_matches(&mut wasm, SectionTy::Function, |wasm, _| {
135            wasm.decode_vec(|wasm| TypeIdx::decode_and_validate(wasm, &types))
136        })?
137        .unwrap_or_default();
138
139    let imported_functions = imports.iter().filter_map(|import| match &import.desc {
140        ImportDesc::Func(type_idx) => Some(*type_idx),
141        _ => None,
142    });
143
144    let functions = ExtendedIdxVec::new(imported_functions.collect(), local_functions)
145        .map_err(|IdxVecOverflowError| ValidationError::TooManyFunctions)?;
146
147    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
148
149    let imported_tables = imports.iter().filter_map(|m| match m.desc {
150        ImportDesc::Table(table) => Some(table),
151        _ => None,
152    });
153    let local_tables = decode_section_if_ty_matches(&mut wasm, SectionTy::Table, |wasm, _| {
154        wasm.decode_vec(TableType::decode_and_validate)
155    })?
156    .unwrap_or_default();
157
158    let tables = ExtendedIdxVec::new(imported_tables.collect(), local_tables)
159        .map_err(|IdxVecOverflowError| ValidationError::TooManyTables)?;
160
161    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
162
163    let imported_memories = imports.iter().filter_map(|m| match m.desc {
164        ImportDesc::Mem(mem) => Some(mem),
165        _ => None,
166    });
167    // let imported_memories_length = imported_memories.len();
168    let local_memories = decode_section_if_ty_matches(&mut wasm, SectionTy::Memory, |wasm, _| {
169        wasm.decode_vec(MemType::decode_and_validate)
170    })?
171    .unwrap_or_default();
172
173    let memories = ExtendedIdxVec::new(imported_memories.collect(), local_memories)
174        .map_err(|IdxVecOverflowError| ValidationError::TooManyMemories)?;
175
176    if memories.inner().len() > 1 {
177        return Err(ValidationError::UnsupportedMultipleMemoriesProposal);
178    }
179
180    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
181
182    let imported_global_types: Vec<GlobalType> = imports
183        .iter()
184        .filter_map(|m| match m.desc {
185            ImportDesc::Global(global) => Some(global),
186            _ => None,
187        })
188        .collect();
189    let local_globals = decode_section_if_ty_matches(&mut wasm, SectionTy::Global, |wasm, _| {
190        wasm.decode_vec(|wasm| {
191            Global::decode_and_validate(
192                wasm,
193                &imported_global_types,
194                &mut validation_context_refs,
195                functions.inner(),
196            )
197        })
198    })?
199    .unwrap_or_default();
200
201    let imported_globals = imported_global_types.iter().map(|ty| Global {
202        // TODO using a default MAX value for spans that are never executed is
203        // not really safe. Maybe opt for an Option instead.
204        init_expr: Span::new(usize::MAX, 0),
205        ty: *ty,
206    });
207    let globals = ExtendedIdxVec::new(imported_globals.collect(), local_globals)
208        .map_err(|IdxVecOverflowError| ValidationError::TooManyGlobals)?;
209
210    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
211
212    let exports = decode_section_if_ty_matches(&mut wasm, SectionTy::Export, |wasm, _| {
213        wasm.decode_vec(|wasm| {
214            Export::decode_and_validate(
215                wasm,
216                functions.inner(),
217                tables.inner(),
218                memories.inner(),
219                globals.inner(),
220            )
221        })
222    })?
223    .unwrap_or_default();
224    validation_context_refs.extend(exports.iter().filter_map(
225        |Export { name: _, desc }| match *desc {
226            ExportDesc::Func(func_idx) => Some(func_idx),
227            _ => None,
228        },
229    ));
230
231    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
232
233    let start = decode_section_if_ty_matches(&mut wasm, SectionTy::Start, |wasm, _| {
234        let func_idx = FuncIdx::decode_and_validate(wasm, functions.inner())?;
235
236        // start function signature must be [] -> []
237        // https://webassembly.github.io/spec/core/valid/modules.html#start-function
238        // SAFETY: We just validated this function index using the same
239        // `IdxVec`.
240        let type_idx = unsafe { functions.inner().get(func_idx) };
241
242        // SAFETY: There exists only one `IdxVec<TypeIdx, FuncType>` in the
243        // current function. Therefore, this has to be the same one used to
244        // create and validate this `TypeIdx`.
245        let func_type = unsafe { types.get(*type_idx) };
246        if func_type
247            != &(FuncType {
248                params: ResultType {
249                    valtypes: Vec::new(),
250                },
251                returns: ResultType {
252                    valtypes: Vec::new(),
253                },
254            })
255        {
256            Err(ValidationError::InvalidStartFunctionSignature)
257        } else {
258            Ok(func_idx)
259        }
260    })?;
261
262    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
263
264    let elements = decode_section_if_ty_matches(&mut wasm, SectionTy::Element, |wasm, _| {
265        ElemType::decode_and_validate(
266            wasm,
267            functions.inner(),
268            &mut validation_context_refs,
269            tables.inner(),
270            &imported_global_types,
271        )
272        .map(|elements| IdxVec::new(elements).expect("that index space creation never fails because the length of the elements vector is encoded as a 32-bit integer in the bytecode"))
273    })?
274    .unwrap_or_default();
275
276    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
277
278    // https://webassembly.github.io/spec/core/binary/modules.html#data-count-section
279    // As per the official documentation:
280    //
281    // The data count section is used to simplify single-pass validation. Since the data section occurs after the code section, the `memory.init` and `data.drop` and instructions would not be able to check whether the data segment index is valid until the data section is read. The data count section occurs before the code section, so a single-pass validator can use this count instead of deferring validation.
282    let data_count: Option<u32> =
283        decode_section_if_ty_matches(&mut wasm, SectionTy::DataCount, |wasm, _| {
284            wasm.decode_var_u32()
285        })?;
286
287    trace!("data count: {data_count:?}");
288
289    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
290
291    let mut sidetable = Sidetable::new();
292    let func_blocks_stps = decode_section_if_ty_matches(&mut wasm, SectionTy::Code, |wasm, _| {
293        // SAFETY: It is required that all passed index values are valid in all
294        // passed `IdxVec`s. The current function does not take any index types
295        // as arguments and every `IdxVec<..., ...>` is unique because they use
296        // different generics. Therefore, all index types must be valid in their
297        // relevant `IdxVec`s.
298        unsafe {
299            decode_and_validate_code_section(
300                wasm,
301                &types,
302                &functions,
303                globals.inner(),
304                memories.inner(),
305                data_count,
306                tables.inner(),
307                &elements,
308                &validation_context_refs,
309                &mut sidetable,
310                user_data,
311            )
312        }
313    })?
314    .unwrap_or_default();
315
316    if func_blocks_stps.len() != functions.len_local_definitions().into_usize() {
317        return Err(ValidationError::FunctionAndCodeSectionsHaveDifferentLengths);
318    }
319
320    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
321
322    let data_section = decode_section_if_ty_matches(&mut wasm, SectionTy::Data, |wasm, _| {
323    wasm.decode_vec(|wasm| {
324        DataSegment::decode_and_validate(wasm, &imported_global_types, functions.inner(), memories.inner())
325    })
326            .map(|data_segments| IdxVec::new(data_segments).expect("that index space creation never fails because the length of the data segments vector is encoded as a 32-bit integer in the bytecode"))
327    })?
328    .unwrap_or_default();
329
330    // https://webassembly.github.io/spec/core/binary/modules.html#data-count-section
331    if let Some(data_count) = data_count {
332        if data_count != data_section.len() {
333            return Err(ValidationError::DataCountAndDataSectionsLengthAreDifferent);
334        }
335    }
336
337    read_all_custom_sections(&mut wasm, &mut custom_sections)?;
338
339    // All sections should have been handled
340    if !wasm.remaining_bytes().is_empty() {
341        let remaining_section_ty = SectionTy::decode(&mut wasm).expect(
342            "that the section type is not malformed, because it must have been peeked before",
343        );
344        return Err(DecodingError::SectionOutOfOrder(remaining_section_ty).into());
345    }
346
347    debug!("Validation was successful");
348    let validation_info = Module {
349        wasm: wasm.into_inner(),
350        types,
351        imports,
352        functions,
353        tables,
354        memories,
355        globals,
356        exports,
357        func_blocks_stps,
358        sidetable,
359        data: data_section,
360        start,
361        elements,
362        custom_sections,
363    };
364    validate_no_duplicate_exports(&validation_info)?;
365
366    Ok(validation_info)
367}
368
369/// Reads the next sections as long as they are custom sections and pushes them
370/// into the `custom_sections` vector.
371fn read_all_custom_sections<'wasm>(
372    wasm: &mut WasmDecoder<'wasm>,
373    custom_sections: &mut Vec<CustomSection<'wasm>>,
374) -> Result<(), ValidationError> {
375    while let Some(custom_section) =
376        decode_section_if_ty_matches(wasm, SectionTy::Custom, CustomSection::decode)?
377    {
378        custom_sections.push(custom_section);
379    }
380
381    Ok(())
382}
383
384impl<'wasm> Module<'wasm> {
385    /// Returns the imports of this module as an iterator. Each import consist
386    /// of a module name, a name and an extern type.
387    ///
388    /// See: WebAssembly Specification 2.0 - 7.1.5 - module_imports
389    pub fn imports<'a>(
390        &'a self,
391    ) -> Map<
392        core::slice::Iter<'a, Import<'wasm>>,
393        impl FnMut(&'a Import<'wasm>) -> (&'a str, &'a str, ExternType),
394    > {
395        self.imports.iter().map(|import| {
396            // SAFETY: This is sound because the argument is `self` and the
397            // import desc also comes from `self`.
398            let extern_type = unsafe { import.desc.extern_type(self) };
399            (import.module_name, import.name, extern_type)
400        })
401    }
402
403    /// Returns the exports of this module as an iterator. Each export consist
404    /// of a name, and an extern type.
405    ///
406    /// See: WebAssembly Specification 2.0 - 7.1.5 - module_exports
407    pub fn exports<'a>(
408        &'a self,
409    ) -> Map<
410        core::slice::Iter<'a, Export<'wasm>>,
411        impl FnMut(&'a Export<'wasm>) -> (&'a str, ExternType),
412    > {
413        self.exports.iter().map(|export| {
414            // SAFETY: This is sound because the argument is `self` and the
415            // export desc also comes from `self`.
416            let extern_type = unsafe { export.desc.extern_type(self) };
417            (export.name, extern_type)
418        })
419    }
420
421    /// Returns a list of all custom sections in the bytecode. Every custom
422    /// section consists of its name and the custom section's bytecode
423    /// (excluding the name itself).
424    pub fn custom_sections(&self) -> &[CustomSection<'wasm>] {
425        &self.custom_sections
426    }
427}