From 9aea77ef98172a0246cc8a97a95d2b1973af622b Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 15 Jan 2024 13:43:59 +0100 Subject: [PATCH 1/6] Count sum of all params over all functions --- packages/vm/src/compatibility.rs | 30 ++++++++++++++++++++++++++---- packages/vm/src/parsed_wasm.rs | 22 +++++++++++++++++++++- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/vm/src/compatibility.rs b/packages/vm/src/compatibility.rs index 77719e5c3d..125d6499d9 100644 --- a/packages/vm/src/compatibility.rs +++ b/packages/vm/src/compatibility.rs @@ -71,9 +71,9 @@ const TABLE_SIZE_LIMIT: u32 = 2500; // entries /// when a user accidentally includes wasm-bindgen, they get a bunch of unsupported imports. const MAX_IMPORTS: usize = 100; -const MAX_FUNCTIONS: usize = 10000; +const MAX_FUNCTIONS: usize = 1000000; // FIXME: reset to normal -const MAX_FUNCTION_PARAMS: usize = 50; +const MAX_FUNCTION_PARAMS: usize = 50_000000; // FIXME: reset to normal const MAX_FUNCTION_RESULTS: usize = 1; @@ -89,6 +89,26 @@ pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet) -> check_wasm_capabilities(&module, available_capabilities)?; check_wasm_functions(&module)?; + // Sum of all params over all functions + let total_params_count = + module + .type_usage + .iter() + .fold(0usize, |acc, (type_index, type_used_count)| { + let params = module + .type_params + .get(&type_index) + .expect("Found a function signature that is used with no known params count"); + acc + type_used_count * params + }); + + let bounds = core::cmp::max(module.max_func_params * module.function_count, 1); // >= 1 + let utilization = (100f64 * total_params_count as f64) / bounds as f64; // in percent + let function_count = module.function_count; + let max_params = module.max_func_params; + eprintln!("Functions: {function_count}; Max params: {max_params}; Total params count: {total_params_count}; Utilization: {utilization:.2}%"); + // eprintln!("Parsed module: {module:?}"); + Ok(()) } @@ -257,23 +277,25 @@ fn check_wasm_functions(module: &ParsedWasm) -> VmResult<()> { #[cfg(test)] mod tests { use super::*; - use crate::errors::VmError; + use crate::{capabilities_from_csv, errors::VmError}; static CONTRACT_0_7: &[u8] = include_bytes!("../testdata/hackatom_0.7.wasm"); static CONTRACT_0_12: &[u8] = include_bytes!("../testdata/hackatom_0.12.wasm"); static CONTRACT_0_14: &[u8] = include_bytes!("../testdata/hackatom_0.14.wasm"); static CONTRACT_0_15: &[u8] = include_bytes!("../testdata/hackatom_0.15.wasm"); static CONTRACT: &[u8] = include_bytes!("../testdata/hackatom.wasm"); + static CYBERPUNK: &[u8] = include_bytes!("../testdata/cyberpunk.wasm"); static CONTRACT_RUST_170: &[u8] = include_bytes!("../testdata/cyberpunk_rust170.wasm"); fn default_capabilities() -> HashSet { - ["staking".to_string()].into_iter().collect() + capabilities_from_csv("cosmwasm_1_1,cosmwasm_1_2,iterator,staking,stargate") } #[test] fn check_wasm_passes_for_latest_contract() { // this is our reference check, must pass check_wasm(CONTRACT, &default_capabilities()).unwrap(); + check_wasm(CYBERPUNK, &default_capabilities()).unwrap(); } #[test] diff --git a/packages/vm/src/parsed_wasm.rs b/packages/vm/src/parsed_wasm.rs index 37e5041899..99c73a22ef 100644 --- a/packages/vm/src/parsed_wasm.rs +++ b/packages/vm/src/parsed_wasm.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use wasmer::wasmparser::{ Export, Import, MemoryType, Parser, Payload, TableType, Type, ValidPayload, Validator, WasmFeatures, @@ -16,6 +18,10 @@ pub struct ParsedWasm<'a> { pub memories: Vec, pub function_count: usize, pub type_count: u32, + /// Counts how often each function signature is used by a function + pub type_usage: HashMap, + /// How many parameters a type has + pub type_params: HashMap, pub max_func_params: usize, pub max_func_results: usize, } @@ -41,6 +47,8 @@ impl<'a> ParsedWasm<'a> { memories: vec![], function_count: 0, type_count: 0, + type_usage: HashMap::default(), + type_params: HashMap::default(), max_func_params: 0, max_func_results: 0, }; @@ -61,10 +69,12 @@ impl<'a> ParsedWasm<'a> { match p { Payload::TypeSection(t) => { this.type_count = t.get_count(); - for t_res in t { + for (type_index, t_res) in t.into_iter().enumerate() { let ty: Type = t_res?; match ty { Type::Func(ft) => { + this.type_params.insert(type_index, ft.params().len()); + this.max_func_params = core::cmp::max(ft.params().len(), this.max_func_params); this.max_func_results = @@ -73,6 +83,16 @@ impl<'a> ParsedWasm<'a> { } } } + Payload::FunctionSection(section) => { + for a in section { + let type_index = a? as usize; + if let Some(value) = this.type_usage.get_mut(&(type_index)) { + *value += 1; + } else { + this.type_usage.insert(type_index, 1); + } + } + } Payload::Version { num, .. } => this.version = num, Payload::ImportSection(i) => { this.imports = i.into_iter().collect::, _>>()?; From d77b1102e53e575dd14ddef6cc39c8ccac6416f2 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Thu, 18 Jan 2024 14:56:26 +0100 Subject: [PATCH 2/6] Restrict total number of function params --- packages/vm/src/compatibility.rs | 33 +++++++++++--------------------- packages/vm/src/parsed_wasm.rs | 26 ++++++++++++++++--------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/packages/vm/src/compatibility.rs b/packages/vm/src/compatibility.rs index 125d6499d9..960c4fbfd5 100644 --- a/packages/vm/src/compatibility.rs +++ b/packages/vm/src/compatibility.rs @@ -71,9 +71,11 @@ const TABLE_SIZE_LIMIT: u32 = 2500; // entries /// when a user accidentally includes wasm-bindgen, they get a bunch of unsupported imports. const MAX_IMPORTS: usize = 100; -const MAX_FUNCTIONS: usize = 1000000; // FIXME: reset to normal +const MAX_FUNCTIONS: usize = 30000; -const MAX_FUNCTION_PARAMS: usize = 50_000000; // FIXME: reset to normal +const MAX_FUNCTION_PARAMS: usize = 150; + +const MAX_TOTAL_FUNCTION_PARAMS: usize = 7_000; const MAX_FUNCTION_RESULTS: usize = 1; @@ -89,26 +91,6 @@ pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet) -> check_wasm_capabilities(&module, available_capabilities)?; check_wasm_functions(&module)?; - // Sum of all params over all functions - let total_params_count = - module - .type_usage - .iter() - .fold(0usize, |acc, (type_index, type_used_count)| { - let params = module - .type_params - .get(&type_index) - .expect("Found a function signature that is used with no known params count"); - acc + type_used_count * params - }); - - let bounds = core::cmp::max(module.max_func_params * module.function_count, 1); // >= 1 - let utilization = (100f64 * total_params_count as f64) / bounds as f64; // in percent - let function_count = module.function_count; - let max_params = module.max_func_params; - eprintln!("Functions: {function_count}; Max params: {max_params}; Total params count: {total_params_count}; Utilization: {utilization:.2}%"); - // eprintln!("Parsed module: {module:?}"); - Ok(()) } @@ -271,6 +253,13 @@ fn check_wasm_functions(module: &ParsedWasm) -> VmResult<()> { "Wasm contract contains function with more than {MAX_FUNCTION_RESULTS} results" ))); } + + if module.total_func_params > MAX_TOTAL_FUNCTION_PARAMS { + return Err(VmError::static_validation_err(format!( + "Wasm contract contains more than {MAX_TOTAL_FUNCTION_PARAMS} function parameters in total" + ))); + } + Ok(()) } diff --git a/packages/vm/src/parsed_wasm.rs b/packages/vm/src/parsed_wasm.rs index 99c73a22ef..d53c57f885 100644 --- a/packages/vm/src/parsed_wasm.rs +++ b/packages/vm/src/parsed_wasm.rs @@ -5,7 +5,7 @@ use wasmer::wasmparser::{ WasmFeatures, }; -use crate::VmResult; +use crate::{VmError, VmResult}; /// A parsed and validated wasm module. /// It keeps track of the parts that are important for our static analysis and compatibility checks. @@ -18,12 +18,14 @@ pub struct ParsedWasm<'a> { pub memories: Vec, pub function_count: usize, pub type_count: u32, - /// Counts how often each function signature is used by a function - pub type_usage: HashMap, /// How many parameters a type has pub type_params: HashMap, + /// How many parameters the function with the most parameters has pub max_func_params: usize, + /// How many results the function with the most results has pub max_func_results: usize, + /// How many function parameters are used in the module + pub total_func_params: usize, } impl<'a> ParsedWasm<'a> { @@ -47,10 +49,10 @@ impl<'a> ParsedWasm<'a> { memories: vec![], function_count: 0, type_count: 0, - type_usage: HashMap::default(), type_params: HashMap::default(), max_func_params: 0, max_func_results: 0, + total_func_params: 0, }; let mut fun_allocations = Default::default(); @@ -84,13 +86,19 @@ impl<'a> ParsedWasm<'a> { } } Payload::FunctionSection(section) => { + // in valid wasm, the function section always has to come after the type section + // (see https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A0%E2%93%AA), + // so we can assume that the type_params map is already filled at this point + for a in section { let type_index = a? as usize; - if let Some(value) = this.type_usage.get_mut(&(type_index)) { - *value += 1; - } else { - this.type_usage.insert(type_index, 1); - } + this.total_func_params += + this.type_params.get(&type_index).ok_or_else(|| { + // this will also be thrown if the wasm section order is invalid + VmError::static_validation_err( + "Wasm bytecode error: function uses unknown type index", + ) + })? } } Payload::Version { num, .. } => this.version = num, From 6dfd99876644700ec8515ee97c89866887330366 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Wed, 24 Jan 2024 12:34:56 +0100 Subject: [PATCH 3/6] Use Vec for type params analysis --- packages/vm/src/parsed_wasm.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/vm/src/parsed_wasm.rs b/packages/vm/src/parsed_wasm.rs index d53c57f885..c9e10e8728 100644 --- a/packages/vm/src/parsed_wasm.rs +++ b/packages/vm/src/parsed_wasm.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use wasmer::wasmparser::{ Export, Import, MemoryType, Parser, Payload, TableType, Type, ValidPayload, Validator, WasmFeatures, @@ -18,8 +16,9 @@ pub struct ParsedWasm<'a> { pub memories: Vec, pub function_count: usize, pub type_count: u32, - /// How many parameters a type has - pub type_params: HashMap, + /// How many parameters a type has. + /// The index is the type id + pub type_params: Vec, /// How many parameters the function with the most parameters has pub max_func_params: usize, /// How many results the function with the most results has @@ -49,7 +48,7 @@ impl<'a> ParsedWasm<'a> { memories: vec![], function_count: 0, type_count: 0, - type_params: HashMap::default(), + type_params: Vec::new(), max_func_params: 0, max_func_results: 0, total_func_params: 0, @@ -71,11 +70,12 @@ impl<'a> ParsedWasm<'a> { match p { Payload::TypeSection(t) => { this.type_count = t.get_count(); - for (type_index, t_res) in t.into_iter().enumerate() { + this.type_params = Vec::with_capacity(t.get_count() as usize); + for t_res in t.into_iter() { let ty: Type = t_res?; match ty { Type::Func(ft) => { - this.type_params.insert(type_index, ft.params().len()); + this.type_params.push(ft.params().len()); this.max_func_params = core::cmp::max(ft.params().len(), this.max_func_params); @@ -93,7 +93,7 @@ impl<'a> ParsedWasm<'a> { for a in section { let type_index = a? as usize; this.total_func_params += - this.type_params.get(&type_index).ok_or_else(|| { + this.type_params.get(type_index).ok_or_else(|| { // this will also be thrown if the wasm section order is invalid VmError::static_validation_err( "Wasm bytecode error: function uses unknown type index", From e65d2396038e3e98086dc64addaac1caa9071e86 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Wed, 24 Jan 2024 12:38:49 +0100 Subject: [PATCH 4/6] Fix casing Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com> --- packages/vm/src/parsed_wasm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vm/src/parsed_wasm.rs b/packages/vm/src/parsed_wasm.rs index c9e10e8728..c75b7b98a2 100644 --- a/packages/vm/src/parsed_wasm.rs +++ b/packages/vm/src/parsed_wasm.rs @@ -86,7 +86,7 @@ impl<'a> ParsedWasm<'a> { } } Payload::FunctionSection(section) => { - // in valid wasm, the function section always has to come after the type section + // In valid Wasm, the function section always has to come after the type section // (see https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A0%E2%93%AA), // so we can assume that the type_params map is already filled at this point From 81a69cbc223b68c8ecf219e8249ee2ca727869d5 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Wed, 24 Jan 2024 12:46:18 +0100 Subject: [PATCH 5/6] Adjust function limits Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com> --- packages/vm/src/compatibility.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vm/src/compatibility.rs b/packages/vm/src/compatibility.rs index 960c4fbfd5..4850cf3006 100644 --- a/packages/vm/src/compatibility.rs +++ b/packages/vm/src/compatibility.rs @@ -71,11 +71,11 @@ const TABLE_SIZE_LIMIT: u32 = 2500; // entries /// when a user accidentally includes wasm-bindgen, they get a bunch of unsupported imports. const MAX_IMPORTS: usize = 100; -const MAX_FUNCTIONS: usize = 30000; +const MAX_FUNCTIONS: usize = 20_000; -const MAX_FUNCTION_PARAMS: usize = 150; +const MAX_FUNCTION_PARAMS: usize = 100; -const MAX_TOTAL_FUNCTION_PARAMS: usize = 7_000; +const MAX_TOTAL_FUNCTION_PARAMS: usize = 10_000; const MAX_FUNCTION_RESULTS: usize = 1; From 2d8c0e2cffa4982665d025c068cbf47579725435 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 24 Jan 2024 23:21:00 +0100 Subject: [PATCH 6/6] Add CHANGELOG entry for #1991 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c83a60698..805fe4b915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to ## [Unreleased] +### Changed + +- cosmwasm-vm: Limit total number of function parameters in + `check_wasm_functions` and increase max function count and max parameter + count. ([#1991]) + +[#1991]: https://github.com/CosmWasm/cosmwasm/pull/1991 + ## [2.0.0-beta.1] - 2023-01-22 ### Fixed