diff --git a/Cargo.lock b/Cargo.lock index 8bda75ef7b..699420da34 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -1711,6 +1711,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" version = "0.2.159" @@ -2084,6 +2090,7 @@ dependencies = [ "toml", "wasmparser", "wasmprinter", + "wat", ] [[package]] @@ -3633,6 +3640,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "untrusted" version = "0.9.0" @@ -3826,6 +3839,15 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +[[package]] +name = "wasm-encoder" +version = "0.217.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b88b0814c9a2b323a9b46c687e726996c255ac8b64aa237dd11c81ed4854760" +dependencies = [ + "leb128", +] + [[package]] name = "wasmparser" version = "0.216.0" @@ -3851,6 +3873,28 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wast" +version = "217.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79004ecebded92d3c710d4841383368c7f04b63d0992ddd6b0c7d5029b7629b7" +dependencies = [ + "bumpalo", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder", +] + +[[package]] +name = "wat" +version = "1.217.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c126271c3d92ca0f7c63e4e462e40c69cca52fd4245fcda730d1cf558fb55088" +dependencies = [ + "wast", +] + [[package]] name = "web-sys" version = "0.3.70" diff --git a/framework/meta-lib/Cargo.toml b/framework/meta-lib/Cargo.toml index 8e9209fa9a..37d726fc0b 100644 --- a/framework/meta-lib/Cargo.toml +++ b/framework/meta-lib/Cargo.toml @@ -29,6 +29,7 @@ hex = "0.4" wasmparser = "0.216" wasmprinter = "0.216" semver = "1.0.20" +wat = "1.217.0" [dependencies.multiversx-sc] version = "=0.53.0" diff --git a/framework/meta-lib/src/code_report_json.rs b/framework/meta-lib/src/code_report_json.rs index a0b997bb6e..b1ec390436 100644 --- a/framework/meta-lib/src/code_report_json.rs +++ b/framework/meta-lib/src/code_report_json.rs @@ -24,7 +24,7 @@ impl CodeReportJson { path: report.path.clone(), size, has_allocator: report.has_allocator, - has_panic: report.has_panic.clone(), + has_panic: report.has_panic.to_string(), } } } diff --git a/framework/meta-lib/src/tools.rs b/framework/meta-lib/src/tools.rs index e0a8962d9b..9437133224 100644 --- a/framework/meta-lib/src/tools.rs +++ b/framework/meta-lib/src/tools.rs @@ -1,8 +1,10 @@ mod find_workspace; mod git_describe; +pub(crate) mod panic_report; pub(crate) mod report_creator; pub mod twiggy; mod wasm_extractor; +mod wasm_extractor_test; mod wasm_opt; mod wasm_to_wat; diff --git a/framework/meta-lib/src/tools/panic_report.rs b/framework/meta-lib/src/tools/panic_report.rs new file mode 100644 index 0000000000..1be0f7cc56 --- /dev/null +++ b/framework/meta-lib/src/tools/panic_report.rs @@ -0,0 +1,80 @@ +use std::fmt::Display; + +use wasmparser::DataSectionReader; +const PANIC_WITH_MESSAGE: &[u8; 16] = b"panic occurred: "; +const PANIC_WITHOUT_MESSAGE: &[u8; 14] = b"panic occurred"; + +#[derive(Default, PartialEq, Clone)] +pub enum PanicReport { + #[default] + None, + WithoutMessage, + WithMessage, +} + +impl Display for PanicReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let panic_status = match self { + PanicReport::None => "None", + PanicReport::WithoutMessage => "without message", + PanicReport::WithMessage => "with message", + }; + write!(f, "{}", panic_status) + } +} + +impl PanicReport { + pub fn data_section_severity(&self, data_section: DataSectionReader) -> Self { + if is_panic_with_message_triggered(data_section.clone()) { + return Self::WithMessage; + } + + if is_panic_without_message_triggered(data_section) { + println!("here"); + return Self::WithoutMessage; + } + + Self::None + } + + pub fn max_severity(&mut self, data_section: DataSectionReader) { + if *self == PanicReport::WithMessage { + return; + } + + let panic_report = self.data_section_severity(data_section); + if panic_report == PanicReport::None { + return; + } + + *self = panic_report; + } +} + +fn is_panic_with_message_triggered(data_section: DataSectionReader) -> bool { + for data_fragment in data_section.into_iter().flatten() { + if data_fragment + .data + .windows(PANIC_WITH_MESSAGE.len()) + .any(|data| data == PANIC_WITH_MESSAGE) + { + return true; + } + } + + false +} + +fn is_panic_without_message_triggered(data_section: DataSectionReader) -> bool { + for data_fragment in data_section.into_iter().flatten() { + if data_fragment + .data + .windows(PANIC_WITHOUT_MESSAGE.len()) + .any(|data| data == PANIC_WITHOUT_MESSAGE) + { + return true; + } + } + + false +} diff --git a/framework/meta-lib/src/tools/report_creator.rs b/framework/meta-lib/src/tools/report_creator.rs index 37af10fa4e..9555814075 100644 --- a/framework/meta-lib/src/tools/report_creator.rs +++ b/framework/meta-lib/src/tools/report_creator.rs @@ -1,10 +1,9 @@ -pub const WITH_MESSAGE: &str = "with message"; -pub const WITHOUT_MESSAGE: &str = "without message"; +use super::panic_report::PanicReport; pub struct ReportCreator { pub path: String, pub has_allocator: bool, - pub has_panic: String, + pub has_panic: PanicReport, } impl ReportCreator {} diff --git a/framework/meta-lib/src/tools/wasm_extractor.rs b/framework/meta-lib/src/tools/wasm_extractor.rs index 50d5b78aff..733ca3fad0 100644 --- a/framework/meta-lib/src/tools/wasm_extractor.rs +++ b/framework/meta-lib/src/tools/wasm_extractor.rs @@ -1,17 +1,15 @@ use colored::Colorize; use std::fs; use wasmparser::{ - BinaryReaderError, DataSectionReader, FunctionBody, ImportSectionReader, Parser, Payload, + BinaryReaderError, DataSectionReader, FunctionBody, ImportSectionReader, Operator, Parser, + Payload, }; use crate::ei::EIVersion; -use super::report_creator::{ReportCreator, WITHOUT_MESSAGE, WITH_MESSAGE}; +use super::{panic_report::PanicReport, report_creator::ReportCreator}; -const PANIC_WITH_MESSAGE: &[u8; 16] = b"panic occurred: "; -const PANIC_WITHOUT_MESSAGE: &[u8; 14] = b"panic occurred"; const ERROR_FAIL_ALLOCATOR: &[u8; 27] = b"memory allocation forbidden"; -const MEMORY_GROW_OPCODE: u8 = 0x40; pub struct WasmInfo { pub imports: Vec, @@ -39,7 +37,7 @@ impl WasmInfo { } } -fn populate_wasm_info( +pub(crate) fn populate_wasm_info( path: String, wasm_data: Vec, extract_imports_enabled: bool, @@ -49,25 +47,21 @@ fn populate_wasm_info( let mut allocator_trigger = false; let mut ei_check = false; let mut memory_grow_flag = false; - let mut has_panic = "none"; + let mut has_panic: PanicReport = PanicReport::default(); let parser = Parser::new(0); for payload in parser.parse_all(&wasm_data) { match payload? { Payload::ImportSection(import_section) => { imports = extract_imports(import_section, extract_imports_enabled); - ei_check = is_ei_valid(imports.clone(), check_ei); + ei_check |= is_ei_valid(imports.clone(), check_ei); }, Payload::DataSection(data_section) => { - allocator_trigger = is_fail_allocator_triggered(data_section.clone()); - if is_panic_with_message_triggered(data_section.clone()) { - has_panic = WITH_MESSAGE; - } else if is_panic_without_message_triggered(data_section) { - has_panic = WITHOUT_MESSAGE; - } + allocator_trigger |= is_fail_allocator_triggered(data_section.clone()); + has_panic.max_severity(data_section); }, Payload::CodeSectionEntry(code_section) => { - memory_grow_flag = is_mem_grow(code_section); + memory_grow_flag |= is_mem_grow(code_section); }, _ => (), } @@ -76,7 +70,7 @@ fn populate_wasm_info( let report = ReportCreator { path, has_allocator: allocator_trigger, - has_panic: has_panic.to_string(), + has_panic, }; Ok(WasmInfo { @@ -109,34 +103,6 @@ fn is_fail_allocator_triggered(data_section: DataSectionReader) -> bool { false } -fn is_panic_with_message_triggered(data_section: DataSectionReader) -> bool { - for data_fragment in data_section.into_iter().flatten() { - if data_fragment - .data - .windows(PANIC_WITH_MESSAGE.len()) - .any(|data| data == PANIC_WITH_MESSAGE) - { - return true; - } - } - - false -} - -fn is_panic_without_message_triggered(data_section: DataSectionReader) -> bool { - for data_fragment in data_section.into_iter().flatten() { - if data_fragment - .data - .windows(PANIC_WITHOUT_MESSAGE.len()) - .any(|data| data == PANIC_WITHOUT_MESSAGE) - { - return true; - } - } - - false -} - pub fn extract_imports( import_section: ImportSectionReader, import_extraction_enabled: bool, @@ -173,11 +139,15 @@ fn is_ei_valid(imports: Vec, check_ei: &Option) -> bool { } fn is_mem_grow(code_section: FunctionBody) -> bool { - let mut code = code_section.get_binary_reader(); - while code.bytes_remaining() > 0 { - if code.read_u8().unwrap() == MEMORY_GROW_OPCODE { + let mut instructions_reader = code_section + .get_operators_reader() + .expect("Failed to get operators reader"); + + while let Ok(op) = instructions_reader.read() { + if let Operator::MemoryGrow { mem: _ } = op { return true; } } + false } diff --git a/framework/meta-lib/src/tools/wasm_extractor_test.rs b/framework/meta-lib/src/tools/wasm_extractor_test.rs new file mode 100644 index 0000000000..030cddc503 --- /dev/null +++ b/framework/meta-lib/src/tools/wasm_extractor_test.rs @@ -0,0 +1,297 @@ +#[cfg(test)] +pub mod tests { + use wat::Parser; + + use crate::tools::{panic_report::PanicReport, wasm_extractor::populate_wasm_info}; + + const EMPTY_WITH_FAIL_ALLOCATOR: &str = r#" +(module $empty_wasm.wasm + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32 i32))) + (type (;2;) (func)) + (import "env" "getNumArguments" (func $getNumArguments (;0;) (type 0))) + (import "env" "signalError" (func $signalError (;1;) (type 1))) + (import "env" "checkNoPayment" (func $checkNoPayment (;2;) (type 2))) + (func $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17hd731b4a37a0ab09aE (;3;) (type 2) + block ;; label = @1 + call $getNumArguments + br_if 0 (;@1;) + return + end + i32.const 131072 + i32.const 25 + call $signalError + unreachable + ) + (func $__rust_alloc (;4;) (type 2) + call $_ZN122_$LT$multiversx_sc_wasm_adapter..wasm_alloc..fail_allocator..FailAllocator$u20$as$u20$core..alloc..global..GlobalAlloc$GT$5alloc17hf044a79dd04bdcb1E + unreachable + ) + (func $_ZN122_$LT$multiversx_sc_wasm_adapter..wasm_alloc..fail_allocator..FailAllocator$u20$as$u20$core..alloc..global..GlobalAlloc$GT$5alloc17hf044a79dd04bdcb1E (;5;) (type 2) + call $_ZN26multiversx_sc_wasm_adapter10wasm_alloc14fail_allocator29signal_allocation_not_allowed17hb15b243b5f851b48E + unreachable + ) + (func $init (;6;) (type 2) + call $checkNoPayment + call $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17hd731b4a37a0ab09aE + i32.const 0 + i32.load8_u offset=131124 + drop + call $__rust_alloc + unreachable + ) + (func $upgrade (;7;) (type 2) + call $checkNoPayment + call $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17hd731b4a37a0ab09aE + ) + (func $callBack (;8;) (type 2)) + (func $_ZN26multiversx_sc_wasm_adapter10wasm_alloc14fail_allocator29signal_allocation_not_allowed17hb15b243b5f851b48E (;9;) (type 2) + i32.const 131097 + i32.const 27 + call $signalError + unreachable + ) + (memory (;0;) 3) + (global $__stack_pointer (;0;) (mut i32) i32.const 131072) + (global (;1;) i32 i32.const 131125) + (global (;2;) i32 i32.const 131136) + (export "memory" (memory 0)) + (export "init" (func $init)) + (export "upgrade" (func $upgrade)) + (export "callBack" (func $callBack)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) + (data $.rodata (;0;) (i32.const 131072) "wrong number of argumentsmemory allocation forbidden") +) +"#; + + const EMPTY_WITH_MEM_GROW: &str = r#" +(module $empty_wasm.wasm + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32 i32))) + (type (;2;) (func)) + (type (;3;) (func (param i64))) + (import "env" "getNumArguments" (func $getNumArguments (;0;) (type 0))) + (import "env" "signalError" (func $signalError (;1;) (type 1))) + (import "env" "checkNoPayment" (func $checkNoPayment (;2;) (type 2))) + (import "env" "smallIntFinishUnsigned" (func $smallIntFinishUnsigned (;3;) (type 3))) + (func $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17hd731b4a37a0ab09aE (;4;) (type 2) + block ;; label = @1 + call $getNumArguments + br_if 0 (;@1;) + return + end + i32.const 131072 + i32.const 25 + call $signalError + unreachable + ) + (func $rust_begin_unwind (;5;) (type 2) + call $_ZN26multiversx_sc_wasm_adapter5panic9panic_fmt17he68b14ffa9b6b21eE + unreachable + ) + (func $_ZN26multiversx_sc_wasm_adapter5panic9panic_fmt17he68b14ffa9b6b21eE (;6;) (type 2) + i32.const 131097 + i32.const 14 + call $signalError + unreachable + ) + (func $init (;7;) (type 2) + (local i32 i32) + call $checkNoPayment + call $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17hd731b4a37a0ab09aE + i32.const 0 + i32.load8_u offset=131120 + drop + block ;; label = @1 + i32.const 0 + i32.load offset=131112 + local.tee 0 + i32.const 3 + i32.and + i32.eqz + br_if 0 (;@1;) + i32.const 0 + local.get 0 + i32.const -4 + i32.and + i32.const 4 + i32.add + local.tee 0 + i32.store offset=131112 + end + block ;; label = @1 + local.get 0 + i32.const 4 + i32.add + local.tee 1 + i32.const 0 + i32.load offset=131116 + i32.le_u + br_if 0 (;@1;) + i32.const 1 + memory.grow + local.set 0 + i32.const 0 + i32.load offset=131116 + local.set 1 + i32.const 0 + local.get 0 + i32.const 16 + i32.shl + local.tee 0 + i32.const 65536 + i32.add + i32.store offset=131116 + i32.const 0 + i32.load offset=131112 + local.get 0 + local.get 0 + local.get 1 + i32.eq + select + local.tee 0 + i32.const 4 + i32.add + local.set 1 + end + i32.const 0 + local.get 1 + i32.store offset=131112 + block ;; label = @1 + local.get 0 + i32.eqz + br_if 0 (;@1;) + local.get 0 + i32.const 42 + i32.store + i64.const 1 + call $smallIntFinishUnsigned + return + end + call $_ZN5alloc5alloc18handle_alloc_error17he71533634a7a5ac5E + unreachable + ) + (func $_ZN5alloc5alloc18handle_alloc_error17he71533634a7a5ac5E (;8;) (type 2) + call $__rust_alloc_error_handler + unreachable + ) + (func $upgrade (;9;) (type 2) + call $checkNoPayment + call $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17hd731b4a37a0ab09aE + ) + (func $callBack (;10;) (type 2)) + (func $__rust_alloc_error_handler (;11;) (type 2) + call $__rdl_oom + unreachable + ) + (func $__rdl_oom (;12;) (type 2) + call $_ZN4core9panicking18panic_nounwind_fmt17h21a92179d680342aE + unreachable + ) + (func $_ZN4core9panicking18panic_nounwind_fmt17h21a92179d680342aE (;13;) (type 2) + call $rust_begin_unwind + unreachable + ) + (memory (;0;) 3) + (global $__stack_pointer (;0;) (mut i32) i32.const 131072) + (global (;1;) i32 i32.const 131121) + (global (;2;) i32 i32.const 131136) + (export "memory" (memory 0)) + (export "init" (func $init)) + (export "upgrade" (func $upgrade)) + (export "callBack" (func $callBack)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) + (data $.rodata (;0;) (i32.const 131072) "wrong number of argumentspanic occurred") +) +"#; + + const EMPTY_DBG_WAT: &str = r#" +(module $empty_wasm.wasm + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32 i32))) + (type (;2;) (func)) + (type (;3;) (func (param i64))) + (import "env" "getNumArguments" (func $getNumArguments (;0;) (type 0))) + (import "env" "signalError" (func $signalError (;1;) (type 1))) + (import "env" "checkNoPayment" (func $checkNoPayment (;2;) (type 2))) + (import "env" "smallIntFinishUnsigned" (func $smallIntFinishUnsigned (;3;) (type 3))) + (func $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17hd731b4a37a0ab09aE (;4;) (type 2) + block ;; label = @1 + call $getNumArguments + br_if 0 (;@1;) + return + end + i32.const 131072 + i32.const 25 + call $signalError + unreachable + ) + (func $init (;5;) (type 2) + call $checkNoPayment + call $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17hd731b4a37a0ab09aE + i64.const 64 + call $smallIntFinishUnsigned + ) + (func $upgrade (;6;) (type 2) + call $checkNoPayment + call $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17hd731b4a37a0ab09aE + ) + (func $callBack (;7;) (type 2)) + (memory (;0;) 3) + (global $__stack_pointer (;0;) (mut i32) i32.const 131072) + (global (;1;) i32 i32.const 131097) + (global (;2;) i32 i32.const 131104) + (export "memory" (memory 0)) + (export "init" (func $init)) + (export "upgrade" (func $upgrade)) + (export "callBack" (func $callBack)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) + (data $.rodata (;0;) (i32.const 131072) "wrong number of arguments") +) +"#; + + #[test] + fn test_empty() { + if let Ok(content) = Parser::new().parse_bytes(None, EMPTY_DBG_WAT.as_bytes()) { + let wasm_info = populate_wasm_info(String::new(), content.to_vec(), false, &None) + .expect("Unable to parse WASM content."); + assert!(!wasm_info.memory_grow_flag); + assert!(!wasm_info.report.has_allocator); + assert_eq!( + PanicReport::None.to_string(), + wasm_info.report.has_panic.to_string() + ); + } + } + + #[test] + fn test_empty_with_mem_grow() { + if let Ok(content) = Parser::new().parse_bytes(None, EMPTY_WITH_MEM_GROW.as_bytes()) { + let wasm_info = populate_wasm_info(String::new(), content.to_vec(), false, &None) + .expect("Unable to parse WASM content."); + assert!(wasm_info.memory_grow_flag); + assert!(!wasm_info.report.has_allocator); + assert_eq!( + PanicReport::WithoutMessage.to_string(), + wasm_info.report.has_panic.to_string() + ); + } + } + + #[test] + fn test_empty_with_fail_allocator() { + if let Ok(content) = Parser::new().parse_bytes(None, EMPTY_WITH_FAIL_ALLOCATOR.as_bytes()) { + let wasm_info = populate_wasm_info(String::new(), content.to_vec(), false, &None) + .expect("Unable to parse WASM content."); + assert!(!wasm_info.memory_grow_flag); + assert!(wasm_info.report.has_allocator); + assert_eq!( + PanicReport::None.to_string(), + wasm_info.report.has_panic.to_string() + ); + } + } +}