diff --git a/src/lib.rs b/src/lib.rs index 4d763e97..4a94c47d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,8 +66,16 @@ pub use crate::validator::ValidatingOperatorParser; pub use crate::validator::ValidatingParser; pub use crate::validator::ValidatingParserConfig; +pub use crate::module_resources::WasmFuncType; +pub use crate::module_resources::WasmGlobalType; +pub use crate::module_resources::WasmMemoryType; +pub use crate::module_resources::WasmModuleResources; +pub use crate::module_resources::WasmTableType; +pub use crate::module_resources::WasmType; + +pub(crate) use crate::module_resources::{wasm_func_type_inputs, wasm_func_type_outputs}; + pub use crate::operators_validator::OperatorValidatorConfig; -pub use crate::operators_validator::WasmModuleResources; pub use crate::readers::CodeSectionReader; pub use crate::readers::CustomSectionContent; @@ -113,6 +121,7 @@ pub use crate::readers::TypeSectionReader; mod binary_reader; mod limits; +mod module_resources; mod operators_validator; mod parser; mod primitives; diff --git a/src/module_resources.rs b/src/module_resources.rs new file mode 100644 index 00000000..09b19375 --- /dev/null +++ b/src/module_resources.rs @@ -0,0 +1,370 @@ +/* Copyright 2019 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// Types that qualify as Wasm types for validation purposes. +/// +/// Must be comparable with `wasmparser` given Wasm types and +/// must be comparable to themselves. +pub trait WasmType: PartialEq + PartialEq + Eq { + /// Converts the custom type into a `wasmparser` known type. + /// + /// # Note + /// + /// This interface is required as bridge until transitioning is complete. + fn to_parser_type(&self) -> crate::Type; +} + +/// Types that qualify as Wasm function types for validation purposes. +pub trait WasmFuncType { + /// A type that is comparable with Wasm types. + type Type: WasmType; + + /// Returns the number of input types. + fn len_inputs(&self) -> usize; + /// Returns the number of output types. + fn len_outputs(&self) -> usize; + /// Returns the type at given index if any. + /// + /// # Note + /// + /// The returned type may be wrapped by the user crate and thus + /// the actually returned type only has to be comparable to a Wasm type. + fn input_at(&self, at: u32) -> Option<&Self::Type>; + /// Returns the type at given index if any. + /// + /// # Note + /// + /// The returned type may be wrapped by the user crate and thus + /// the actually returned type only has to be comparable to a Wasm type. + fn output_at(&self, at: u32) -> Option<&Self::Type>; +} + +/// Iterator over the inputs of a Wasm function type. +pub(crate) struct WasmFuncTypeInputs<'a, F, T> +where + F: WasmFuncType, + T: WasmType + 'a, +{ + /// The iterated-over function type. + func_type: &'a F, + /// The current starting index. + start: u32, + /// The current ending index. + end: u32, +} + +impl<'a, F, T> WasmFuncTypeInputs<'a, F, T> +where + F: WasmFuncType, + T: WasmType + 'a, +{ + fn new(func_type: &'a F) -> Self { + Self { + func_type, + start: 0, + end: func_type.len_inputs() as u32, + } + } +} + +impl<'a, F, T> Iterator for WasmFuncTypeInputs<'a, F, T> +where + F: WasmFuncType, + T: WasmType + 'a, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + if self.start == self.end { + return None; + } + let ty = self + .func_type + .input_at(self.start) + // Expected since `self.start != self.end`. + .expect("we expect to receive an input type at this point"); + self.start += 1; + Some(ty) + } + + fn size_hint(&self) -> (usize, Option) { + (self.len(), Some(self.len())) + } +} + +impl<'a, F, T> DoubleEndedIterator for WasmFuncTypeInputs<'a, F, T> +where + F: WasmFuncType, + T: WasmType + 'a, +{ + fn next_back(&mut self) -> Option { + if self.start == self.end { + return None; + } + let ty = self + .func_type + .input_at(self.end) + // Expected since `self.start != self.end`. + .expect("we expect to receive an input type at this point"); + self.end -= 1; + Some(ty) + } +} + +impl<'a, F, T> ExactSizeIterator for WasmFuncTypeInputs<'a, F, T> +where + F: WasmFuncType, + T: WasmType + 'a, +{ + fn len(&self) -> usize { + (self.end as usize) - (self.start as usize) + } +} + +/// Iterator over the outputs of a Wasm function type. +pub(crate) struct WasmFuncTypeOutputs<'a, F, T> +where + F: WasmFuncType, + T: WasmType + 'a, +{ + /// The iterated-over function type. + func_type: &'a F, + /// The current starting index. + start: u32, + /// The current ending index. + end: u32, +} + +impl<'a, F, T> WasmFuncTypeOutputs<'a, F, T> +where + F: WasmFuncType, + T: WasmType + 'a, +{ + fn new(func_type: &'a F) -> Self { + Self { + func_type, + start: 0, + end: func_type.len_outputs() as u32, + } + } +} + +impl<'a, F, T> Iterator for WasmFuncTypeOutputs<'a, F, T> +where + F: WasmFuncType, + T: WasmType + 'a, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + if self.start == self.end { + return None; + } + let ty = self + .func_type + .output_at(self.start) + // Expected since `self.start != self.end`. + .expect("we expect to receive an input type at this point"); + self.start += 1; + Some(ty) + } + + fn size_hint(&self) -> (usize, Option) { + (self.len(), Some(self.len())) + } +} + +impl<'a, F, T> DoubleEndedIterator for WasmFuncTypeOutputs<'a, F, T> +where + F: WasmFuncType, + T: WasmType + 'a, +{ + fn next_back(&mut self) -> Option { + if self.start == self.end { + return None; + } + let ty = self + .func_type + .output_at(self.end) + // Expected since `self.start != self.end`. + .expect("we expect to receive an input type at this point"); + self.end -= 1; + Some(ty) + } +} + +impl<'a, F, T> ExactSizeIterator for WasmFuncTypeOutputs<'a, F, T> +where + F: WasmFuncType, + T: WasmType + 'a, +{ + fn len(&self) -> usize { + (self.end as usize) - (self.start as usize) + } +} + +/// Returns an iterator over the input types of a Wasm function type. +pub(crate) fn wasm_func_type_inputs<'a, F, T>(func_type: &'a F) -> WasmFuncTypeInputs<'a, F, T> +where + F: WasmFuncType, + T: WasmType + 'a, +{ + WasmFuncTypeInputs::new(func_type) +} + +/// Returns an iterator over the output types of a Wasm function type. +pub(crate) fn wasm_func_type_outputs<'a, F, T>(func_type: &'a F) -> WasmFuncTypeOutputs<'a, F, T> +where + F: WasmFuncType, + T: WasmType + 'a, +{ + WasmFuncTypeOutputs::new(func_type) +} + +/// Types that qualify as Wasm table types for validation purposes. +pub trait WasmTableType { + /// A type that is comparable with Wasm types. + type Type: WasmType; + + /// Returns the element type of the table. + fn element_type(&self) -> &Self::Type; + /// Returns the initial limit of the table. + fn initial_limit(&self) -> u32; + /// Returns the maximum limit of the table if any. + fn maximum_limit(&self) -> Option; +} + +/// Types that qualify as Wasm memory types for validation purposes. +pub trait WasmMemoryType { + /// Returns `true` if the linear memory is shared. + fn is_shared(&self) -> bool; + /// Returns the initial limit of the linear memory. + fn initial_limit(&self) -> u32; + /// Returns the maximum limit of the linear memory if any. + fn maximum_limit(&self) -> Option; +} + +/// Types that qualify as Wasm global types for validation purposes. +pub trait WasmGlobalType { + /// A type that is comparable with Wasm types. + type Type: WasmType; + + /// Returns `true` if the global variable is mutable. + fn is_mutable(&self) -> bool; + /// Returns the content type of the global variable. + fn content_type(&self) -> &Self::Type; +} + +/// Types that qualify as Wasm valiation database. +/// +/// # Note +/// +/// The `wasmparser` crate provides a builtin validation framework but allows +/// users of this crate to also feed the parsed Wasm into their own data +/// structure while parsing and also validate at the same time without +/// the need of an additional parsing or validation step or copying data around. +pub trait WasmModuleResources { + /// The function type used for validation. + type FuncType: WasmFuncType; + /// The table type used for validation. + type TableType: WasmTableType; + /// The memory type used for validation. + type MemoryType: WasmMemoryType; + /// The global type used for validation. + type GlobalType: WasmGlobalType; + + /// Returns the type at given index. + fn type_at(&self, at: u32) -> Option<&Self::FuncType>; + /// Returns the table at given index if any. + fn table_at(&self, at: u32) -> Option<&Self::TableType>; + /// Returns the linear memory at given index. + fn memory_at(&self, at: u32) -> Option<&Self::MemoryType>; + /// Returns the global variable at given index. + fn global_at(&self, at: u32) -> Option<&Self::GlobalType>; + /// Returns the function signature ID at given index. + fn func_type_id_at(&self, at: u32) -> Option; + + /// Returns the number of elements. + fn element_count(&self) -> u32; + /// Returns the number of bytes in the Wasm data section. + fn data_count(&self) -> u32; +} + +impl WasmType for crate::Type { + fn to_parser_type(&self) -> crate::Type { + *self + } +} + +impl WasmFuncType for crate::FuncType { + type Type = crate::Type; + + fn len_inputs(&self) -> usize { + self.params.len() + } + + fn len_outputs(&self) -> usize { + self.returns.len() + } + + fn input_at(&self, at: u32) -> Option<&Self::Type> { + self.params.get(at as usize) + } + + fn output_at(&self, at: u32) -> Option<&Self::Type> { + self.returns.get(at as usize) + } +} + +impl WasmGlobalType for crate::GlobalType { + type Type = crate::Type; + + fn is_mutable(&self) -> bool { + self.mutable + } + + fn content_type(&self) -> &Self::Type { + &self.content_type + } +} + +impl WasmTableType for crate::TableType { + type Type = crate::Type; + + fn element_type(&self) -> &Self::Type { + &self.element_type + } + + fn initial_limit(&self) -> u32 { + self.limits.initial + } + + fn maximum_limit(&self) -> Option { + self.limits.maximum + } +} + +impl WasmMemoryType for crate::MemoryType { + fn is_shared(&self) -> bool { + self.shared + } + + fn initial_limit(&self) -> u32 { + self.limits.initial + } + fn maximum_limit(&self) -> Option { + self.limits.maximum + } +} diff --git a/src/operators_validator.rs b/src/operators_validator.rs index 3509f715..b602942b 100644 --- a/src/operators_validator.rs +++ b/src/operators_validator.rs @@ -17,9 +17,10 @@ use std::cmp::min; use std::result; use std::str; -use crate::primitives::{ - FuncType, GlobalType, MemoryImmediate, MemoryType, Operator, SIMDLaneIndex, TableType, Type, - TypeOrFuncType, +use crate::primitives::{MemoryImmediate, Operator, SIMDLaneIndex, Type, TypeOrFuncType}; +use crate::{ + wasm_func_type_inputs, wasm_func_type_outputs, WasmFuncType, WasmGlobalType, WasmMemoryType, + WasmModuleResources, WasmTableType, WasmType, }; /// Test if `subtype` is a subtype of `supertype`. @@ -122,18 +123,36 @@ impl FuncState { } Ok(()) } - fn push_block( + fn push_block( &mut self, ty: TypeOrFuncType, block_type: BlockType, - resources: &dyn WasmModuleResources, + resources: &dyn WasmModuleResources< + FuncType = F, + TableType = T, + MemoryType = M, + GlobalType = G, + >, ) -> OperatorValidatorResult<()> { let (start_types, return_types) = match ty { TypeOrFuncType::Type(Type::EmptyBlockType) => (vec![], vec![]), TypeOrFuncType::Type(ty) => (vec![], vec![ty]), TypeOrFuncType::FuncType(idx) => { - let ty = &resources.types()[idx as usize]; - (ty.params.clone().into_vec(), ty.returns.clone().into_vec()) + let ty = resources + .type_at(idx) + // Note: This was an out-of-bounds memory access before + // the change to return `Option` at `type_at`. So + // I assumed that invalid indices at this point are + // bugs. + .expect("function type index is out of bounds"); + ( + wasm_func_type_inputs(ty) + .map(WasmType::to_parser_type) + .collect::>(), + wasm_func_type_outputs(ty) + .map(WasmType::to_parser_type) + .collect::>(), + ) } }; if block_type == BlockType::If { @@ -202,16 +221,16 @@ impl FuncState { self.stack_types.push(ty); Ok(()) } - fn change_frame_with_types( + fn change_frame_with_types( &mut self, remove_count: usize, - new_items: &[Type], - ) -> OperatorValidatorResult<()> { + new_items: I, + ) -> OperatorValidatorResult<()> + where + I: Iterator, + { self.remove_frame_stack_types(remove_count)?; - if new_items.is_empty() { - return Ok(()); - } - self.stack_types.extend_from_slice(new_items); + self.stack_types.extend(new_items); Ok(()) } fn change_frame_to_exact_types_from(&mut self, depth: usize) -> OperatorValidatorResult<()> { @@ -253,16 +272,6 @@ enum BlockType { If, } -pub trait WasmModuleResources { - fn types(&self) -> &[FuncType]; - fn tables(&self) -> &[TableType]; - fn memories(&self) -> &[MemoryType]; - fn globals(&self) -> &[GlobalType]; - fn func_type_indices(&self) -> &[u32]; - fn element_count(&self) -> u32; - fn data_count(&self) -> u32; -} - pub enum FunctionEnd { No, Yes, @@ -301,22 +310,30 @@ pub(crate) struct OperatorValidator { } impl OperatorValidator { - pub fn new( - func_type: &FuncType, + pub fn new( + func_type: &F, locals: &[(u32, Type)], config: OperatorValidatorConfig, - ) -> OperatorValidator { - let mut local_types = Vec::new(); - local_types.extend_from_slice(&*func_type.params); - for local in locals { - for _ in 0..local.0 { - local_types.push(local.1); + ) -> OperatorValidator + where + F: WasmFuncType, + T: WasmType, + { + let local_types = { + let mut local_types = wasm_func_type_inputs(func_type) + .map(WasmType::to_parser_type) + .collect::>(); + for local in locals { + for _ in 0..local.0 { + local_types.push(local.1); + } } - } - + local_types + }; let mut blocks = Vec::new(); - let mut last_returns = Vec::new(); - last_returns.extend_from_slice(&*func_type.returns); + let last_returns = wasm_func_type_outputs(func_type) + .map(WasmType::to_parser_type) + .collect::>(); blocks.push(BlockState { start_types: vec![], return_types: last_returns, @@ -369,13 +386,35 @@ impl OperatorValidator { Ok(()) } - fn check_operands(&self, expected_types: &[Type]) -> OperatorValidatorResult<()> { + fn check_operands_3( + &self, + operand1: Type, + operand2: Type, + operand3: Type, + ) -> OperatorValidatorResult<()> { + self.check_frame_size(3)?; + if !self.func_state.assert_stack_type_at(2, operand1) { + return Err("stack operand type mismatch"); + } + if !self.func_state.assert_stack_type_at(1, operand2) { + return Err("stack operand type mismatch"); + } + if !self.func_state.assert_stack_type_at(0, operand3) { + return Err("stack operand type mismatch"); + } + Ok(()) + } + + fn check_operands(&self, expected_types: I) -> OperatorValidatorResult<()> + where + I: ExactSizeIterator, + { let len = expected_types.len(); self.check_frame_size(len)?; - for (i, expected_type) in expected_types.iter().enumerate() { + for (i, expected_type) in expected_types.enumerate() { if !self .func_state - .assert_stack_type_at(len - 1 - i, *expected_type) + .assert_stack_type_at(len - 1 - i, expected_type) { return Err("stack operand type mismatch"); } @@ -463,36 +502,61 @@ impl OperatorValidator { Ok(()) } - fn check_memory_index( + fn check_memory_index< + F: WasmFuncType, + T: WasmTableType, + M: WasmMemoryType, + G: WasmGlobalType, + >( &self, memory_index: u32, - resources: &dyn WasmModuleResources, + resources: &dyn WasmModuleResources< + FuncType = F, + TableType = T, + MemoryType = M, + GlobalType = G, + >, ) -> OperatorValidatorResult<()> { - if memory_index as usize >= resources.memories().len() { + if resources.memory_at(memory_index).is_none() { return Err("no linear memories are present"); } Ok(()) } - fn check_shared_memory_index( + fn check_shared_memory_index< + F: WasmFuncType, + T: WasmTableType, + M: WasmMemoryType, + G: WasmGlobalType, + >( &self, memory_index: u32, - resources: &dyn WasmModuleResources, + resources: &dyn WasmModuleResources< + FuncType = F, + TableType = T, + MemoryType = M, + GlobalType = G, + >, ) -> OperatorValidatorResult<()> { - if memory_index as usize >= resources.memories().len() { - return Err("no linear memories are present"); - } - if !resources.memories()[memory_index as usize].shared { - return Err("atomic accesses require shared memory"); + match resources.memory_at(memory_index) { + Some(memory) if !memory.is_shared() => { + return Err("atomic accesses require shared memory") + } + None => return Err("no linear memories are present"), + _ => Ok(()), } - Ok(()) } - fn check_memarg( + fn check_memarg( &self, memarg: &MemoryImmediate, max_align: u32, - resources: &dyn WasmModuleResources, + resources: &dyn WasmModuleResources< + FuncType = F, + TableType = T, + MemoryType = M, + GlobalType = G, + >, ) -> OperatorValidatorResult<()> { self.check_memory_index(0, resources)?; let align = memarg.flags; @@ -544,10 +608,20 @@ impl OperatorValidator { Ok(()) } - fn check_shared_memarg_wo_align( + fn check_shared_memarg_wo_align< + F: WasmFuncType, + T: WasmTableType, + M: WasmMemoryType, + G: WasmGlobalType, + >( &self, _: &MemoryImmediate, - resources: &dyn WasmModuleResources, + resources: &dyn WasmModuleResources< + FuncType = F, + TableType = T, + MemoryType = M, + GlobalType = G, + >, ) -> OperatorValidatorResult<()> { self.check_shared_memory_index(0, resources)?; Ok(()) @@ -560,10 +634,15 @@ impl OperatorValidator { Ok(()) } - fn check_block_type( + fn check_block_type( &self, ty: TypeOrFuncType, - resources: &dyn WasmModuleResources, + resources: &dyn WasmModuleResources< + FuncType = F, + TableType = T, + MemoryType = M, + GlobalType = G, + >, ) -> OperatorValidatorResult<()> { match ty { TypeOrFuncType::Type(Type::EmptyBlockType) @@ -575,43 +654,55 @@ impl OperatorValidator { self.check_reference_types_enabled() } TypeOrFuncType::Type(Type::V128) => self.check_simd_enabled(), - TypeOrFuncType::FuncType(idx) => { - let idx = idx as usize; - let types = resources.types(); - if idx >= types.len() { - return Err("type index out of bounds"); - } - let ty = &types[idx]; - if !self.config.enable_multi_value { - if ty.returns.len() > 1 { + TypeOrFuncType::FuncType(idx) => match resources.type_at(idx) { + None => Err("type index out of bounds"), + Some(ty) if !self.config.enable_multi_value => { + if ty.len_outputs() > 1 { return Err("blocks, loops, and ifs may only return at most one \ value when multi-value is not enabled"); } - if ty.params.len() > 0 { + if ty.len_inputs() > 0 { return Err("blocks, loops, and ifs accept no parameters \ when multi-value is not enabled"); } + Ok(()) } - Ok(()) - } + Some(_) => Ok(()), + }, _ => Err("invalid block return type"), } } - fn check_block_params( + fn check_block_params< + F: WasmFuncType, + T: WasmTableType, + M: WasmMemoryType, + G: WasmGlobalType, + >( &self, ty: TypeOrFuncType, - resources: &dyn WasmModuleResources, + resources: &dyn WasmModuleResources< + FuncType = F, + TableType = T, + MemoryType = M, + GlobalType = G, + >, skip: usize, ) -> OperatorValidatorResult<()> { if let TypeOrFuncType::FuncType(idx) = ty { - let func_ty = &resources.types()[idx as usize]; - let len = func_ty.params.len(); + let func_ty = resources + .type_at(idx) + // Note: This was an out-of-bounds memory access before + // the change to return `Option` at `type_at`. So + // I assumed that invalid indices at this point are + // bugs. + .expect("function type index is out of bounds"); + let len = func_ty.len_inputs(); self.check_frame_size(len + skip)?; - for i in 0..len { + for (i, ty) in wasm_func_type_inputs(func_ty).enumerate() { if !self .func_state - .assert_stack_type_at(len - 1 - i + skip, func_ty.params[i]) + .assert_stack_type_at(len - 1 - i + skip, ty.to_parser_type()) { return Err("stack operand type mismatch for block"); } @@ -655,10 +746,20 @@ impl OperatorValidator { Ok(Some(ty)) } - pub(crate) fn process_operator( + pub(crate) fn process_operator< + F: WasmFuncType, + T: WasmTableType, + M: WasmMemoryType, + G: WasmGlobalType, + >( &mut self, operator: &Operator, - resources: &dyn WasmModuleResources, + resources: &dyn WasmModuleResources< + FuncType = F, + TableType = T, + MemoryType = M, + GlobalType = G, + >, ) -> OperatorValidatorResult { if self.func_state.end_function { return Err("unexpected operator"); @@ -735,30 +836,43 @@ impl OperatorValidator { self.check_jump_from_block(depth, 0)?; self.func_state.start_dead_code() } - Operator::Call { function_index } => { - if function_index as usize >= resources.func_type_indices().len() { - return Err("function index out of bounds"); + Operator::Call { function_index } => match resources.func_type_id_at(function_index) { + Some(type_index) => { + let ty = resources + .type_at(type_index) + // Note: This was an out-of-bounds memory access before + // the change to return `Option` at `type_at`. So + // I assumed that invalid indices at this point are + // bugs. + .expect("function type index is out of bounds"); + self.check_operands(wasm_func_type_inputs(ty).map(WasmType::to_parser_type))?; + self.func_state.change_frame_with_types( + ty.len_inputs(), + wasm_func_type_outputs(ty).map(WasmType::to_parser_type), + )?; } - let type_index = resources.func_type_indices()[function_index as usize]; - let ty = &resources.types()[type_index as usize]; - self.check_operands(&ty.params)?; - self.func_state - .change_frame_with_types(ty.params.len(), &ty.returns)?; - } + None => return Err("function index out of bounds"), + }, Operator::CallIndirect { index, table_index } => { - if table_index as usize >= resources.tables().len() { + if resources.table_at(table_index).is_none() { return Err("table index out of bounds"); } - if index as usize >= resources.types().len() { - return Err("type index out of bounds"); + match resources.type_at(index) { + None => return Err("type index out of bounds"), + Some(ty) => { + let types = { + let mut types = Vec::with_capacity(ty.len_inputs() + 1); + types.extend(wasm_func_type_inputs(ty).map(WasmType::to_parser_type)); + types.push(Type::I32); + types + }; + self.check_operands(types.into_iter())?; + self.func_state.change_frame_with_types( + ty.len_inputs() + 1, + wasm_func_type_outputs(ty).map(WasmType::to_parser_type), + )?; + } } - let ty = &resources.types()[index as usize]; - let mut types = Vec::with_capacity(ty.params.len() + 1); - types.extend_from_slice(&ty.params); - types.push(Type::I32); - self.check_operands(&types)?; - self.func_state - .change_frame_with_types(ty.params.len() + 1, &ty.returns)?; } Operator::Drop => { self.check_frame_size(1)?; @@ -769,7 +883,7 @@ impl OperatorValidator { self.func_state.change_frame_after_select(ty)?; } Operator::TypedSelect { ty } => { - self.check_operands(&[Type::I32, ty, ty])?; + self.check_operands_3(Type::I32, ty, ty)?; self.func_state.change_frame_after_select(Some(ty))?; } Operator::LocalGet { local_index } => { @@ -796,22 +910,23 @@ impl OperatorValidator { self.func_state.change_frame_with_type(1, local_type)?; } Operator::GlobalGet { global_index } => { - if global_index as usize >= resources.globals().len() { + if let Some(ty) = resources.global_at(global_index) { + self.func_state + .change_frame_with_type(0, ty.content_type().to_parser_type())?; + } else { return Err("global index out of bounds"); - } - let ty = &resources.globals()[global_index as usize]; - self.func_state.change_frame_with_type(0, ty.content_type)?; + }; } Operator::GlobalSet { global_index } => { - if global_index as usize >= resources.globals().len() { + if let Some(ty) = resources.global_at(global_index) { + if !ty.is_mutable() { + return Err("global expected to be mutable"); + } + self.check_operands_1(ty.content_type().to_parser_type())?; + self.func_state.change_frame(1)?; + } else { return Err("global index out of bounds"); - } - let ty = &resources.globals()[global_index as usize]; - if !ty.mutable { - return Err("global expected to be mutable"); - } - self.check_operands_1(ty.content_type)?; - self.func_state.change_frame(1)?; + }; } Operator::I32Load { ref memarg } => { self.check_memarg(memarg, 2, resources)?; @@ -1287,7 +1402,7 @@ impl OperatorValidator { | Operator::I32AtomicRmw8CmpxchgU { ref memarg } => { self.check_threads_enabled()?; self.check_shared_memarg_wo_align(memarg, resources)?; - self.check_operands(&[Type::I32, Type::I32, Type::I32])?; + self.check_operands_3(Type::I32, Type::I32, Type::I32)?; self.func_state.change_frame_with_type(3, Type::I32)?; } Operator::I64AtomicRmwXchg { ref memarg } @@ -1305,7 +1420,7 @@ impl OperatorValidator { | Operator::I64AtomicRmw8CmpxchgU { ref memarg } => { self.check_threads_enabled()?; self.check_shared_memarg_wo_align(memarg, resources)?; - self.check_operands(&[Type::I32, Type::I64, Type::I64])?; + self.check_operands_3(Type::I32, Type::I64, Type::I64)?; self.func_state.change_frame_with_type(3, Type::I64)?; } Operator::AtomicNotify { ref memarg } => { @@ -1317,13 +1432,13 @@ impl OperatorValidator { Operator::I32AtomicWait { ref memarg } => { self.check_threads_enabled()?; self.check_shared_memarg_wo_align(memarg, resources)?; - self.check_operands(&[Type::I32, Type::I32, Type::I64])?; + self.check_operands_3(Type::I32, Type::I32, Type::I64)?; self.func_state.change_frame_with_type(3, Type::I32)?; } Operator::I64AtomicWait { ref memarg } => { self.check_threads_enabled()?; self.check_shared_memarg_wo_align(memarg, resources)?; - self.check_operands(&[Type::I32, Type::I64, Type::I64])?; + self.check_operands_3(Type::I32, Type::I64, Type::I64)?; self.func_state.change_frame_with_type(3, Type::I32)?; } Operator::AtomicFence { ref flags } => { @@ -1338,12 +1453,12 @@ impl OperatorValidator { } Operator::RefIsNull => { self.check_reference_types_enabled()?; - self.check_operands(&[Type::AnyRef])?; + self.check_operands_1(Type::AnyRef)?; self.func_state.change_frame_with_type(1, Type::I32)?; } Operator::RefFunc { function_index } => { self.check_reference_types_enabled()?; - if function_index as usize >= resources.func_type_indices().len() { + if resources.func_type_id_at(function_index).is_none() { return Err("function index out of bounds"); } self.func_state.change_frame_with_type(0, Type::AnyFunc)?; @@ -1593,7 +1708,7 @@ impl OperatorValidator { } Operator::V128Bitselect => { self.check_simd_enabled()?; - self.check_operands(&[Type::V128, Type::V128, Type::V128])?; + self.check_operands_3(Type::V128, Type::V128, Type::V128)?; self.func_state.change_frame_with_type(3, Type::V128)?; } Operator::I8x16AnyTrue @@ -1674,7 +1789,7 @@ impl OperatorValidator { return Err("segment index out of bounds"); } self.check_memory_index(0, resources)?; - self.check_operands(&[Type::I32, Type::I32, Type::I32])?; + self.check_operands_3(Type::I32, Type::I32, Type::I32)?; self.func_state.change_frame(3)?; } Operator::DataDrop { segment } => { @@ -1686,7 +1801,7 @@ impl OperatorValidator { Operator::MemoryCopy | Operator::MemoryFill => { self.check_bulk_memory_enabled()?; self.check_memory_index(0, resources)?; - self.check_operands(&[Type::I32, Type::I32, Type::I32])?; + self.check_operands_3(Type::I32, Type::I32, Type::I32)?; self.func_state.change_frame(3)?; } Operator::TableInit { segment, table } => { @@ -1697,10 +1812,10 @@ impl OperatorValidator { if table > 0 { self.check_reference_types_enabled()?; } - if table as usize >= resources.tables().len() { + if resources.table_at(table).is_none() { return Err("table index out of bounds"); } - self.check_operands(&[Type::I32, Type::I32, Type::I32])?; + self.check_operands_3(Type::I32, Type::I32, Type::I32)?; self.func_state.change_frame(3)?; } Operator::ElemDrop { segment } => { @@ -1717,55 +1832,55 @@ impl OperatorValidator { if src_table > 0 || dst_table > 0 { self.check_reference_types_enabled()?; } - if src_table as usize >= resources.tables().len() - || dst_table as usize >= resources.tables().len() + if resources.table_at(src_table).is_none() + || resources.table_at(dst_table).is_none() { return Err("table index out of bounds"); } - self.check_operands(&[Type::I32, Type::I32, Type::I32])?; + self.check_operands_3(Type::I32, Type::I32, Type::I32)?; self.func_state.change_frame(3)?; } Operator::TableGet { table } => { self.check_reference_types_enabled()?; - let ty = match resources.tables().get(table as usize) { - Some(ty) => ty.element_type, + let ty = match resources.table_at(table) { + Some(ty) => ty.element_type().to_parser_type(), None => return Err("table index out of bounds"), }; - self.check_operands(&[Type::I32])?; + self.check_operands_1(Type::I32)?; self.func_state.change_frame_with_type(1, ty)?; } Operator::TableSet { table } => { self.check_reference_types_enabled()?; - let ty = match resources.tables().get(table as usize) { - Some(ty) => ty.element_type, + let ty = match resources.table_at(table) { + Some(ty) => ty.element_type().to_parser_type(), None => return Err("table index out of bounds"), }; - self.check_operands(&[Type::I32, ty])?; + self.check_operands_2(Type::I32, ty)?; self.func_state.change_frame(2)?; } Operator::TableGrow { table } => { self.check_reference_types_enabled()?; - let ty = match resources.tables().get(table as usize) { - Some(ty) => ty.element_type, + let ty = match resources.table_at(table) { + Some(ty) => ty.element_type().to_parser_type(), None => return Err("table index out of bounds"), }; - self.check_operands(&[ty, Type::I32])?; + self.check_operands_2(ty, Type::I32)?; self.func_state.change_frame_with_type(2, Type::I32)?; } Operator::TableSize { table } => { self.check_reference_types_enabled()?; - if table as usize >= resources.tables().len() { + if resources.table_at(table).is_none() { return Err("table index out of bounds"); } self.func_state.change_frame_with_type(0, Type::I32)?; } Operator::TableFill { table } => { self.check_bulk_memory_enabled()?; - let ty = match resources.tables().get(table as usize) { - Some(ty) => ty.element_type, + let ty = match resources.table_at(table) { + Some(ty) => ty.element_type().to_parser_type(), None => return Err("table index out of bounds"), }; - self.check_operands(&[Type::I32, ty, Type::I32])?; + self.check_operands_3(Type::I32, ty, Type::I32)?; self.func_state.change_frame(3)?; } } diff --git a/src/validator.rs b/src/validator.rs index d1de5c9f..0133248d 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -29,13 +29,13 @@ use crate::primitives::{ Operator, ResizableLimits, Result, SectionCode, TableType, Type, }; -use crate::parser::{Parser, ParserInput, ParserState, WasmDecoder}; - use crate::operators_validator::{ is_subtype_supertype, FunctionEnd, OperatorValidator, OperatorValidatorConfig, - WasmModuleResources, DEFAULT_OPERATOR_VALIDATOR_CONFIG, + DEFAULT_OPERATOR_VALIDATOR_CONFIG, }; +use crate::parser::{Parser, ParserInput, ParserState, WasmDecoder}; use crate::{ElemSectionEntryTable, ElementItem}; +use crate::{WasmFuncType, WasmGlobalType, WasmMemoryType, WasmModuleResources, WasmTableType}; use crate::readers::FunctionBody; @@ -105,24 +105,29 @@ struct ValidatingParserResources { } impl<'a> WasmModuleResources for ValidatingParserResources { - fn types(&self) -> &[FuncType] { - &self.types + type FuncType = crate::FuncType; + type TableType = crate::TableType; + type MemoryType = crate::MemoryType; + type GlobalType = crate::GlobalType; + + fn type_at(&self, at: u32) -> Option<&Self::FuncType> { + self.types.get(at as usize) } - fn tables(&self) -> &[TableType] { - &self.tables + fn table_at(&self, at: u32) -> Option<&Self::TableType> { + self.tables.get(at as usize) } - fn memories(&self) -> &[MemoryType] { - &self.memories + fn memory_at(&self, at: u32) -> Option<&Self::MemoryType> { + self.memories.get(at as usize) } - fn globals(&self) -> &[GlobalType] { - &self.globals + fn global_at(&self, at: u32) -> Option<&Self::GlobalType> { + self.globals.get(at as usize) } - fn func_type_indices(&self) -> &[u32] { - &self.func_type_indices + fn func_type_id_at(&self, at: u32) -> Option { + self.func_type_indices.get(at as usize).copied() } fn element_count(&self) -> u32 { @@ -175,7 +180,14 @@ impl<'a> ValidatingParser<'a> { } } - pub fn get_resources(&self) -> &dyn WasmModuleResources { + pub fn get_resources( + &self, + ) -> &dyn WasmModuleResources< + FuncType = crate::FuncType, + TableType = crate::TableType, + MemoryType = crate::MemoryType, + GlobalType = crate::GlobalType, + > { &self.resources } @@ -775,7 +787,15 @@ impl<'b> ValidatingOperatorParser<'b> { /// } /// } /// ``` - pub fn next<'c>(&mut self, resources: &dyn WasmModuleResources) -> Result> + pub fn next<'c, F: WasmFuncType, T: WasmTableType, M: WasmMemoryType, G: WasmGlobalType>( + &mut self, + resources: &dyn WasmModuleResources< + FuncType = F, + TableType = T, + MemoryType = M, + GlobalType = G, + >, + ) -> Result> where 'b: 'c, { @@ -804,11 +824,21 @@ impl<'b> ValidatingOperatorParser<'b> { /// Test whether the given buffer contains a valid WebAssembly function. /// The resources parameter contains all needed data to validate the operators. -pub fn validate_function_body( +pub fn validate_function_body< + F: WasmFuncType, + T: WasmTableType, + M: WasmMemoryType, + G: WasmGlobalType, +>( bytes: &[u8], offset: usize, func_index: u32, - resources: &dyn WasmModuleResources, + resources: &dyn WasmModuleResources< + FuncType = F, + TableType = T, + MemoryType = M, + GlobalType = G, + >, operator_config: Option, ) -> Result<()> { let operator_config = operator_config.unwrap_or(DEFAULT_OPERATOR_VALIDATOR_CONFIG); @@ -841,8 +871,18 @@ pub fn validate_function_body( locals.push((count, ty)); } let operators_reader = function_body.get_operators_reader()?; - let func_type_index = resources.func_type_indices()[func_index as usize]; - let func_type = &resources.types()[func_type_index as usize]; + let func_type_index = resources + .func_type_id_at(func_index) + // Note: This was an out-of-bounds access before the change to return `Option` + // so I assumed it is considered a bug to access a non-existing function + // id here and went with panicking instead of returning a proper error. + .expect("the function index of the validated function itself is out of bounds"); + let func_type = resources + .type_at(func_type_index) + // Note: This was an out-of-bounds access before the change to return `Option` + // so I assumed it is considered a bug to access a non-existing function + // id here and went with panicking instead of returning a proper error. + .expect("the function type indexof the validated function itself is out of bounds"); let mut operator_validator = OperatorValidator::new(func_type, &locals, operator_config); let mut eof_found = false; let mut last_op = 0;