From 16fd81ec7d9b6d04cbcb2ff7a55f4aca86f5a16d Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Fri, 12 Sep 2025 10:02:06 -0700 Subject: [PATCH] wasmparser: reject function bodies larger than implementation limit. The implementation limits for JS embedders at [1] describe various upper bounds for entities in Wasm modules, including the size of a single function body in bytes. wasmparser is not strictly required to abide by these implementation limits because they are conventions for the JS embedding of Wasm rather than part of the core Wasm standard. However, it does seem to enforce other limits, such as the number of types or locals; this PR updates it to enforce function body size as well. This came up in bytecodealliance/wasmtime#11682, where a very large function (larger than the implementation limit) led to out-of-bounds SSA value numbers in Cranelift when they exceeded the range allowed by our data-structure bitpacking. Rather than doing major surgery to plumb the exact failure through all of Cranelift (including its public API being `Result`-ified on every builder interface) it seems better to have a single limit on the size of incoming functions. It turns out that the Wasm implementation limits were designed for just this purpose, so let's use them in our tooling as well. [1]: https://webassembly.github.io/spec/js-api/#limits --- crates/wasmparser/src/limits.rs | 4 +++- crates/wasmparser/src/validator.rs | 8 ++++++++ crates/wasmparser/tests/big-module.rs | 28 +++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/crates/wasmparser/src/limits.rs b/crates/wasmparser/src/limits.rs index 118629f894..157c3b236f 100644 --- a/crates/wasmparser/src/limits.rs +++ b/crates/wasmparser/src/limits.rs @@ -17,6 +17,8 @@ // The following limits are imposed by wasmparser on WebAssembly modules. // The limits are agreed upon with other engines for consistency. +// +// See https://webassembly.github.io/spec/js-api/#limits for details. pub const MAX_WASM_TYPES: usize = 1_000_000; pub const MAX_WASM_SUPERTYPES: usize = 1; pub const MAX_WASM_FUNCTIONS: usize = 1_000_000; @@ -26,7 +28,7 @@ pub const MAX_WASM_GLOBALS: usize = 1_000_000; pub const MAX_WASM_ELEMENT_SEGMENTS: usize = 100_000; pub const MAX_WASM_DATA_SEGMENTS: usize = 100_000; pub const MAX_WASM_STRING_SIZE: usize = 100_000; -pub const MAX_WASM_FUNCTION_SIZE: usize = 128 * 1024; +pub const MAX_WASM_FUNCTION_SIZE: usize = 7_654_321; pub const MAX_WASM_FUNCTION_LOCALS: u32 = 50000; pub const MAX_WASM_FUNCTION_PARAMS: usize = 1000; pub const MAX_WASM_FUNCTION_RETURNS: usize = 1000; diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs index a5c345e895..c675ece0f0 100644 --- a/crates/wasmparser/src/validator.rs +++ b/crates/wasmparser/src/validator.rs @@ -1006,6 +1006,14 @@ impl Validator { ) -> Result> { let offset = body.range().start; self.state.ensure_module("code", offset)?; + check_max( + 0, + u32::try_from(body.range().len()) + .expect("usize already validated to u32 during section-length decoding"), + MAX_WASM_FUNCTION_SIZE, + "function body size", + offset, + )?; let state = self.module.as_mut().unwrap(); diff --git a/crates/wasmparser/tests/big-module.rs b/crates/wasmparser/tests/big-module.rs index 9064eba88b..3ad2c90286 100644 --- a/crates/wasmparser/tests/big-module.rs +++ b/crates/wasmparser/tests/big-module.rs @@ -32,3 +32,31 @@ fn big_type_indices() { .validate_all(&wasm) .unwrap(); } + +#[test] +fn big_function_body() { + let mut module = Module::new(); + + let mut types = TypeSection::new(); + types.ty().function([], []); + module.section(&types); + let mut funcs = FunctionSection::new(); + funcs.function(0); + module.section(&funcs); + + let mut code = CodeSection::new(); + let mut body = Function::new([]); + // Function body larger than the 7_654_321-byte implementation + // limit. + for _ in 0..8_000_000 { + body.instructions().unreachable(); + } + body.instructions().end(); + code.function(&body); + module.section(&code); + + let wasm = module.finish(); + + let result = wasmparser::Validator::default().validate_all(&wasm); + assert!(result.is_err()); +}