diff --git a/crates/wasmi/src/engine/config.rs b/crates/wasmi/src/engine/config.rs index c47229b05f..e707e39ddd 100644 --- a/crates/wasmi/src/engine/config.rs +++ b/crates/wasmi/src/engine/config.rs @@ -1,4 +1,4 @@ -use super::StackLimits; +use super::{EngineLimits, StackLimits}; use core::{mem::size_of, num::NonZeroU64}; use wasmi_core::UntypedValue; use wasmparser::WasmFeatures; @@ -39,6 +39,8 @@ pub struct Config { fuel_costs: FuelCosts, /// The mode of Wasm to Wasmi bytecode compilation. compilation_mode: CompilationMode, + /// Enforced limits for Wasm module parsing and compilation. + limits: EngineLimits, } /// Type storing all kinds of fuel costs of instructions. @@ -180,6 +182,7 @@ impl Default for Config { consume_fuel: false, fuel_costs: FuelCosts::default(), compilation_mode: CompilationMode::default(), + limits: EngineLimits::default(), } } } @@ -352,6 +355,8 @@ impl Config { /// Sets the [`CompilationMode`] used for the [`Engine`]. /// + /// By default [`CompilationMode::Eager`] is used. + /// /// [`Engine`]: crate::Engine pub fn compilation_mode(&mut self, mode: CompilationMode) -> &mut Self { self.compilation_mode = mode; @@ -365,6 +370,23 @@ impl Config { self.compilation_mode } + /// Sets the [`EngineLimits`] enforced by the [`Engine`] for Wasm module parsing and compilation. + /// + /// By default no limits are enforced. + /// + /// [`Engine`]: crate::Engine + pub fn engine_limits(&mut self, limits: EngineLimits) -> &mut Self { + self.limits = limits; + self + } + + /// Returns the [`EngineLimits`] used for the [`Engine`]. + /// + /// [`Engine`]: crate::Engine + pub(crate) fn get_engine_limits(&self) -> &EngineLimits { + &self.limits + } + /// Returns the [`WasmFeatures`] represented by the [`Config`]. pub(crate) fn wasm_features(&self) -> WasmFeatures { WasmFeatures { diff --git a/crates/wasmi/src/engine/limits/engine.rs b/crates/wasmi/src/engine/limits/engine.rs new file mode 100644 index 0000000000..a1414e01d7 --- /dev/null +++ b/crates/wasmi/src/engine/limits/engine.rs @@ -0,0 +1,218 @@ +use core::fmt::{self, Display}; + +/// An error that can occur upon parsing or compiling a Wasm module when [`EngineLimits`] are set. +#[derive(Debug, Copy, Clone)] +pub enum EngineLimitsError { + /// When a Wasm module exceeds the global variable limit. + TooManyGlobals { limit: u32 }, + /// When a Wasm module exceeds the table limit. + TooManyTables { limit: u32 }, + /// When a Wasm module exceeds the function limit. + TooManyFunctions { limit: u32 }, + /// When a Wasm module exceeds the linear memory limit. + TooManyMemories { limit: u32 }, + /// When a Wasm module exceeds the element segment limit. + TooManyElementSegments { limit: u32 }, + /// When a Wasm module exceeds the data segment limit. + TooManyDataSegments { limit: u32 }, + /// When a Wasm module exceeds the function parameter limit. + TooManyParameters { limit: usize }, + /// When a Wasm module exceeds the function results limit. + TooManyResults { limit: usize }, + /// When a Wasm module exceeds the average bytes per function limit. + MinAvgBytesPerFunction { limit: u32, avg: u32 }, +} + +impl Display for EngineLimitsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::TooManyGlobals { limit } => write!( + f, + "the Wasm module exceeds the limit of {limit} global variables" + ), + Self::TooManyTables { limit } => { + write!(f, "the Wasm module exceeds the limit of {limit} tables") + } + Self::TooManyFunctions { limit } => { + write!(f, "the Wasm modules exceeds the limit of {limit} functions") + } + Self::TooManyMemories { limit } => { + write!(f, "the Wasm module exceeds the limit of {limit} memories") + } + Self::TooManyElementSegments { limit } => write!( + f, + "the Wasm module exceeds the limit of {limit} active element segments" + ), + Self::TooManyDataSegments { limit } => write!( + f, + "the Wasm module exceeds the limit of {limit} active data segments", + ), + Self::TooManyParameters { limit } => { + write!(f, "a function type exceeds the limit of {limit} parameters",) + } + Self::TooManyResults { limit } => { + write!(f, "a function type exceeds the limit of {limit} results",) + } + Self::MinAvgBytesPerFunction { limit, avg } => write!( + f, + "the Wasm module failed to meet the minumum average bytes per function of {limit}: \ + avg={avg}" + ), + } + } +} + +/// Stores customizable limits for the [`Engine`] when parsing or compiling Wasm modules. +/// +/// By default no limits are enforced. +/// +/// [`Engine`]: crate::Engine +#[derive(Debug, Default, Copy, Clone)] +pub struct EngineLimits { + /// Number of global variables a single Wasm module can have at most. + /// + /// # Note + /// + /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. + /// - `None` means the limit is not enforced. + /// + /// [`Module::new`]: crate::Module::new + /// [`Module::new_unchecked`]: crate::Module::new_unchecked + pub(crate) max_globals: Option, + /// Number of functions a single Wasm module can have at most. + /// + /// # Note + /// + /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. + /// - `None` means the limit is not enforced. + /// + /// [`Module::new`]: crate::Module::new + /// [`Module::new_unchecked`]: crate::Module::new_unchecked + pub(crate) max_functions: Option, + /// Number of tables a single Wasm module can have at most. + /// + /// # Note + /// + /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. + /// - This is only relevant if the Wasm `reference-types` proposal is enabled. + /// - `None` means the limit is not enforced. + /// + /// [`Module::new`]: crate::Module::new + /// [`Module::new_unchecked`]: crate::Module::new_unchecked + pub(crate) max_tables: Option, + /// Number of table element segments a single Wasm module can have at most. + /// + /// # Note + /// + /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. + /// - This is only relevant if the Wasm `reference-types` proposal is enabled. + /// - `None` means the limit is not enforced. + /// + /// [`Module::new`]: crate::Module::new + /// [`Module::new_unchecked`]: crate::Module::new_unchecked + pub(crate) max_element_segments: Option, + /// Number of linear memories a single Wasm module can have. + /// + /// # Note + /// + /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. + /// - This is only relevant if the Wasm `multi-memories` proposal is enabled + /// which is not supported in Wasmi at the time of writing this comment. + /// - `None` means the limit is not enforced. + /// + /// [`Module::new`]: crate::Module::new + /// [`Module::new_unchecked`]: crate::Module::new_unchecked + pub(crate) max_memories: Option, + /// Number of linear memory data segments a single Wasm module can have at most. + /// + /// # Note + /// + /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. + /// - This is only relevant if the Wasm `reference-types` proposal is enabled. + /// - `None` means the limit is not enforced. + /// + /// [`Module::new`]: crate::Module::new + /// [`Module::new_unchecked`]: crate::Module::new_unchecked + pub(crate) max_data_segments: Option, + /// Limits the number of parameter of all functions and control structures. + /// + /// # Note + /// + /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. + /// - `None` means the limit is not enforced. + /// + /// [`Engine`]: crate::Engine + /// [`Module::new`]: crate::Module::new + /// [`Module::new_unchecked`]: crate::Module::new_unchecked + pub(crate) max_params: Option, + /// Limits the number of results of all functions and control structures. + /// + /// # Note + /// + /// - This is only relevant if the Wasm `multi-value` proposal is enabled. + /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. + /// - `None` means the limit is not enforced. + /// + /// [`Engine`]: crate::Engine + /// [`Module::new`]: crate::Module::new + /// [`Module::new_unchecked`]: crate::Module::new_unchecked + pub(crate) max_results: Option, + /// Minimum number of bytes a function must have on average. + /// + /// # Note + /// + /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. + /// - This limitation might seem arbitrary but is important to defend against + /// malicious inputs targeting lazy compilation. + /// - `None` means the limit is not enforced. + /// + /// [`Module::new`]: crate::Module::new + /// [`Module::new_unchecked`]: crate::Module::new_unchecked + pub(crate) min_avg_bytes_per_function: Option, +} + +/// The limit for average bytes per function limit and the threshold at which it is enforced. +#[derive(Debug, Copy, Clone)] +pub struct AvgBytesPerFunctionLimit { + /// The number of Wasm module bytes at which the limit is actually enforced. + /// + /// This represents the total number of bytes of all Wasm function bodies in the Wasm module combined. + /// + /// # Note + /// + /// - A `req_funcs_bytes` of 0 always enforces the `min_avg_bytes_per_function` limit. + /// - The `req_funcs_bytes` field exists to filter out small Wasm modules + /// that cannot seriously be used to attack the Wasmi compilation. + pub req_funcs_bytes: u32, + /// The minimum number of bytes a function must have on average. + pub min_avg_bytes_per_function: u32, +} + +impl EngineLimits { + /// A strict set of limits that makes use of Wasmi implementation details. + /// + /// This set of strict enforced rules can be used by Wasmi users in order + /// to safeguard themselves against malicious actors trying to attack the Wasmi + /// compilation procedures. + pub fn strict() -> Self { + Self { + max_globals: Some(1000), + max_functions: Some(10_000), + max_tables: Some(100), + max_element_segments: Some(1000), + max_memories: Some(1), + max_data_segments: Some(1000), + max_params: Some(32), + max_results: Some(32), + min_avg_bytes_per_function: Some(AvgBytesPerFunctionLimit { + // If all function bodies combined use a total of at least 1000 bytes + // the average bytes per function body limit is enforced. + req_funcs_bytes: 1000, + // Compiled and optimized Wasm modules usually average out on 100-2500 + // bytes per Wasm function. Thus the chosen limit is way below this threshold + // and should not be exceeded for non-malicous Wasm modules. + min_avg_bytes_per_function: 40, + }), + } + } +} diff --git a/crates/wasmi/src/engine/limits/mod.rs b/crates/wasmi/src/engine/limits/mod.rs new file mode 100644 index 0000000000..f111c1c0bb --- /dev/null +++ b/crates/wasmi/src/engine/limits/mod.rs @@ -0,0 +1,10 @@ +mod engine; +mod stack; + +#[cfg(test)] +mod tests; + +pub use self::{ + engine::{EngineLimits, EngineLimitsError}, + stack::StackLimits, +}; diff --git a/crates/wasmi/src/engine/limits.rs b/crates/wasmi/src/engine/limits/stack.rs similarity index 100% rename from crates/wasmi/src/engine/limits.rs rename to crates/wasmi/src/engine/limits/stack.rs diff --git a/crates/wasmi/src/engine/limits/tests.rs b/crates/wasmi/src/engine/limits/tests.rs new file mode 100644 index 0000000000..ca71dd35d6 --- /dev/null +++ b/crates/wasmi/src/engine/limits/tests.rs @@ -0,0 +1,503 @@ +use self::engine::AvgBytesPerFunctionLimit; +use super::*; +use crate::{error::ErrorKind, Config, Engine, Error, Module}; +use std::vec::Vec; + +/// Converts the given `.wat` into `.wasm`. +fn wat2wasm(wat: &str) -> Vec { + wat::parse_str(wat).unwrap() +} + +/// Parses and returns the Wasm module `wasm` with the given [`EngineLimits`] `limits`. +fn parse_with(wasm: &str, limits: EngineLimits) -> Result { + let wasm = wat2wasm(wasm); + let mut config = Config::default(); + config.engine_limits(limits); + let engine = Engine::new(&config); + Module::new(&engine, &wasm[..]) +} + +#[test] +fn max_globals_ok() { + let wasm = " + (module + (global i32 (i32.const 1)) + (global i32 (i32.const 2)) + ) + "; + parse_with( + wasm, + EngineLimits { + max_globals: Some(2), + ..EngineLimits::default() + }, + ) + .unwrap(); +} + +#[test] +fn max_globals_err() { + let wasm = " + (module + (global i32 (i32.const 1)) + (global i32 (i32.const 2)) + (global i32 (i32.const 3)) + ) + "; + let limits = EngineLimits { + max_globals: Some(2), + ..EngineLimits::default() + }; + assert!(matches!( + parse_with(wasm, limits).unwrap_err().kind(), + ErrorKind::Limits(EngineLimitsError::TooManyGlobals { limit: 2 }), + )) +} + +#[test] +fn max_functions_ok() { + let wasm = " + (module + (func) + (func) + ) + "; + parse_with( + wasm, + EngineLimits { + max_functions: Some(2), + ..EngineLimits::default() + }, + ) + .unwrap(); +} + +#[test] +fn max_functions_err() { + let wasm = " + (module + (func) + (func) + (func) + ) + "; + let limits = EngineLimits { + max_functions: Some(2), + ..EngineLimits::default() + }; + assert!(matches!( + parse_with(wasm, limits).unwrap_err().kind(), + ErrorKind::Limits(EngineLimitsError::TooManyFunctions { limit: 2 }), + )) +} + +#[test] +fn max_tables_ok() { + let wasm = " + (module + (table 0 funcref) + (table 0 funcref) + ) + "; + parse_with( + wasm, + EngineLimits { + max_tables: Some(2), + ..EngineLimits::default() + }, + ) + .unwrap(); +} + +#[test] +fn max_tables_err() { + let wasm = " + (module + (table 0 funcref) + (table 0 funcref) + (table 0 funcref) + ) + "; + let limits = EngineLimits { + max_tables: Some(2), + ..EngineLimits::default() + }; + assert!(matches!( + parse_with(wasm, limits).unwrap_err().kind(), + ErrorKind::Limits(EngineLimitsError::TooManyTables { limit: 2 }), + )) +} + +#[test] +#[ignore] // TODO: remove once multi-memories are supported in Wasmi +fn max_memories_ok() { + let wasm = " + (module + (memory 0) + (memory 0) + ) + "; + parse_with( + wasm, + EngineLimits { + max_memories: Some(2), + ..EngineLimits::default() + }, + ) + .unwrap(); +} + +#[test] +#[ignore] // TODO: remove once multi-memories are supported in Wasmi +fn max_memories_err() { + let wasm = " + (module + (memory 0) + (memory 0) + (memory 0) + ) + "; + let limits = EngineLimits { + max_memories: Some(2), + ..EngineLimits::default() + }; + assert!(matches!( + parse_with(wasm, limits).unwrap_err().kind(), + ErrorKind::Limits(EngineLimitsError::TooManyMemories { limit: 2 }), + )) +} + +#[test] +fn max_element_segments_ok() { + let wasm = " + (module + (table $t 0 funcref) + (func $f) + (elem (table $t) (i32.const 0) funcref (ref.func $f) (ref.null func)) + (elem (table $t) (i32.const 1) funcref (ref.func $f) (ref.null func)) + ) + "; + parse_with( + wasm, + EngineLimits { + max_element_segments: Some(2), + ..EngineLimits::default() + }, + ) + .unwrap(); +} + +#[test] +fn max_element_segments_err() { + let wasm = " + (module + (table $t 0 funcref) + (func $f) + (elem (table $t) (i32.const 0) funcref (ref.func $f) (ref.null func)) + (elem (table $t) (i32.const 1) funcref (ref.func $f) (ref.null func)) + (elem (table $t) (i32.const 2) funcref (ref.func $f) (ref.null func)) + ) + "; + let limits = EngineLimits { + max_element_segments: Some(2), + ..EngineLimits::default() + }; + assert!(matches!( + parse_with(wasm, limits).unwrap_err().kind(), + ErrorKind::Limits(EngineLimitsError::TooManyElementSegments { limit: 2 }), + )) +} + +#[test] +fn max_data_segments_ok() { + let wasm = " + (module + (memory $m 0) + (data (memory $m) (i32.const 0) \"abc\") + (data (memory $m) (i32.const 1) \"abc\") + ) + "; + parse_with( + wasm, + EngineLimits { + max_data_segments: Some(2), + ..EngineLimits::default() + }, + ) + .unwrap(); +} + +#[test] +fn max_data_segments_err() { + let wasm = " + (module + (memory $m 0) + (data (memory $m) (i32.const 0) \"abc\") + (data (memory $m) (i32.const 1) \"abc\") + (data (memory $m) (i32.const 2) \"abc\") + ) + "; + let limits = EngineLimits { + max_data_segments: Some(2), + ..EngineLimits::default() + }; + assert!(matches!( + parse_with(wasm, limits).unwrap_err().kind(), + ErrorKind::Limits(EngineLimitsError::TooManyDataSegments { limit: 2 }), + )) +} + +#[test] +fn max_params_func_ok() { + let wasm = " + (module + (func (param i32 i32)) + ) + "; + parse_with( + wasm, + EngineLimits { + max_params: Some(2), + ..EngineLimits::default() + }, + ) + .unwrap(); +} + +#[test] +fn max_params_func_err() { + let wasm = " + (module + (func (param i32 i32 i32)) + ) + "; + let limits = EngineLimits { + max_params: Some(2), + ..EngineLimits::default() + }; + assert!(matches!( + parse_with(wasm, limits).unwrap_err().kind(), + ErrorKind::Limits(EngineLimitsError::TooManyParameters { limit: 2 }), + )) +} + +#[test] +fn max_params_control_ok() { + let wasm = " + (module + (func (param i32) + (local.get 0) + (local.get 0) + (block (param i32 i32) + (drop) + (drop) + ) + ) + ) + "; + parse_with( + wasm, + EngineLimits { + max_params: Some(2), + ..EngineLimits::default() + }, + ) + .unwrap(); +} + +#[test] +fn max_params_control_err() { + let wasm = " + (module + (func (param i32) + (local.get 0) + (local.get 0) + (block (param i32 i32 i32) + (drop) + (drop) + (drop) + ) + ) + ) + "; + let limits = EngineLimits { + max_params: Some(2), + ..EngineLimits::default() + }; + assert!(matches!( + parse_with(wasm, limits).unwrap_err().kind(), + ErrorKind::Limits(EngineLimitsError::TooManyParameters { limit: 2 }), + )) +} + +#[test] +fn max_results_func_ok() { + let wasm = " + (module + (func (result i32 i32) + (i32.const 1) + (i32.const 2) + ) + ) + "; + parse_with( + wasm, + EngineLimits { + max_results: Some(2), + ..EngineLimits::default() + }, + ) + .unwrap(); +} + +#[test] +fn max_results_func_err() { + let wasm = " + (module + (func (result i32 i32 i32) + (i32.const 1) + (i32.const 2) + (i32.const 3) + ) + ) + "; + let limits = EngineLimits { + max_results: Some(2), + ..EngineLimits::default() + }; + assert!(matches!( + parse_with(wasm, limits).unwrap_err().kind(), + ErrorKind::Limits(EngineLimitsError::TooManyResults { limit: 2 }), + )) +} + +#[test] +fn max_results_control_ok() { + let wasm = " + (module + (func + (block (result i32 i32) + (i32.const 1) + (i32.const 2) + ) + (drop) + (drop) + ) + ) + "; + parse_with( + wasm, + EngineLimits { + max_results: Some(2), + ..EngineLimits::default() + }, + ) + .unwrap(); +} + +#[test] +fn max_results_control_err() { + let wasm = " + (module + (func + (block (result i32 i32 i32) + (i32.const 1) + (i32.const 2) + (i32.const 3) + ) + (drop) + (drop) + (drop) + ) + ) + "; + let limits = EngineLimits { + max_results: Some(2), + ..EngineLimits::default() + }; + assert!(matches!( + parse_with(wasm, limits).unwrap_err().kind(), + ErrorKind::Limits(EngineLimitsError::TooManyResults { limit: 2 }), + )) +} + +#[test] +fn min_avg_code_bytes_ok() { + let wasm = " + (module + (func + (nop) + (nop) + (nop) + ) + (func + (nop) + (nop) + (nop) + ) + ) + "; + parse_with( + wasm, + EngineLimits { + min_avg_bytes_per_function: Some(AvgBytesPerFunctionLimit { + req_funcs_bytes: 0, + min_avg_bytes_per_function: 6, + }), + ..EngineLimits::default() + }, + ) + .unwrap(); +} + +#[test] +fn min_avg_code_bytes_err() { + let wasm = " + (module + (func + (nop) + (nop) + ) + (func + (nop) + (nop) + ) + ) + "; + let limits = EngineLimits { + min_avg_bytes_per_function: Some(AvgBytesPerFunctionLimit { + req_funcs_bytes: 0, + min_avg_bytes_per_function: 6, + }), + ..EngineLimits::default() + }; + std::println!("{:?}", parse_with(wasm, limits).unwrap_err()); + assert!(matches!( + parse_with(wasm, limits).unwrap_err().kind(), + ErrorKind::Limits(EngineLimitsError::MinAvgBytesPerFunction { limit: 6, avg: 5 }), + )) +} + +#[test] +fn min_avg_code_bytes_ok_threshold() { + let wasm = " + (module + (func + (nop) + (nop) + ) + (func + (nop) + (nop) + ) + ) + "; + let limits = EngineLimits { + min_avg_bytes_per_function: Some(AvgBytesPerFunctionLimit { + req_funcs_bytes: 12, + min_avg_bytes_per_function: 6, + }), + ..EngineLimits::default() + }; + parse_with(wasm, limits).unwrap(); +} diff --git a/crates/wasmi/src/engine/mod.rs b/crates/wasmi/src/engine/mod.rs index e57fba661f..1e8e9ebb30 100644 --- a/crates/wasmi/src/engine/mod.rs +++ b/crates/wasmi/src/engine/mod.rs @@ -37,7 +37,7 @@ pub(crate) use self::{ pub use self::{ code_map::CompiledFunc, config::{CompilationMode, Config}, - limits::StackLimits, + limits::{EngineLimits, EngineLimitsError, StackLimits}, resumable::{ResumableCall, ResumableInvocation, TypedResumableCall, TypedResumableInvocation}, traits::{CallParams, CallResults}, translator::{Instr, TranslationError}, diff --git a/crates/wasmi/src/error.rs b/crates/wasmi/src/error.rs index 1002cca396..7101a1468c 100644 --- a/crates/wasmi/src/error.rs +++ b/crates/wasmi/src/error.rs @@ -1,4 +1,5 @@ use super::errors::{ + EngineLimitsError, FuelError, FuncError, GlobalError, @@ -167,6 +168,8 @@ pub enum ErrorKind { Wasm(WasmError), /// Encountered when there is a Wasm to Wasmi translation error. Translation(TranslationError), + /// Encountered when an enforced limit is exceeded. + Limits(EngineLimitsError), } impl ErrorKind { @@ -231,6 +234,7 @@ impl Display for ErrorKind { Self::Read(error) => Display::fmt(error, f), Self::Wasm(error) => Display::fmt(error, f), Self::Translation(error) => Display::fmt(error, f), + Self::Limits(error) => Display::fmt(error, f), } } } @@ -260,6 +264,7 @@ impl_from! { impl From for Error::Read; impl From for Error::Fuel; impl From for Error::Func; + impl From for Error::Limits; } /// An error that can occur upon `memory.grow` or `table.grow`. diff --git a/crates/wasmi/src/lib.rs b/crates/wasmi/src/lib.rs index 405463287a..75395fd1a9 100644 --- a/crates/wasmi/src/lib.rs +++ b/crates/wasmi/src/lib.rs @@ -109,6 +109,7 @@ pub use wasmi_core as core; /// Defines some errors that may occur upon interaction with Wasmi. pub mod errors { pub use super::{ + engine::EngineLimitsError, error::ErrorKind, func::FuncError, global::GlobalError, @@ -125,6 +126,7 @@ pub use self::{ CompilationMode, Config, Engine, + EngineLimits, ResumableCall, ResumableInvocation, StackLimits, diff --git a/crates/wasmi/src/module/parser.rs b/crates/wasmi/src/module/parser.rs index 575a9b2770..37e7d663a2 100644 --- a/crates/wasmi/src/module/parser.rs +++ b/crates/wasmi/src/module/parser.rs @@ -11,7 +11,14 @@ use super::{ ModuleHeader, Read, }; -use crate::{engine::CompiledFunc, Engine, Error, FuncType, MemoryType, TableType}; +use crate::{ + engine::{CompiledFunc, EngineLimitsError}, + Engine, + Error, + FuncType, + MemoryType, + TableType, +}; use core::ops::Range; use std::{boxed::Box, vec::Vec}; use wasmparser::{ @@ -194,8 +201,8 @@ impl ModuleParser { Payload::DataCountSection { count, range } => { self.process_data_count(count, range) } - Payload::CodeSectionStart { count, range, .. } => { - self.process_code_start(count, range)?; + Payload::CodeSectionStart { count, range, size } => { + self.process_code_start(count, range, size)?; buffer.drain(..consumed); break; } @@ -355,8 +362,20 @@ impl ModuleParser { header: &mut ModuleHeaderBuilder, ) -> Result<(), Error> { self.validator.type_section(§ion)?; - let func_types = section.into_iter().map(|result| match result? { - wasmparser::Type::Func(ty) => Ok(FuncType::from_wasmparser(ty)), + let limits = self.engine.config().get_engine_limits(); + let func_types = section.into_iter().map(|result| { + let wasmparser::Type::Func(ty) = result?; + if let Some(limit) = limits.max_params { + if ty.params().len() > limit { + return Err(Error::from(EngineLimitsError::TooManyParameters { limit })); + } + } + if let Some(limit) = limits.max_results { + if ty.results().len() > limit { + return Err(Error::from(EngineLimitsError::TooManyResults { limit })); + } + } + Ok(FuncType::from_wasmparser(ty)) }); header.push_func_types(func_types)?; Ok(()) @@ -414,6 +433,11 @@ impl ModuleParser { section: FunctionSectionReader, header: &mut ModuleHeaderBuilder, ) -> Result<(), Error> { + if let Some(limit) = self.engine.config().get_engine_limits().max_functions { + if section.count() > limit { + return Err(Error::from(EngineLimitsError::TooManyFunctions { limit })); + } + } self.validator.function_section(§ion)?; let funcs = section .into_iter() @@ -436,6 +460,11 @@ impl ModuleParser { section: TableSectionReader, header: &mut ModuleHeaderBuilder, ) -> Result<(), Error> { + if let Some(limit) = self.engine.config().get_engine_limits().max_tables { + if section.count() > limit { + return Err(Error::from(EngineLimitsError::TooManyTables { limit })); + } + } self.validator.table_section(§ion)?; let tables = section .into_iter() @@ -458,6 +487,11 @@ impl ModuleParser { section: MemorySectionReader, header: &mut ModuleHeaderBuilder, ) -> Result<(), Error> { + if let Some(limit) = self.engine.config().get_engine_limits().max_memories { + if section.count() > limit { + return Err(Error::from(EngineLimitsError::TooManyMemories { limit })); + } + } self.validator.memory_section(§ion)?; let memories = section .into_iter() @@ -490,6 +524,11 @@ impl ModuleParser { section: GlobalSectionReader, header: &mut ModuleHeaderBuilder, ) -> Result<(), Error> { + if let Some(limit) = self.engine.config().get_engine_limits().max_globals { + if section.count() > limit { + return Err(Error::from(EngineLimitsError::TooManyGlobals { limit })); + } + } self.validator.global_section(§ion)?; let globals = section .into_iter() @@ -557,6 +596,18 @@ impl ModuleParser { section: ElementSectionReader, header: &mut ModuleHeaderBuilder, ) -> Result<(), Error> { + if let Some(limit) = self + .engine + .config() + .get_engine_limits() + .max_element_segments + { + if section.count() > limit { + return Err(Error::from(EngineLimitsError::TooManyElementSegments { + limit, + })); + } + } self.validator.element_section(§ion)?; let segments = section .into_iter() @@ -572,6 +623,13 @@ impl ModuleParser { /// This is part of the bulk memory operations Wasm proposal and not yet supported /// by Wasmi. fn process_data_count(&mut self, count: u32, range: Range) -> Result<(), Error> { + if let Some(limit) = self.engine.config().get_engine_limits().max_data_segments { + if count > limit { + return Err(Error::from(EngineLimitsError::TooManyDataSegments { + limit, + })); + } + } self.validator .data_count_section(count, &range) .map_err(Into::into) @@ -591,6 +649,13 @@ impl ModuleParser { section: DataSectionReader, builder: &mut ModuleBuilder, ) -> Result<(), Error> { + if let Some(limit) = self.engine.config().get_engine_limits().max_data_segments { + if section.count() > limit { + return Err(Error::from(EngineLimitsError::TooManyDataSegments { + limit, + })); + } + } self.validator.data_section(§ion)?; let segments = section .into_iter() @@ -610,7 +675,30 @@ impl ModuleParser { /// # Errors /// /// If the code start section fails to validate. - fn process_code_start(&mut self, count: u32, range: Range) -> Result<(), Error> { + fn process_code_start( + &mut self, + count: u32, + range: Range, + size: u32, + ) -> Result<(), Error> { + let engine_limits = self.engine.config().get_engine_limits(); + if let Some(limit) = engine_limits.max_functions { + if count > limit { + return Err(Error::from(EngineLimitsError::TooManyFunctions { limit })); + } + } + if let Some(limit) = engine_limits.min_avg_bytes_per_function { + if size >= limit.req_funcs_bytes { + let limit = limit.min_avg_bytes_per_function; + let avg = size / count; + if avg < limit { + return Err(Error::from(EngineLimitsError::MinAvgBytesPerFunction { + limit, + avg, + })); + } + } + } self.validator.code_section_start(count, &range)?; Ok(()) }