diff --git a/Cargo.lock b/Cargo.lock index 7fafa6ec81a..829cf81c741 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13486,7 +13486,7 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-encoder" version = "0.35.0" -source = "git+https://github.com/gear-tech/wasm-tools.git?branch=gear-stable#b5ee21ec07edc43b7e03edc3b9138bf78b3fd332" +source = "git+https://github.com/gear-tech/wasm-tools.git?branch=gear-stable#ffe9abee63ad640c051d50515cedee7f67f31884" dependencies = [ "leb128", ] @@ -13571,7 +13571,7 @@ dependencies = [ [[package]] name = "wasm-smith" version = "0.12.21" -source = "git+https://github.com/gear-tech/wasm-tools.git?branch=gear-stable#b5ee21ec07edc43b7e03edc3b9138bf78b3fd332" +source = "git+https://github.com/gear-tech/wasm-tools.git?branch=gear-stable#ffe9abee63ad640c051d50515cedee7f67f31884" dependencies = [ "arbitrary", "flagset", @@ -13995,7 +13995,7 @@ dependencies = [ [[package]] name = "wasmparser" version = "0.115.0" -source = "git+https://github.com/gear-tech/wasm-tools.git?branch=gear-stable#b5ee21ec07edc43b7e03edc3b9138bf78b3fd332" +source = "git+https://github.com/gear-tech/wasm-tools.git?branch=gear-stable#ffe9abee63ad640c051d50515cedee7f67f31884" dependencies = [ "indexmap 2.1.0", "semver 1.0.18", diff --git a/utils/wasm-gen/src/config.rs b/utils/wasm-gen/src/config.rs index b328717952a..a4ed9e43b3a 100644 --- a/utils/wasm-gen/src/config.rs +++ b/utils/wasm-gen/src/config.rs @@ -93,6 +93,8 @@ //! There's a pre-defined one - [`StandardGearWasmConfigsBundle`], usage of which will result //! in generation of valid (always) gear-wasm module. +use std::num::NonZeroU32; + mod generator; mod module; mod syscalls; @@ -131,6 +133,11 @@ impl ConfigsBundle for (GearWasmGeneratorConfig, SelectableParams) { pub struct StandardGearWasmConfigsBundle { /// Externalities to be logged. pub log_info: Option, + /// Probability of wait syscalls. + /// + /// For example, if this parameter is 4, wait syscalls will be invoked + /// with probability 1/4. + pub waiting_probability: Option, /// Flag which signals whether recursions must be removed. pub remove_recursion: bool, /// If the limit is set to `Some(_)`, programs will try to stop execution @@ -156,6 +163,7 @@ impl Default for StandardGearWasmConfigsBundle { fn default() -> Self { Self { log_info: Some("StandardGearWasmConfigsBundle".into()), + waiting_probability: NonZeroU32::new(4), remove_recursion: false, critical_gas_limit: Some(1_000_000), injection_types: SyscallsInjectionTypes::all_once(), @@ -171,6 +179,7 @@ impl ConfigsBundle for StandardGearWasmConfigsBundle { fn into_parts(self) -> (GearWasmGeneratorConfig, SelectableParams) { let StandardGearWasmConfigsBundle { log_info, + waiting_probability, remove_recursion, critical_gas_limit, injection_types, @@ -186,6 +195,10 @@ impl ConfigsBundle for StandardGearWasmConfigsBundle { if let Some(log_info) = log_info { syscalls_config_builder = syscalls_config_builder.with_log_info(log_info); } + if let Some(waiting_probability) = waiting_probability { + syscalls_config_builder = + syscalls_config_builder.with_waiting_probability(waiting_probability); + } syscalls_config_builder = syscalls_config_builder.with_params_config(params_config); let memory_pages_config = MemoryPagesConfig { diff --git a/utils/wasm-gen/src/config/module.rs b/utils/wasm-gen/src/config/module.rs index 69afe09c70a..4c2856ed7d4 100644 --- a/utils/wasm-gen/src/config/module.rs +++ b/utils/wasm-gen/src/config/module.rs @@ -25,6 +25,7 @@ use std::num::NonZeroUsize; +use crate::MemoryLayout; use arbitrary::{Arbitrary, Result, Unstructured}; pub use wasm_smith::InstructionKind; use wasm_smith::{InstructionKind::*, InstructionKinds, SwarmConfig}; @@ -81,6 +82,8 @@ impl From<(SelectableParams, ArbitraryParams)> for WasmModuleConfig { min_data_segments, max_types, min_types, + memory_offset_choices, + reserved_memory_size, } = ConstantParams::default(); let SelectableParams { @@ -109,7 +112,6 @@ impl From<(SelectableParams, ArbitraryParams)> for WasmModuleConfig { max_type_size, max_values, memory_max_size_required, - memory_offset_choices, min_element_segments, min_elements, min_globals, @@ -169,6 +171,7 @@ impl From<(SelectableParams, ArbitraryParams)> for WasmModuleConfig { min_uleb_size, multi_value_enabled, reference_types_enabled, + reserved_memory_size, tail_call_enabled, relaxed_simd_enabled, saturating_float_to_int_enabled, @@ -205,7 +208,6 @@ pub struct ArbitraryParams { max_type_size: u32, max_values: usize, memory_max_size_required: bool, - memory_offset_choices: (u32, u32, u32), min_element_segments: usize, min_elements: usize, min_globals: usize, @@ -237,7 +239,6 @@ impl Arbitrary<'_> for ArbitraryParams { max_type_size, max_values, memory_max_size_required, - memory_offset_choices, min_element_segments, min_elements, min_globals, @@ -267,7 +268,6 @@ impl Arbitrary<'_> for ArbitraryParams { max_type_size, max_values, memory_max_size_required, - memory_offset_choices, min_element_segments, min_elements, min_globals, @@ -312,6 +312,8 @@ pub struct ConstantParams { float_enabled: bool, memory_grow_enabled: bool, min_types: usize, + memory_offset_choices: (u32, u32, u32), + reserved_memory_size: Option, } impl Default for ConstantParams { @@ -343,6 +345,8 @@ impl Default for ConstantParams { min_data_segments: 0, max_types: 100, min_types: 5, + memory_offset_choices: (75, 25, 0), + reserved_memory_size: Some(MemoryLayout::RESERVED_MEMORY_SIZE as u64), } } } diff --git a/utils/wasm-gen/src/config/syscalls.rs b/utils/wasm-gen/src/config/syscalls.rs index 7a7a4aaf8e6..e120917099e 100644 --- a/utils/wasm-gen/src/config/syscalls.rs +++ b/utils/wasm-gen/src/config/syscalls.rs @@ -25,6 +25,7 @@ mod precise; mod process_errors; use gear_wasm_instrument::syscalls::SyscallName; +use std::num::NonZeroU32; pub use injection::*; pub use param::*; @@ -46,6 +47,7 @@ impl SyscallsConfigBuilder { precise_syscalls_config: PreciseSyscallsConfig::default(), error_processing_config: ErrorProcessingConfig::None, log_info: None, + waiting_probability: None, }) } @@ -93,6 +95,13 @@ impl SyscallsConfigBuilder { self } + /// Set probability of wait syscalls. + pub fn with_waiting_probability(mut self, waiting_probability: NonZeroU32) -> Self { + self.0.waiting_probability = Some(waiting_probability); + + self + } + /// Setup fallible syscalls error processing options. pub fn with_error_processing_config(mut self, config: ErrorProcessingConfig) -> Self { self.0.error_processing_config = config; @@ -114,6 +123,7 @@ pub struct SyscallsConfig { precise_syscalls_config: PreciseSyscallsConfig, error_processing_config: ErrorProcessingConfig, log_info: Option, + waiting_probability: Option, } impl SyscallsConfig { @@ -143,4 +153,9 @@ impl SyscallsConfig { pub fn error_processing_config(&self) -> &ErrorProcessingConfig { &self.error_processing_config } + + /// Get probability of wait syscalls. + pub fn waiting_probability(&self) -> Option { + self.waiting_probability + } } diff --git a/utils/wasm-gen/src/generator.rs b/utils/wasm-gen/src/generator.rs index cc3c928c3eb..1f0024adc33 100644 --- a/utils/wasm-gen/src/generator.rs +++ b/utils/wasm-gen/src/generator.rs @@ -33,21 +33,14 @@ //! //! # First generators nesting level //! GearWasmGenerator--->MemoryGenerator--->DisabledMemoryGenerator--->ModuleWithCallIndexes--->WasmModule -//! GearWasmGenerator--->EntryPointsGenerator--->DisabledEntryPointsGenerator--->ModuleWithCallIndexes--->WasmModule //! //! # Second generators nesting level //! GearWasmGenerator--->MemoryGenerator--(DisabledMemoryGenerator, FrozenGearWasmGenerator)---\ //! |--->GearWasmGenerator--->EntryPointsGenerator--->DisabledEntryPointsGenerator--->ModuleWithCallIndexes---> //! -//! GearWasmGenerator--->EntryPointsGenerator--(DisabledEntryPointsGenerator, FrozenGearWasmGenerator)---\ -//! |--->GearWasmGenerator--->MemoryGenerator--->DisabledMemoryGenerator--->ModuleWithCallIndexes--->WasmModule -//! //! # Third generators nesting level //! GearWasmGenerator--->MemoryGenerator--(DisabledMemoryGenerator, FrozenGearWasmGenerator)---\ //! |--->GearWasmGenerator--->EntryPointsGenerator--->DisabledEntryPointsGenerator--(MemoryImportGenerationProof, GearEntryPointGenerationProof)-->(syscalls-module-state-machine) -//! -//! GearWasmGenerator--->EntryPointsGenerator--(DisabledEntryPointsGenerator, FrozenGearWasmGenerator)---\ -//! |--->GearWasmGenerator--->MemoryGenerator--->DisabledMemoryGenerator--(MemoryImportGenerationProof, GearEntryPointGenerationProof)-->(syscalls-module-state-machine) //! ``` //! //! State machine named `(syscalls-module-state-machine)` can be started only with having proof of work from `MemoryGenerator` and `EntryPointsGenerator`. @@ -125,11 +118,11 @@ impl<'a, 'b> GearWasmGenerator<'a, 'b> { self.generate_memory_export(); let (disabled_ep_gen, frozen_gear_wasm_gen, ep_gen_proof) = - Self::from((disabled_mem_gen, frozen_gear_wasm_gen)).generate_entry_points()?; + Self::from((disabled_mem_gen, frozen_gear_wasm_gen)) + .generate_entry_points(mem_imports_gen_proof)?; let (disabled_syscalls_invocator, frozen_gear_wasm_gen) = - Self::from((disabled_ep_gen, frozen_gear_wasm_gen)) - .generate_syscalls(mem_imports_gen_proof, ep_gen_proof)?; + Self::from((disabled_ep_gen, frozen_gear_wasm_gen)).generate_syscalls(ep_gen_proof)?; let config = frozen_gear_wasm_gen.melt(); let module = ModuleWithCallIndexes::from(disabled_syscalls_invocator) @@ -171,13 +164,16 @@ impl<'a, 'b> GearWasmGenerator<'a, 'b> { /// Generate gear wasm gentry points using entry points generator. pub fn generate_entry_points( self, + mem_import_gen_proof: MemoryImportGenerationProof, ) -> Result<( DisabledEntryPointsGenerator<'a, 'b>, FrozenGearWasmGenerator<'a, 'b>, GearEntryPointGenerationProof, )> { + let entry_points_gen_instantiator = + EntryPointsGeneratorInstantiator::from((self, mem_import_gen_proof)); let (ep_gen, frozen_gear_wasm_gen): (EntryPointsGenerator, FrozenGearWasmGenerator) = - self.into(); + entry_points_gen_instantiator.into(); let (disabled_ep_gen, ep_gen_proof) = ep_gen.generate_entry_points()?; Ok((disabled_ep_gen, frozen_gear_wasm_gen, ep_gen_proof)) @@ -186,11 +182,10 @@ impl<'a, 'b> GearWasmGenerator<'a, 'b> { /// Generate syscalls using syscalls module generators. pub fn generate_syscalls( self, - mem_import_gen_proof: MemoryImportGenerationProof, ep_gen_proof: GearEntryPointGenerationProof, ) -> Result<(DisabledSyscallsInvocator, FrozenGearWasmGenerator<'a, 'b>)> { let syscalls_imports_gen_instantiator = - SyscallsImportsGeneratorInstantiator::from((self, mem_import_gen_proof, ep_gen_proof)); + SyscallsImportsGeneratorInstantiator::from((self, ep_gen_proof)); let (syscalls_imports_gen, frozen_gear_wasm_gen) = syscalls_imports_gen_instantiator.into(); let syscalls_imports_gen_res = syscalls_imports_gen.generate()?; diff --git a/utils/wasm-gen/src/generator/entry_points.rs b/utils/wasm-gen/src/generator/entry_points.rs index 94c8dfb287e..1df7dfae639 100644 --- a/utils/wasm-gen/src/generator/entry_points.rs +++ b/utils/wasm-gen/src/generator/entry_points.rs @@ -19,8 +19,12 @@ //! Gear wasm entry points generator module. use crate::{ - generator::{CallIndexes, FrozenGearWasmGenerator, GearWasmGenerator, ModuleWithCallIndexes}, - EntryPointsSet, WasmModule, + generator::{ + CallIndexes, FrozenGearWasmGenerator, GearWasmGenerator, MemoryImportGenerationProof, + ModuleWithCallIndexes, + }, + wasm::{PageCount as WasmPageCount, WasmModule}, + EntryPointsSet, MemoryLayout, }; use arbitrary::{Result, Unstructured}; use gear_wasm_instrument::parity_wasm::{ @@ -38,13 +42,29 @@ pub struct EntryPointsGenerator<'a, 'b> { call_indexes: CallIndexes, } -impl<'a, 'b> From> +/// Entry points generator instantiator. +/// +/// Serves as a new type in order to create the generator from gear wasm generator and memory import proof. +pub struct EntryPointsGeneratorInstantiator<'a, 'b>( + (GearWasmGenerator<'a, 'b>, MemoryImportGenerationProof), +); + +impl<'a, 'b> From<(GearWasmGenerator<'a, 'b>, MemoryImportGenerationProof)> + for EntryPointsGeneratorInstantiator<'a, 'b> +{ + fn from(inner: (GearWasmGenerator<'a, 'b>, MemoryImportGenerationProof)) -> Self { + Self(inner) + } +} + +impl<'a, 'b> From> for ( EntryPointsGenerator<'a, 'b>, FrozenGearWasmGenerator<'a, 'b>, ) { - fn from(generator: GearWasmGenerator<'a, 'b>) -> Self { + fn from(instantiator: EntryPointsGeneratorInstantiator<'a, 'b>) -> Self { + let EntryPointsGeneratorInstantiator((generator, _mem_import_gen_proof)) = instantiator; let ep_generator = EntryPointsGenerator { unstructured: generator.unstructured, module: generator.module, @@ -165,7 +185,8 @@ impl<'a, 'b> EntryPointsGenerator<'a, 'b> { }); let export_body_instructions = - self.generate_export_body(export_body_call_idx, export_body_call_func_type)?; + self.generate_export_body(name, export_body_call_idx, export_body_call_func_type)?; + self.module.with(|module| { let module = builder::from_module(module) .function() @@ -194,6 +215,7 @@ impl<'a, 'b> EntryPointsGenerator<'a, 'b> { /// Generates body of the export function. fn generate_export_body( &mut self, + name: &str, export_body_call_idx: usize, export_body_call_func_type: FunctionType, ) -> Result> { @@ -211,6 +233,27 @@ impl<'a, 'b> EntryPointsGenerator<'a, 'b> { } res.push(Instruction::Call(export_body_call_idx as u32)); res.extend(results.iter().map(|_| Instruction::Drop)); + + // after initializing the program, we will write about this in a special pointer + if name == "init" { + let memory_size_pages = self + .module + .initial_mem_size() + .expect("generator is instantiated with a mem import generation proof"); + let mem_size = Into::::into(memory_size_pages).memory_size(); + + let MemoryLayout { + init_called_ptr, .. + } = MemoryLayout::from(mem_size); + + res.extend_from_slice(&[ + // *init_called_ptr = true + Instruction::I32Const(init_called_ptr), + Instruction::I32Const(1), + Instruction::I32Store8(0, 0), + ]); + } + res.push(Instruction::End); Ok(res) diff --git a/utils/wasm-gen/src/generator/syscalls.rs b/utils/wasm-gen/src/generator/syscalls.rs index 4b0417f588d..ece437ad6ad 100644 --- a/utils/wasm-gen/src/generator/syscalls.rs +++ b/utils/wasm-gen/src/generator/syscalls.rs @@ -218,6 +218,14 @@ impl InvocableSyscall { }) } + /// Returns `true` for wait syscalls. + fn is_wait_syscall(&self) -> bool { + use InvocableSyscall::*; + use SyscallName::*; + + matches!(self, Loose(Wait | WaitFor | WaitUpTo)) + } + /// Checks whether syscall is error-prone either by returning error indicating value /// or by providing error pointer as a syscall param. /// diff --git a/utils/wasm-gen/src/generator/syscalls/imports.rs b/utils/wasm-gen/src/generator/syscalls/imports.rs index 35b503eeb38..75466fcc2c4 100644 --- a/utils/wasm-gen/src/generator/syscalls/imports.rs +++ b/utils/wasm-gen/src/generator/syscalls/imports.rs @@ -24,7 +24,7 @@ use crate::{ GearWasmGenerator, MemoryImportGenerationProof, ModuleWithCallIndexes, }, wasm::{PageCount as WasmPageCount, WasmModule}, - InvocableSyscall, SyscallInjectionType, SyscallsConfig, + InvocableSyscall, MemoryLayout, SyscallInjectionType, SyscallsConfig, }; use arbitrary::{Error as ArbitraryError, Result, Unstructured}; use gear_wasm_instrument::{ @@ -50,11 +50,7 @@ pub struct SyscallsImportsGenerator<'a, 'b> { /// /// Serves as a new type in order to create the generator from gear wasm generator and proofs. pub struct SyscallsImportsGeneratorInstantiator<'a, 'b>( - ( - GearWasmGenerator<'a, 'b>, - MemoryImportGenerationProof, - GearEntryPointGenerationProof, - ), + (GearWasmGenerator<'a, 'b>, GearEntryPointGenerationProof), ); /// The set of syscalls that need to be imported to create precise syscall. @@ -71,20 +67,10 @@ pub enum PreciseSyscallError { Arbitrary(#[from] ArbitraryError), } -impl<'a, 'b> - From<( - GearWasmGenerator<'a, 'b>, - MemoryImportGenerationProof, - GearEntryPointGenerationProof, - )> for SyscallsImportsGeneratorInstantiator<'a, 'b> +impl<'a, 'b> From<(GearWasmGenerator<'a, 'b>, GearEntryPointGenerationProof)> + for SyscallsImportsGeneratorInstantiator<'a, 'b> { - fn from( - inner: ( - GearWasmGenerator<'a, 'b>, - MemoryImportGenerationProof, - GearEntryPointGenerationProof, - ), - ) -> Self { + fn from(inner: (GearWasmGenerator<'a, 'b>, GearEntryPointGenerationProof)) -> Self { Self(inner) } } @@ -96,11 +82,7 @@ impl<'a, 'b> From> ) { fn from(instantiator: SyscallsImportsGeneratorInstantiator<'a, 'b>) -> Self { - let SyscallsImportsGeneratorInstantiator(( - generator, - _mem_import_gen_proof, - _gen_ep_gen_proof, - )) = instantiator; + let SyscallsImportsGeneratorInstantiator((generator, _gen_ep_gen_proof)) = instantiator; let syscall_gen = SyscallsImportsGenerator { unstructured: generator.unstructured, call_indexes: generator.call_indexes, @@ -325,8 +307,8 @@ impl<'a, 'b> SyscallsImportsGenerator<'a, 'b> { } impl<'a, 'b> SyscallsImportsGenerator<'a, 'b> { - /// The amount of memory used to create a precise syscall. - const PRECISE_SYSCALL_MEMORY_SIZE: u32 = 100; + /// The amount of reserved memory used to create a precise syscall. + const PRECISE_SYSCALL_RESERVED_MEMORY_SIZE: u32 = 128; /// Generates a function which calls "properly" the `gr_reservation_send`. fn generate_send_from_reservation( @@ -799,12 +781,13 @@ impl<'a, 'b> SyscallsImportsGenerator<'a, 'b> { /// Reserves enough memory build precise syscall. fn reserve_memory(&self) -> i32 { - self.memory_size_in_bytes() - .saturating_sub(Self::PRECISE_SYSCALL_MEMORY_SIZE) as i32 + self.memory_size_bytes() + .saturating_sub(MemoryLayout::RESERVED_MEMORY_SIZE) + .saturating_sub(Self::PRECISE_SYSCALL_RESERVED_MEMORY_SIZE) as i32 } /// Returns the size of the memory in bytes that can be used to build precise syscall. - fn memory_size_in_bytes(&self) -> u32 { + fn memory_size_bytes(&self) -> u32 { let initial_mem_size: WasmPageCount = self .module .initial_mem_size() diff --git a/utils/wasm-gen/src/generator/syscalls/invocator.rs b/utils/wasm-gen/src/generator/syscalls/invocator.rs index b93ec114fc9..367d8801c39 100644 --- a/utils/wasm-gen/src/generator/syscalls/invocator.rs +++ b/utils/wasm-gen/src/generator/syscalls/invocator.rs @@ -25,8 +25,8 @@ use crate::{ }, utils::{self, WasmWords}, wasm::{PageCount as WasmPageCount, WasmModule}, - ActorKind, InvocableSyscall, PtrParamAllowedValues, RegularParamAllowedValues, SyscallsConfig, - SyscallsParamsConfig, + ActorKind, InvocableSyscall, MemoryLayout, PtrParamAllowedValues, RegularParamAllowedValues, + SyscallsConfig, SyscallsParamsConfig, }; use arbitrary::{Result, Unstructured}; use gear_wasm_instrument::{ @@ -94,11 +94,14 @@ pub(crate) fn process_syscall_params( Length if length_param_indexes.contains(¶m_idx) => { // Due to match guard `RegularParamType::Length` can be processed in two ways: // 1. The function will return `ProcessedSyscallParams::MemoryArraySize` - // if this parameter is associated with Ptr::SizedBufferStart { .. }`. + // if this parameter is associated with Ptr::SizedBufferStart { .. }` + // or `Ptr::MutSizedBufferStart`. // 2. Otherwise, `ProcessedSyscallParams::Value` will be returned from the function. ProcessedSyscallParams::MemoryArrayLength } - Pointer(Ptr::SizedBufferStart { .. }) => ProcessedSyscallParams::MemoryArrayPtr, + Pointer(Ptr::SizedBufferStart { .. } | Ptr::MutSizedBufferStart { .. }) => { + ProcessedSyscallParams::MemoryArrayPtr + } // It's guaranteed that fallible syscall has error pointer as a last param. Pointer(ptr) => ProcessedSyscallParams::MemoryPtrValue { allowed_values: params_config.get_ptr_rule(ptr), @@ -315,6 +318,14 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> { }; instructions.append(&mut result_processing); + if let Some(waiting_probability) = self + .config + .waiting_probability() + .filter(|_| invocable.is_wait_syscall()) + { + self.limit_infinite_waits(&mut instructions, waiting_probability.get()); + } + log::trace!( "Random data after building `{}` syscall invoke instructions - {}", invocable.to_str(), @@ -333,13 +344,8 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> { self.unstructured.len() ); - let mem_size_pages = self - .module - .initial_mem_size() - // To instantiate this generator, we must instantiate SyscallImportsGenerator, which can be - // instantiated only with memory import generation proof. - .expect("generator is instantiated with a memory import generation proof"); - let mem_size = Into::::into(mem_size_pages).memory_size(); + let mem_size_pages = self.memory_size_pages(); + let mem_size = self.memory_size_bytes(); let mut ret = Vec::with_capacity(params.len()); let mut memory_array_definition: Option<(i32, Option)> = None; @@ -386,7 +392,8 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> { } ProcessedSyscallParams::MemoryArrayLength => { let length; - let upper_limit = mem_size.saturating_sub(1) as i32; + let upper_limit = + mem_size.saturating_sub(MemoryLayout::RESERVED_MEMORY_SIZE) as i32; (memory_array_definition, length) = if let Some((offset, _)) = memory_array_definition @@ -405,7 +412,8 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> { } ProcessedSyscallParams::MemoryArrayPtr => { let offset; - let upper_limit = mem_size.saturating_sub(1) as i32; + let upper_limit = + mem_size.saturating_sub(MemoryLayout::RESERVED_MEMORY_SIZE) as i32; (memory_array_definition, offset) = if let Some((offset, _)) = memory_array_definition { @@ -421,7 +429,9 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> { } ProcessedSyscallParams::MemoryPtrValue { allowed_values } => { // Subtract a bit more so entities from `gsys` fit. - let upper_limit = mem_size.saturating_sub(100); + let upper_limit = mem_size + .saturating_sub(MemoryLayout::RESERVED_MEMORY_SIZE) + .saturating_sub(128); let offset = self.unstructured.int_in_range(0..=upper_limit)? as i32; let param_instructions = if let Some(allowed_values) = allowed_values { @@ -661,6 +671,46 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> { } } + /// Patches instructions of wait-syscalls to prevent deadlocks. + fn limit_infinite_waits(&self, instructions: &mut Vec, waiting_probability: u32) { + let MemoryLayout { + init_called_ptr, + wait_called_ptr, + .. + } = MemoryLayout::from(self.memory_size_bytes()); + + // add instructions before calling wait syscall + instructions.splice( + 0..0, + [ + Instruction::I32Const(init_called_ptr), + Instruction::I32Load8U(0, 0), + // if *init_called_ptr { .. } + Instruction::If(BlockType::NoResult), + Instruction::I32Const(wait_called_ptr), + Instruction::I32Load(2, 0), + Instruction::I32Const(waiting_probability as i32), + Instruction::I32RemU, + Instruction::I32Eqz, + // if *wait_called_ptr % waiting_probability == 0 { orig_wait_syscall(); } + Instruction::If(BlockType::NoResult), + ], + ); + + // add instructions after calling wait syscall + instructions.extend_from_slice(&[ + Instruction::End, + // *wait_called_ptr += 1 + Instruction::I32Const(wait_called_ptr), + Instruction::I32Const(wait_called_ptr), + Instruction::I32Load(2, 0), + Instruction::I32Const(1), + Instruction::I32Add, + Instruction::I32Store(2, 0), + Instruction::End, + ]); + } + fn resolves_calls_indexes(&mut self) { log::trace!("Resolving calls indexes"); @@ -736,6 +786,20 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> { (module, ()) }) } + + /// Returns the size of the memory in bytes. + fn memory_size_bytes(&self) -> u32 { + Into::::into(self.memory_size_pages()).memory_size() + } + + /// Returns the size of the memory in pages. + fn memory_size_pages(&self) -> u32 { + self.module + .initial_mem_size() + // To instantiate this generator, we must instantiate SyscallImportsGenerator, which can be + // instantiated only with memory import generation proof. + .expect("generator is instantiated with a memory import generation proof") + } } /// Disabled syscalls invocator. diff --git a/utils/wasm-gen/src/lib.rs b/utils/wasm-gen/src/lib.rs index e535b26c5a7..c9cce715653 100644 --- a/utils/wasm-gen/src/lib.rs +++ b/utils/wasm-gen/src/lib.rs @@ -30,6 +30,7 @@ pub mod wasm_gen_arbitrary { } pub mod config; pub mod generator; + #[cfg(test)] mod tests; mod utils; @@ -38,7 +39,7 @@ mod wasm; pub use config::*; pub use gear_wasm_instrument::syscalls::SyscallName; pub use generator::*; -pub use wasm::WasmModule; +pub use wasm::{MemoryLayout, WasmModule}; pub use wasm_gen_arbitrary::*; use gear_wasm_instrument::parity_wasm::{self, elements::Module}; diff --git a/utils/wasm-gen/src/tests.rs b/utils/wasm-gen/src/tests.rs index fd4b2c933ee..5a657481066 100644 --- a/utils/wasm-gen/src/tests.rs +++ b/utils/wasm-gen/src/tests.rs @@ -40,7 +40,7 @@ use gear_wasm_instrument::{ }; use proptest::prelude::*; use rand::{rngs::SmallRng, RngCore, SeedableRng}; -use std::num::NonZeroUsize; +use std::num::{NonZeroU32, NonZeroUsize}; const UNSTRUCTURED_SIZE: usize = 1_000_000; @@ -171,6 +171,28 @@ fn remove_multiple_recursions() { println!("wat = {wat}"); } +#[test] +fn test_avoid_waits_works() { + let mut rng = SmallRng::seed_from_u64(123); + let mut buf = vec![0; UNSTRUCTURED_SIZE]; + rng.fill_bytes(&mut buf); + let mut unstructured = Unstructured::new(&buf); + + let mut injection_types = SyscallsInjectionTypes::all_never(); + injection_types.set(InvocableSyscall::Loose(SyscallName::Wait), 1, 1); + let syscalls_config = SyscallsConfigBuilder::new(injection_types) + .with_waiting_probability(NonZeroU32::new(4).unwrap()) + .build(); + + let backend_report = + execute_wasm_with_custom_configs(&mut unstructured, syscalls_config, None, 1024, false, 0); + + assert_eq!( + backend_report.termination_reason, + TerminationReason::Actor(ActorTerminationReason::Success) + ); +} + #[test] fn test_source_as_address_param() { let mut rng = SmallRng::seed_from_u64(123); diff --git a/utils/wasm-gen/src/utils.rs b/utils/wasm-gen/src/utils.rs index c516904bf92..2379a03b13f 100644 --- a/utils/wasm-gen/src/utils.rs +++ b/utils/wasm-gen/src/utils.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::wasm::PageCount as WasmPageCount; use gear_utils::NonEmpty; use gear_wasm_instrument::{ parity_wasm::{ @@ -253,27 +252,6 @@ fn find_recursion_impl( /// } /// ``` pub fn inject_critical_gas_limit(module: Module, critical_gas_limit: u64) -> Module { - // get initial memory size of program - let Some(mem_size) = module - .import_section() - .and_then(|section| { - section - .entries() - .iter() - .find_map(|entry| match entry.external() { - External::Memory(mem_ty) => Some(mem_ty.limits().initial()), - _ => None, - }) - }) - .map(Into::::into) - .map(|page_count| page_count.memory_size()) - else { - return module; - }; - - // store available gas pointer on the last memory page - let gas_ptr = mem_size - mem::size_of::() as u32; - // add gr_gas_available import if needed let maybe_gr_gas_available_index = module.import_section().and_then(|section| { section @@ -336,14 +314,17 @@ pub fn inject_critical_gas_limit(module: Module, critical_gas_limit: u64) -> Mod let Type::Function(signature) = signature; let results = signature.results(); + // store available gas pointer on the first memory page + const GAS_PTR: i32 = 1024; + // create the body of the gas limiter: let mut body = Vec::with_capacity(results.len() + 9); body.extend_from_slice(&[ - // gr_gas_available(gas_ptr) - Instruction::I32Const(gas_ptr as i32), + // gr_gas_available(GAS_PTR) + Instruction::I32Const(GAS_PTR), Instruction::Call(gr_gas_available_index), - // gas_available = *gas_ptr - Instruction::I32Const(gas_ptr as i32), + // gas_available = *GAS_PTR + Instruction::I32Const(GAS_PTR), Instruction::I64Load(3, 0), Instruction::I64Const(critical_gas_limit as i64), // if gas_available <= critical_gas_limit { return result; } diff --git a/utils/wasm-gen/src/wasm.rs b/utils/wasm-gen/src/wasm.rs index e35c4d0d7bf..7487e189209 100644 --- a/utils/wasm-gen/src/wasm.rs +++ b/utils/wasm-gen/src/wasm.rs @@ -196,3 +196,47 @@ impl PageCount { self.0 * WASM_PAGE_SIZE as u32 } } + +/// Represents memory layout that can be safely used between syscalls and +/// instructions. +/// +/// The last memory page in program generated by `wasm-gen` is reserved for +/// internal use. Currently, we take [`MemoryLayout::RESERVED_MEMORY_SIZE`] +/// bytes from the last memory page and also prohibit modification of this +/// memory at the `wasm-smith` and `wasm-gen` level. +/// +/// If you want to store some data in memory and then access it in the program, +/// consider adding a new pointer to this structure. +pub struct MemoryLayout { + pub init_called_ptr: i32, + pub wait_called_ptr: i32, + pub remaining_memory_len: u32, + pub remaining_memory_ptr: i32, +} + +impl MemoryLayout { + /// The amount of reserved memory. + pub const RESERVED_MEMORY_SIZE: u32 = 256; +} + +impl From for MemoryLayout { + fn from(mem_size: u32) -> Self { + let start_memory_ptr = mem_size.saturating_sub(Self::RESERVED_MEMORY_SIZE) as i32; + let init_called_ptr = start_memory_ptr; + let wait_called_ptr = init_called_ptr + mem::size_of::() as i32; + let remaining_memory_ptr = wait_called_ptr + mem::size_of::() as i32; + let remaining_memory_len = (remaining_memory_ptr - start_memory_ptr) as u32; + + assert!( + remaining_memory_len <= Self::RESERVED_MEMORY_SIZE, + "reserved memory exceeded" + ); + + Self { + init_called_ptr, + wait_called_ptr, + remaining_memory_len, + remaining_memory_ptr, + } + } +}