diff --git a/src/evm/corpus_initializer.rs b/src/evm/corpus_initializer.rs index 80de117e8..e0dea489f 100644 --- a/src/evm/corpus_initializer.rs +++ b/src/evm/corpus_initializer.rs @@ -44,8 +44,6 @@ use std::path::Path; use std::rc::Rc; use std::time::Duration; -use crate::evm::gpt::add_gpt_generated_txs; - pub struct EVMCorpusInitializer<'a> { executor: &'a mut EVMExecutor, scheduler: &'a dyn Scheduler, @@ -300,9 +298,6 @@ impl<'a> EVMCorpusInitializer<'a> { ); } - // add gpt corpus - // add_gpt_generated_txs(self.state, self.scheduler, contract); - // add transfer txn { let input = EVMInput { diff --git a/src/evm/gpt.rs b/src/evm/gpt.rs index 23d06f92d..a16794c2f 100644 --- a/src/evm/gpt.rs +++ b/src/evm/gpt.rs @@ -1,25 +1,25 @@ use crate::add_input_to_corpus; use crate::evm::abi::get_abi_type_boxed; +use crate::evm::contract_utils::ContractLoader; use crate::evm::mutator::AccessPattern; use crate::evm::types::EVMU256; +use crate::evm::{input::EVMInput, types::EVMFuzzState}; use crate::state::HasCaller; use crate::state_input::StagedVMState; -use crate::evm::{input::EVMInput, types::EVMFuzzState}; - use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::hex; +use crate::evm::contract_utils::ContractInfo; use libafl::corpus::Testcase; use libafl::schedulers::Scheduler; use std::cell::RefCell; use std::fs::File; use std::io::prelude::*; use std::rc::Rc; +use std::str::FromStr; use std::time::Duration; -use crate::evm::contract_utils::ContractInfo; - /* You are a Solidity expert. I'll send you the ".sol" file and You need to generate three targeted fuzz test cases for each function. @@ -41,17 +41,14 @@ UniswapV2Pair/swap/0/1000000000000000000,1000000000000000000,0x00000000000000000 /* -You are a Solidity expert. I'll send you the ".sol" file and You need to generate three targeted fuzz test cases for each function. +You are a Solidity expert. I'll send you a solidity project which contains many .sol file. You need to generate targeted fuzz test cases for maximizing code coverage. + 1. Note that the functions in interface are ignored, only the functions in contract are targeted. 2. You answer in the following format: contract_name/function_name/tx_value/param1,param2..param3 3. tx_value should be 0 if the function is not payable. otherwise, You pick a meaningful value. 4. Outputs the value of the basic type corresponding to the parameter. treat all contract/interface type as address. 5. Strictly follow the above format output without any other description or explanation. -6. param is the abi.encode(param) of the parameter. - -For example, the following is the output of the function "swap" in the contract "UniswapV2Pair.sol": -UniswapV2Pair/swap/0/0x0000000000000000000000000000000000000000000000000000000000000000,0x0000000000000000000000000000000000000000000000000000000000000000,0x0000000000000000000000000000000000000000000000000000000000000000,0x0000000000000000000000000000000000000000000000000000000000000000 */ @@ -85,7 +82,7 @@ pub fn encode_item(item: &str, sol_type: &DynSolType) -> DynSolValue { pub fn add_gpt_generated_txs( state: &mut EVMFuzzState, scheduler: &dyn Scheduler, - contract: &mut ContractInfo, + contract: &ContractInfo, ) { println!("============= add_gpt_generated_txs ============="); let contract_name = contract @@ -101,7 +98,7 @@ pub fn add_gpt_generated_txs( let mut contents = String::new(); file.read_to_string(&mut contents) .expect("Unable to read the file"); - for abi in contract.abi.iter() { + contract.abi.iter().for_each(|abi| { for line in contents.lines() { let mut parts = line.split('/'); if contract_name != parts.next().unwrap() { @@ -164,5 +161,95 @@ pub fn add_gpt_generated_txs( add_input_to_corpus!(state, scheduler, input); } - } + }); +} + +pub fn get_gpt_generated_corpus( + state: &mut EVMFuzzState, + initial_vm_state: StagedVMState< + revm_primitives::B160, + revm_primitives::B160, + crate::evm::vm::EVMState, + crate::evm::input::ConciseEVMInput, + >, + contract_loader: &ContractLoader, +) -> Vec { + println!("============= gpt_generated_corpus ============="); + let mut inputs = Vec::new(); + + let mut file = File::open("gpt.txt").expect("Unable to open the file"); + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("Unable to read the file"); + contract_loader.contracts.iter().for_each(|contract| { + let contract_name = contract + .name + .split('/') + .last() + .unwrap() + .strip_suffix('*') + .unwrap(); + for abi in &contract.abi { + for line in contents.lines() { + let mut parts = line.split('/'); + if contract_name != parts.next().unwrap() { + continue; + } + let function_signature = parts.next().unwrap(); + let tx_value = parts.next().unwrap(); + let params = parts.next().unwrap_or_default(); + let params = params.split(',').collect::>(); + + if abi.function_name != function_signature { + continue; + } + // println!("abi: {:?}", abi); + println!( + "contract_name: {}, function_signature: {}, tx_value: {}, params: {:?}", + contract_name, function_signature, tx_value, params + ); + + let my_type: DynSolType = abi.abi.parse().unwrap(); + + let encoded_params = params + .iter() + .zip(my_type.as_tuple().unwrap()) + .map(|(param, sol_type)| { + println!("param: {}, sol_type: {:?}", param, sol_type); + encode_item(param, sol_type) + }) + .collect::>(); + + let mut data = abi.function.to_vec(); + let encoded = DynSolValue::Tuple(encoded_params).encode_params(); + data.extend(encoded); + + // println!("data: {:?}", hex::encode(&data)); + + let mut abi_instance = get_abi_type_boxed(abi.abi.as_str()); + abi_instance.set_func_with_name(abi.function, abi.function_name.clone()); + abi_instance.set_bytes(data); + + inputs.push(EVMInput { + caller: state.get_rand_caller(), + contract: contract.deployed_address, + data: Some(abi_instance), + sstate: initial_vm_state.clone(), + sstate_idx: 0, + txn_value: Some(EVMU256::from_str(tx_value).unwrap()), + step: false, + env: Default::default(), + access_pattern: Rc::new(RefCell::new(AccessPattern::new())), + direct_data: Default::default(), + #[cfg(feature = "flashloan_v2")] + liquidation_percent: 0, + #[cfg(feature = "flashloan_v2")] + input_type: EVMInputTy::ABI, + randomness: vec![0], + repeat: 1, + }); + } + } + }); + inputs } diff --git a/src/evm/host.rs b/src/evm/host.rs index 23c457310..32bda99c6 100644 --- a/src/evm/host.rs +++ b/src/evm/host.rs @@ -1,24 +1,31 @@ -use crate::evm::bytecode_analyzer; -use crate::evm::input::{ConciseEVMInput, EVMInput, EVMInputT, EVMInputTy}; -use crate::evm::middlewares::middleware::{add_corpus, CallMiddlewareReturn, Middleware, MiddlewareType}; +use crate::evm::input::{ConciseEVMInput, EVMInput, EVMInputT}; +use crate::evm::middlewares::middleware::{ + add_corpus, CallMiddlewareReturn, Middleware, MiddlewareType, +}; use crate::evm::mutator::AccessPattern; - -use crate::evm::onchain::flashloan::register_borrow_txn; -use crate::evm::onchain::flashloan::{Flashloan, FlashloanData}; +use crate::evm::onchain::flashloan::Flashloan; use bytes::Bytes; use itertools::Itertools; -use libafl::prelude::{HasCorpus, Scheduler, HasRand, HasMetadata}; +use libafl::prelude::{HasCorpus, HasMetadata, HasRand, Scheduler}; use libafl::state::State; -use primitive_types::H256; -use revm::db::BenchmarkDB; -use revm_interpreter::InstructionResult::{Continue, ControlLeak, Return, Revert}; - +use revm_interpreter::InstructionResult::{Continue, ControlLeak, Revert}; +use crate::evm::types::{as_u64, generate_random_address, is_zero, EVMAddress, EVMU256}; +use crate::evm::vm::{EVMState, SinglePostExecution, IN_DEPLOY, IS_FAST_CALL_STATIC}; +use crate::generic_vm::vm_executor::MAP_SIZE; +use crate::generic_vm::vm_state::VMStateT; +use crate::input::VMInputT; +use revm::precompile::{Precompile, Precompiles}; +use revm_interpreter::analysis::to_analysed; +use revm_interpreter::{ + BytecodeLocked, CallContext, CallInputs, CallScheme, Contract, CreateInputs, Gas, Host, + InstructionResult, Interpreter, SelfDestructResult, +}; +use revm_primitives::{Bytecode, Env, LatestSpec, B256}; use std::cell::RefCell; use std::collections::hash_map::DefaultHasher; use std::collections::{HashMap, HashSet}; use std::fmt::{Debug, Formatter}; -use std::fs::OpenOptions; use std::hash::Hash; use std::hash::Hasher; use std::io::Write; @@ -26,31 +33,17 @@ use std::ops::Deref; use std::rc::Rc; use std::str::FromStr; use std::sync::Arc; -use std::time::{SystemTime, UNIX_EPOCH}; -use hex::FromHex; -use revm::precompile::{Precompile, Precompiles}; -use revm_interpreter::{BytecodeLocked, CallContext, CallInputs, CallScheme, Contract, CreateInputs, Gas, Host, InstructionResult, Interpreter, SelfDestructResult}; -use revm_interpreter::analysis::to_analysed; -use revm_primitives::{B256, Bytecode, Env, LatestSpec, Spec}; -use crate::evm::types::{as_u64, bytes_to_u64, EVMAddress, EVMU256, generate_random_address, is_zero}; -use crate::evm::uniswap::{generate_uniswap_router_call, TokenContext}; -use crate::evm::vm::{EVMState, IN_DEPLOY, IS_FAST_CALL_STATIC, PostExecutionCtx, SinglePostExecution}; -use crate::generic_vm::vm_executor::{ExecutionResult, GenericVM, MAP_SIZE}; -use crate::generic_vm::vm_state::VMStateT; -use crate::input::VMInputT; - -use crate::state::{HasCaller, HasCurrentInputIdx, HasHashToAddress, HasItyState}; -use revm_primitives::{SpecId, FrontierSpec, HomesteadSpec, TangerineSpec, SpuriousDragonSpec, ByzantiumSpec, - PetersburgSpec, IstanbulSpec, BerlinSpec, LondonSpec, MergeSpec, ShanghaiSpec}; use crate::evm::abi::{get_abi_type_boxed, register_abi_instance}; use crate::evm::contract_utils::extract_sig_from_contract; use crate::evm::corpus_initializer::ABIMap; -use crate::evm::input::EVMInputTy::ArbitraryCallBoundedAddr; use crate::evm::onchain::abi_decompiler::fetch_abi_heimdall; -use crate::handle_contract_insertion; +use crate::state::{HasCaller, HasHashToAddress, HasItyState}; use crate::state_input::StagedVMState; - +use revm_primitives::{ + BerlinSpec, ByzantiumSpec, FrontierSpec, HomesteadSpec, IstanbulSpec, LondonSpec, MergeSpec, + PetersburgSpec, ShanghaiSpec, SpecId, SpuriousDragonSpec, TangerineSpec, +}; pub static mut JMP_MAP: [u8; MAP_SIZE] = [0; MAP_SIZE]; @@ -79,10 +72,12 @@ pub static mut CALL_UNTIL: u32 = u32::MAX; /// Shall we dump the contract calls pub static mut WRITE_RELATIONSHIPS: bool = false; -const SCRIBBLE_EVENT_HEX: [u8; 32] = [0xb4,0x26,0x04,0xcb,0x10,0x5a,0x16,0xc8,0xf6,0xdb,0x8a,0x41,0xe6,0xb0,0x0c,0x0c,0x1b,0x48,0x26,0x46,0x5e,0x8b,0xc5,0x04,0xb3,0xeb,0x3e,0x88,0xb3,0xe6,0xa4,0xa0]; +const SCRIBBLE_EVENT_HEX: [u8; 32] = [ + 0xb4, 0x26, 0x04, 0xcb, 0x10, 0x5a, 0x16, 0xc8, 0xf6, 0xdb, 0x8a, 0x41, 0xe6, 0xb0, 0x0c, 0x0c, + 0x1b, 0x48, 0x26, 0x46, 0x5e, 0x8b, 0xc5, 0x04, 0xb3, 0xeb, 0x3e, 0x88, 0xb3, 0xe6, 0xa4, 0xa0, +]; pub static mut CONCRETE_CREATE: bool = false; - /// Check if address is precompile by having assumption /// that precompiles are in range of 1 to N. #[inline(always)] @@ -94,7 +89,6 @@ pub fn is_precompile(address: EVMAddress, num_of_precompiles: usize) -> bool { num.wrapping_sub(1) < num_of_precompiles as u16 } - pub struct FuzzHost where S: State + HasCaller + Debug + Clone + 'static, @@ -136,7 +130,7 @@ where // set_code data pub setcode_data: HashMap, // selftdestruct - pub selfdestruct_hit:bool, + pub selfdestruct_hit: bool, // relations file handle relations_file: std::fs::File, // Filter duplicate relations @@ -213,14 +207,14 @@ where call_count: 0, #[cfg(feature = "print_logs")] logs: Default::default(), - setcode_data:self.setcode_data.clone(), - selfdestruct_hit:self.selfdestruct_hit, + setcode_data: self.setcode_data.clone(), + selfdestruct_hit: self.selfdestruct_hit, relations_file: self.relations_file.try_clone().unwrap(), relations_hash: self.relations_hash.clone(), current_typed_bug: self.current_typed_bug.clone(), randomness: vec![], work_dir: self.work_dir.clone(), - spec_id: self.spec_id.clone(), + spec_id: self.spec_id, precompiles: Precompiles::default(), leak_ctx: self.leak_ctx.clone(), mapping_sstore_pcs: self.mapping_sstore_pcs.clone(), @@ -238,15 +232,23 @@ const UNBOUND_CALL_THRESHOLD: usize = 3; // if a PC transfers control to >2 addresses, we consider call at this PC to be unbounded const CONTROL_LEAK_THRESHOLD: usize = 2; - impl FuzzHost where - S: State +HasRand + HasCaller + Debug + Clone + HasCorpus + HasMetadata + HasItyState + 'static, + S: State + + HasRand + + HasCaller + + Debug + + Clone + + HasCorpus + + HasMetadata + + HasItyState + + 'static, I: VMInputT + EVMInputT + 'static, VS: VMStateT, { pub fn new(scheduler: Arc>, workdir: String) -> Self { - let ret = Self { + // ret.env.block.timestamp = EVMU256::max_value(); + Self { evmstate: EVMState::new(), env: Env::default(), code: HashMap::new(), @@ -268,21 +270,19 @@ where call_count: 0, #[cfg(feature = "print_logs")] logs: Default::default(), - setcode_data:HashMap::new(), - selfdestruct_hit:false, + setcode_data: HashMap::new(), + selfdestruct_hit: false, relations_file: std::fs::File::create(format!("{}/relations.log", workdir)).unwrap(), relations_hash: HashSet::new(), current_typed_bug: Default::default(), randomness: vec![], - work_dir: workdir.clone(), + work_dir: workdir, spec_id: SpecId::LATEST, precompiles: Default::default(), leak_ctx: vec![], mapping_sstore_pcs: Default::default(), mapping_sstore_pcs_to_slot: Default::default(), - }; - // ret.env.block.timestamp = EVMU256::max_value(); - ret + } } pub fn set_spec_id(&mut self, spec_id: String) { @@ -290,25 +290,39 @@ where } /// custom spec id run_inspect - pub fn run_inspect( - &mut self, - mut interp: &mut Interpreter, - mut state: &mut S, - ) -> InstructionResult { + pub fn run_inspect(&mut self, interp: &mut Interpreter, state: &mut S) -> InstructionResult { match self.spec_id { SpecId::LATEST => interp.run_inspect::, LatestSpec>(self, state), - SpecId::FRONTIER => interp.run_inspect::, FrontierSpec>(self, state), - SpecId::HOMESTEAD => interp.run_inspect::, HomesteadSpec>(self, state), - SpecId::TANGERINE => interp.run_inspect::, TangerineSpec>(self, state), - SpecId::SPURIOUS_DRAGON => interp.run_inspect::, SpuriousDragonSpec>(self, state), - SpecId::BYZANTIUM => interp.run_inspect::, ByzantiumSpec>( self, state), - SpecId::CONSTANTINOPLE | SpecId::PETERSBURG => interp.run_inspect::, PetersburgSpec>(self, state), - SpecId::ISTANBUL => interp.run_inspect::, IstanbulSpec>(self, state), - SpecId::MUIR_GLACIER | SpecId::BERLIN => interp.run_inspect::, BerlinSpec>(self, state), + SpecId::FRONTIER => { + interp.run_inspect::, FrontierSpec>(self, state) + } + SpecId::HOMESTEAD => { + interp.run_inspect::, HomesteadSpec>(self, state) + } + SpecId::TANGERINE => { + interp.run_inspect::, TangerineSpec>(self, state) + } + SpecId::SPURIOUS_DRAGON => { + interp.run_inspect::, SpuriousDragonSpec>(self, state) + } + SpecId::BYZANTIUM => { + interp.run_inspect::, ByzantiumSpec>(self, state) + } + SpecId::CONSTANTINOPLE | SpecId::PETERSBURG => { + interp.run_inspect::, PetersburgSpec>(self, state) + } + SpecId::ISTANBUL => { + interp.run_inspect::, IstanbulSpec>(self, state) + } + SpecId::MUIR_GLACIER | SpecId::BERLIN => { + interp.run_inspect::, BerlinSpec>(self, state) + } SpecId::LONDON => interp.run_inspect::, LondonSpec>(self, state), SpecId::MERGE => interp.run_inspect::, MergeSpec>(self, state), - SpecId::SHANGHAI => interp.run_inspect::, ShanghaiSpec>(self, state), - _=> interp.run_inspect::, LatestSpec>(self, state), + SpecId::SHANGHAI => { + interp.run_inspect::, ShanghaiSpec>(self, state) + } + _ => interp.run_inspect::, LatestSpec>(self, state), } } @@ -319,11 +333,7 @@ where pub fn add_middlewares(&mut self, middlewares: Rc>>) { self.middlewares_enabled = true; - let ty = middlewares.deref().borrow().get_type(); - self.middlewares - .deref() - .borrow_mut() - .push(middlewares); + self.middlewares.deref().borrow_mut().push(middlewares); } pub fn remove_middlewares(&mut self, middlewares: Rc>>) { @@ -401,7 +411,7 @@ where } } - pub fn set_codedata(&mut self, address: EVMAddress, mut code: Bytecode) { + pub fn set_codedata(&mut self, address: EVMAddress, code: Bytecode) { self.setcode_data.insert(address, code); } @@ -412,15 +422,12 @@ where pub fn set_code(&mut self, address: EVMAddress, mut code: Bytecode, state: &mut S) { unsafe { if self.middlewares_enabled { - match self.flashloan_middleware.clone() { - Some(m) => { - let mut middleware = m.deref().borrow_mut(); - middleware.on_insert(&mut code, address, self, state); - } - _ => {} + if let Some(m) = self.flashloan_middleware.clone() { + let mut middleware = m.deref().borrow_mut(); + middleware.on_insert(&mut code, address, self, state); } - for middleware in &mut self.middlewares.clone().deref().borrow_mut().iter_mut() - { + + for middleware in &mut self.middlewares.clone().deref().borrow_mut().iter_mut() { middleware .deref() .deref() @@ -444,7 +451,7 @@ where data: Bytes, state: &mut S, ) -> Vec { - return vec![]; + vec![] // let call = Contract::new_with_context_not_cloned::( // data, // self.code.get(&address).expect("no code").clone(), @@ -469,15 +476,20 @@ where if funtion_hash.len() < 0x4 { return; } - let cur_write_str = format!("{{caller:0x{} --> traget:0x{} function(0x{})}}\n", hex::encode(caller), hex::encode(target), hex::encode(&funtion_hash[..4])); + let cur_write_str = format!( + "{{caller:0x{} --> traget:0x{} function(0x{})}}\n", + hex::encode(caller), + hex::encode(target), + hex::encode(&funtion_hash[..4]) + ); let mut hasher = DefaultHasher::new(); cur_write_str.hash(&mut hasher); let cur_wirte_hash = hasher.finish(); if self.relations_hash.contains(&cur_wirte_hash) { return; } - if self.relations_hash.len() == 0{ - let write_head = format!("[ityfuzz relations] caller, traget, function hash\n"); + if self.relations_hash.is_empty() { + let write_head = "[ityfuzz relations] caller, traget, function hash\n".to_string(); self.relations_file .write_all(write_head.as_bytes()) .unwrap(); @@ -489,27 +501,29 @@ where .unwrap(); } - fn call_allow_control_leak(&mut self, input: &mut CallInputs, interp: &mut Interpreter, (out_offset, out_len): (usize, usize), state: &mut S) -> (InstructionResult, Gas, Bytes) { + fn call_allow_control_leak( + &mut self, + input: &mut CallInputs, + interp: &mut Interpreter, + (out_offset, out_len): (usize, usize), + state: &mut S, + ) -> (InstructionResult, Gas, Bytes) { macro_rules! push_interp { - - () => { - unsafe { - self.leak_ctx = vec![SinglePostExecution::from_interp(interp, (out_offset, out_len))]; - } - }; + () => {{ + self.leak_ctx = vec![SinglePostExecution::from_interp( + interp, + (out_offset, out_len), + )]; + }}; } self.call_count += 1; - if self.call_count >= unsafe {CALL_UNTIL} { + if self.call_count >= unsafe { CALL_UNTIL } { push_interp!(); return (ControlLeak, Gas::new(0), Bytes::new()); } if unsafe { WRITE_RELATIONSHIPS } { - self.write_relations( - input.transfer.source.clone(), - input.contract.clone(), - input.input.clone(), - ); + self.write_relations(input.transfer.source, input.contract, input.input.clone()); } let mut hash = input.input.to_vec(); @@ -544,16 +558,13 @@ where } self.middlewares_latent_call_actions.clear(); - if middleware_result.is_some() { - return middleware_result.unwrap(); + if let Some(r) = middleware_result { + return r; } - - - let mut input_seq = input.input.to_vec(); + let input_seq = input.input.to_vec(); if input.context.scheme == CallScheme::Call { - // if calling sender, then definitely control leak if state.has_caller(&input.contract) { record_func_hash!(); @@ -562,88 +573,100 @@ where return (ControlLeak, Gas::new(0), Bytes::new()); } // check whether the whole CALLDATAVALUE can be arbitrary - if !self.pc_to_call_hash.contains_key(&(input.context.caller, self._pc)) { - self.pc_to_call_hash.insert((input.context.caller, self._pc), HashSet::new()); - } + self.pc_to_call_hash + .entry((input.context.caller, self._pc)) + .or_insert_with(HashSet::new); self.pc_to_call_hash .get_mut(&(input.context.caller, self._pc)) .unwrap() .insert(hash.to_vec()); - if self.pc_to_call_hash.get(&(input.context.caller, self._pc)).unwrap().len() > UNBOUND_CALL_THRESHOLD + if self + .pc_to_call_hash + .get(&(input.context.caller, self._pc)) + .unwrap() + .len() + > UNBOUND_CALL_THRESHOLD && input_seq.len() >= 4 { // println!("ub leak {:?} -> {:?} with {:?}", input.context.caller, input.contract, hex::encode(input.input.clone())); push_interp!(); return ( - InstructionResult::ArbitraryExternalCallAddressBounded(input.context.caller, input.context.address), + InstructionResult::ArbitraryExternalCallAddressBounded( + input.context.caller, + input.context.address, + ), Gas::new(0), - Bytes::new() + Bytes::new(), ); } // control leak check assert_ne!(self._pc, 0); - if !self.pc_to_addresses.contains_key(&(input.context.caller, self._pc)) { - self.pc_to_addresses.insert((input.context.caller, self._pc), HashSet::new()); - } - let addresses_at_pc = self.pc_to_addresses + self.pc_to_addresses + .entry((input.context.caller, self._pc)) + .or_insert_with(HashSet::new); + let addresses_at_pc = self + .pc_to_addresses .get_mut(&(input.context.caller, self._pc)) .unwrap(); addresses_at_pc.insert(input.contract); // if control leak is enabled, return controlleak if it is unbounded call - if CONTROL_LEAK_DETECTION == true { - if addresses_at_pc.len() > CONTROL_LEAK_THRESHOLD { - record_func_hash!(); - push_interp!(); - // println!("control leak {:?} -> {:?} with {:?}", input.context.caller, input.contract, hex::encode(input.input.clone())); - return (ControlLeak, Gas::new(0), Bytes::new()); - } + if CONTROL_LEAK_DETECTION && addresses_at_pc.len() > CONTROL_LEAK_THRESHOLD { + record_func_hash!(); + push_interp!(); + // println!("control leak {:?} -> {:?} with {:?}", input.context.caller, input.contract, hex::encode(input.input.clone())); + return (ControlLeak, Gas::new(0), Bytes::new()); } } - let input_bytes = Bytes::from(input_seq); // find contracts that have this function hash let contract_loc_option = self.hash_to_address.get(hash.as_slice()); - if unsafe { ACTIVE_MATCH_EXT_CALL } && contract_loc_option.is_some() { - let loc = contract_loc_option.unwrap(); - // if there is such a location known, then we can use exact call - if !loc.contains(&input.contract) { - // todo(@shou): resolve multi locs - if loc.len() != 1 { - panic!("more than one contract found for the same hash"); - } - let mut interp = Interpreter::new( - Contract::new_with_context_analyzed( - input_bytes, - self.code.get(loc.iter().nth(0).unwrap()).unwrap().clone(), - &input.context, - ), - 1e10 as u64, - false - ); + if unsafe { ACTIVE_MATCH_EXT_CALL } { + if let Some(loc) = contract_loc_option { + // if there is such a location known, then we can use exact call + if !loc.contains(&input.contract) { + // todo(@shou): resolve multi locs + if loc.len() != 1 { + panic!("more than one contract found for the same hash"); + } + let mut interp = Interpreter::new( + Contract::new_with_context_analyzed( + input_bytes, + self.code.get(loc.iter().next().unwrap()).unwrap().clone(), + &input.context, + ), + 1e10 as u64, + false, + ); - let ret = self.run_inspect(&mut interp, state); - return (ret, Gas::new(0), interp.return_value()); + let ret = self.run_inspect(&mut interp, state); + return (ret, Gas::new(0), interp.return_value()); + } } } // if there is code, then call the code let res = self.call_forbid_control_leak(input, state); match res.0 { - ControlLeak | InstructionResult::ArbitraryExternalCallAddressBounded(_, _) => unsafe { - unsafe { - self.leak_ctx.push(SinglePostExecution::from_interp(interp, (out_offset, out_len))); - } + ControlLeak | InstructionResult::ArbitraryExternalCallAddressBounded(_, _) => { + self.leak_ctx.push(SinglePostExecution::from_interp( + interp, + (out_offset, out_len), + )); } _ => {} } res } - fn call_forbid_control_leak(&mut self, input: &mut CallInputs, state: &mut S) -> (InstructionResult, Gas, Bytes) { + fn call_forbid_control_leak( + &mut self, + input: &mut CallInputs, + state: &mut S, + ) -> (InstructionResult, Gas, Bytes) { let mut hash = input.input.to_vec(); hash.resize(4, 0); // if there is code, then call the code @@ -655,7 +678,7 @@ where &input.context, ), 1e10 as u64, - false + false, ); let ret = self.run_inspect(&mut interp, state); return (ret, Gas::new(0), interp.return_value()); @@ -665,25 +688,29 @@ where if hash == [0x00, 0x00, 0x00, 0x00] { return (Continue, Gas::new(0), Bytes::new()); } - return (Revert, Gas::new(0), Bytes::new()); + (Revert, Gas::new(0), Bytes::new()) } - fn call_precompile(&mut self, input: &mut CallInputs, state: &mut S) -> (InstructionResult, Gas, Bytes) { + fn call_precompile( + &mut self, + input: &mut CallInputs, + state: &mut S, + ) -> (InstructionResult, Gas, Bytes) { let precompile = self .precompiles .get(&input.contract) .expect("Check for precompile should be already done"); let out = match precompile { - Precompile::Standard(fun) => fun(&input.input.to_vec().as_slice(), u64::MAX), - Precompile::Custom(fun) => fun(&input.input.to_vec().as_slice(), u64::MAX), + Precompile::Standard(fun) => fun(input.input.to_vec().as_slice(), u64::MAX), + Precompile::Custom(fun) => fun(input.input.to_vec().as_slice(), u64::MAX), }; match out { - Ok((_, data)) => { - (InstructionResult::Return, Gas::new(0), Bytes::from(data)) - } - Err(e) => { - (InstructionResult::PrecompileError, Gas::new(0), Bytes::new()) - } + Ok((_, data)) => (InstructionResult::Return, Gas::new(0), Bytes::from(data)), + Err(_) => ( + InstructionResult::PrecompileError, + Gas::new(0), + Bytes::new(), + ), } } } @@ -707,7 +734,6 @@ macro_rules! u256_to_u8 { }; } - #[macro_export] macro_rules! invoke_middlewares { ($host: expr, $interp: expr, $state: expr, $invoke: ident) => { @@ -722,8 +748,7 @@ macro_rules! invoke_middlewares { if $host.setcode_data.len() > 0 { $host.clear_codedata(); } - for middleware in &mut $host.middlewares.clone().deref().borrow_mut().iter_mut() - { + for middleware in &mut $host.middlewares.clone().deref().borrow_mut().iter_mut() { middleware .deref() .deref() @@ -731,7 +756,6 @@ macro_rules! invoke_middlewares { .$invoke($interp, $host, $state); } - if $host.setcode_data.len() > 0 { for (address, code) in &$host.setcode_data.clone() { $host.set_code(address.clone(), code.clone(), $state); @@ -746,15 +770,13 @@ macro_rules! invoke_middlewares { Some(m) => { let mut middleware = m.deref().borrow_mut(); middleware.$invoke($code, $addr, $host, $state); - } _ => {} } if $host.setcode_data.len() > 0 { $host.clear_codedata(); } - for middleware in &mut $host.middlewares.clone().deref().borrow_mut().iter_mut() - { + for middleware in &mut $host.middlewares.clone().deref().borrow_mut().iter_mut() { middleware .deref() .deref() @@ -762,7 +784,6 @@ macro_rules! invoke_middlewares { .$invoke($code, $addr, $host, $state); } - if $host.setcode_data.len() > 0 { for (address, code) in &$host.setcode_data.clone() { $host.set_code(address.clone(), code.clone(), $state); @@ -774,7 +795,15 @@ macro_rules! invoke_middlewares { impl Host for FuzzHost where - S: State +HasRand + HasCaller + Debug + Clone + HasCorpus + HasMetadata + HasItyState + 'static, + S: State + + HasRand + + HasCaller + + Debug + + Clone + + HasCorpus + + HasMetadata + + HasItyState + + 'static, I: VMInputT + EVMInputT + 'static, VS: VMStateT, { @@ -806,9 +835,7 @@ where if JMP_MAP[idx] == 0 { self.coverage_changed = true; } - if JMP_MAP[idx] < 255 { - JMP_MAP[idx] += 1; - } + JMP_MAP[idx] = JMP_MAP[idx].saturating_add(1); #[cfg(feature = "cmp")] { @@ -821,12 +848,19 @@ where 0x55 => { // SSTORE let pc = interp.program_counter(); - if !self.mapping_sstore_pcs.contains(&(interp.contract.address, pc)) { + if !self + .mapping_sstore_pcs + .contains(&(interp.contract.address, pc)) + { let mut key = fast_peek!(0); - let slots = self.mapping_sstore_pcs_to_slot.entry((interp.contract.address, pc)).or_default(); + let slots = self + .mapping_sstore_pcs_to_slot + .entry((interp.contract.address, pc)) + .or_default(); slots.insert(key); if slots.len() > 10 { - self.mapping_sstore_pcs.insert((interp.contract.address, pc)); + self.mapping_sstore_pcs + .insert((interp.contract.address, pc)); } let value = fast_peek!(1); @@ -845,8 +879,6 @@ where STATE_CHANGE |= value_changed; } - - } #[cfg(feature = "dataflow")] @@ -919,7 +951,7 @@ where 0xf4 | 0xfa => 5, _ => unreachable!(), }; - unsafe { + { RET_OFFSET = as_u64(fast_peek!(offset_of_ret_size - 1)) as usize; // println!("RET_OFFSET: {}", RET_OFFSET); RET_SIZE = as_u64(fast_peek!(offset_of_ret_size)) as usize; @@ -938,15 +970,20 @@ where .borrow_mut() .decode_instruction(interp); } - return Continue; + Continue } - fn step_end(&mut self, _interp: &mut Interpreter, _ret: InstructionResult, _: &mut S) -> InstructionResult { - return Continue; + fn step_end( + &mut self, + _interp: &mut Interpreter, + _ret: InstructionResult, + _: &mut S, + ) -> InstructionResult { + Continue } fn env(&mut self) -> &mut Env { - return &mut self.env; + &mut self.env } fn load_account(&mut self, _address: EVMAddress) -> Option<(bool, bool)> { @@ -973,9 +1010,7 @@ where // println!("code"); match self.code.get(&address) { Some(code) => Some((code.clone(), true)), - None => Some((Arc::new( - BytecodeLocked::default() - ), true)), + None => Some((Arc::new(BytecodeLocked::default()), true)), } } @@ -990,7 +1025,7 @@ where fn sload(&mut self, address: EVMAddress, index: EVMU256) -> Option<(EVMU256, bool)> { if let Some(account) = self.evmstate.get(&address) { if let Some(slot) = account.get(&index) { - return Some((slot.clone(), true)); + return Some((*slot, true)); } } Some((self.next_slot, true)) @@ -1022,20 +1057,27 @@ where fn log(&mut self, _address: EVMAddress, _topics: Vec, _data: Bytes) { // flag check - if _topics.len() == 1 { - let current_flag = (*_topics.last().unwrap()).0; - /// hex is "fuzzland" - if current_flag[0] == 0x66 && current_flag[1] == 0x75 && current_flag[2] == 0x7a && current_flag[3] == 0x7a && - current_flag[4] == 0x6c && current_flag[5] == 0x61 && current_flag[6] == 0x6e && current_flag[7] == 0x64 && - current_flag[8] == 0x00 && current_flag[9] == 0x00 - || current_flag == SCRIBBLE_EVENT_HEX { + if _topics.len() == 1 { + let current_flag = _topics.last().unwrap().0; + // hex is "fuzzland" + if current_flag[0] == 0x66 + && current_flag[1] == 0x75 + && current_flag[2] == 0x7a + && current_flag[3] == 0x7a + && current_flag[4] == 0x6c + && current_flag[5] == 0x61 + && current_flag[6] == 0x6e + && current_flag[7] == 0x64 + && current_flag[8] == 0x00 + && current_flag[9] == 0x00 + || current_flag == SCRIBBLE_EVENT_HEX + { let data_string = String::from_utf8(_data[64..].to_vec()).unwrap(); - if unsafe {PANIC_ON_BUG} { - panic!( - "target bug found: {}", data_string - ); + if unsafe { PANIC_ON_BUG } { + panic!("target bug found: {}", data_string); } - self.current_typed_bug.push(data_string.clone().trim_end_matches("\u{0}").to_string()); + self.current_typed_bug + .push(data_string.trim_end_matches('\u{0}').to_string()); } } @@ -1056,8 +1098,12 @@ where } } - fn selfdestruct(&mut self, _address: EVMAddress, _target: EVMAddress) -> Option { - return Some(SelfDestructResult::default()); + fn selfdestruct( + &mut self, + _address: EVMAddress, + _target: EVMAddress, + ) -> Option { + Some(SelfDestructResult::default()) } fn create( @@ -1065,144 +1111,127 @@ where inputs: &mut CreateInputs, state: &mut S, ) -> (InstructionResult, Option, Gas, Bytes) { - unsafe { - if unsafe {CONCRETE_CREATE || IN_DEPLOY} { - // todo: use nonce + hash instead - let r_addr = generate_random_address(state); - let mut interp = Interpreter::new( - Contract::new_with_context( - Bytes::new(), - Bytecode::new_raw(inputs.init_code.clone()), - &CallContext { - address: r_addr, - caller: inputs.caller, - code_address: r_addr, - apparent_value: inputs.value, - scheme: CallScheme::Call, - }, - ), - 1e10 as u64, - false - ); - let ret = self.run_inspect(&mut interp, state); - if ret == InstructionResult::Continue { - let runtime_code = interp.return_value(); - self.set_code( - r_addr, - Bytecode::new_raw(runtime_code.clone()), - state - ); - { - // now we build & insert abi - let contract_code_str = hex::encode(runtime_code.clone()); - let sigs = extract_sig_from_contract(&contract_code_str); - let mut unknown_sigs: usize = 0; - let mut parsed_abi = vec![]; - for sig in &sigs { - if let Some(abi) = state.metadata().get::().unwrap().get(sig) { - parsed_abi.push(abi.clone()); - } else { - unknown_sigs += 1; - } - } - - if unknown_sigs >= sigs.len() / 30 { - println!("Too many unknown function signature for newly created contract, we are going to decompile this contract using Heimdall"); - let abis = fetch_abi_heimdall(contract_code_str) - .iter() - .map(|abi| { - if let Some(known_abi) = state.metadata().get::().unwrap().get(&abi.function) { - known_abi - } else { - abi - } - }) - .cloned() - .collect_vec(); - parsed_abi = abis; - } - // notify flashloan and blacklisting flashloan addresses - #[cfg(feature = "flashloan_v2")] - { - handle_contract_insertion!(state, self, r_addr, parsed_abi); + if unsafe { CONCRETE_CREATE || IN_DEPLOY } { + // todo: use nonce + hash instead + let r_addr = generate_random_address(state); + let mut interp = Interpreter::new( + Contract::new_with_context( + Bytes::new(), + Bytecode::new_raw(inputs.init_code.clone()), + &CallContext { + address: r_addr, + caller: inputs.caller, + code_address: r_addr, + apparent_value: inputs.value, + scheme: CallScheme::Call, + }, + ), + 1e10 as u64, + false, + ); + let ret = self.run_inspect(&mut interp, state); + if ret == InstructionResult::Continue { + let runtime_code = interp.return_value(); + self.set_code(r_addr, Bytecode::new_raw(runtime_code.clone()), state); + { + // now we build & insert abi + let contract_code_str = hex::encode(runtime_code.clone()); + let sigs = extract_sig_from_contract(&contract_code_str); + let mut unknown_sigs: usize = 0; + let mut parsed_abi = vec![]; + for sig in &sigs { + if let Some(abi) = state.metadata().get::().unwrap().get(sig) { + parsed_abi.push(abi.clone()); + } else { + unknown_sigs += 1; } + } - parsed_abi + if unknown_sigs >= sigs.len() / 30 { + println!("Too many unknown function signature for newly created contract, we are going to decompile this contract using Heimdall"); + let abis = fetch_abi_heimdall(contract_code_str) .iter() - .filter(|v| !v.is_constructor) - .for_each(|abi| { - #[cfg(not(feature = "fuzz_static"))] - if abi.is_static { - return; + .map(|abi| { + if let Some(known_abi) = + state.metadata().get::().unwrap().get(&abi.function) + { + known_abi + } else { + abi } + }) + .cloned() + .collect_vec(); + parsed_abi = abis; + } + // notify flashloan and blacklisting flashloan addresses + #[cfg(feature = "flashloan_v2")] + { + handle_contract_insertion!(state, self, r_addr, parsed_abi); + } - let mut abi_instance = get_abi_type_boxed(&abi.abi); - abi_instance - .set_func_with_name(abi.function, abi.function_name.clone()); - register_abi_instance(r_addr, abi_instance.clone(), state); - - let input = EVMInput { - caller: state.get_rand_caller(), - contract: r_addr, - data: Some(abi_instance), - sstate: StagedVMState::new_uninitialized(), - sstate_idx: 0, - txn_value: if abi.is_payable { - Some(EVMU256::ZERO) - } else { - None - }, - step: false, - - env: Default::default(), - access_pattern: Rc::new(RefCell::new(AccessPattern::new())), - #[cfg(feature = "flashloan_v2")] - liquidation_percent: 0, - #[cfg(feature = "flashloan_v2")] - input_type: EVMInputTy::ABI, - direct_data: Default::default(), - randomness: vec![0], - repeat: 1, - }; - add_corpus(self, state, &input); - }); + parsed_abi + .iter() + .filter(|v| !v.is_constructor) + .for_each(|abi| { + #[cfg(not(feature = "fuzz_static"))] + if abi.is_static { + return; + } - } - ( - Continue, - Some(r_addr), - Gas::new(0), - runtime_code, - ) - } else { - ( - ret, - Some(r_addr), - Gas::new(0), - Bytes::new(), - ) + let mut abi_instance = get_abi_type_boxed(&abi.abi); + abi_instance + .set_func_with_name(abi.function, abi.function_name.clone()); + register_abi_instance(r_addr, abi_instance.clone(), state); + + let input = EVMInput { + caller: state.get_rand_caller(), + contract: r_addr, + data: Some(abi_instance), + sstate: StagedVMState::new_uninitialized(), + sstate_idx: 0, + txn_value: if abi.is_payable { + Some(EVMU256::ZERO) + } else { + None + }, + step: false, + + env: Default::default(), + access_pattern: Rc::new(RefCell::new(AccessPattern::new())), + #[cfg(feature = "flashloan_v2")] + liquidation_percent: 0, + #[cfg(feature = "flashloan_v2")] + input_type: EVMInputTy::ABI, + direct_data: Default::default(), + randomness: vec![0], + repeat: 1, + }; + add_corpus(self, state, &input); + }); } + (Continue, Some(r_addr), Gas::new(0), runtime_code) } else { - ( - InstructionResult::Revert, - None, - Gas::new(0), - Bytes::new(), - ) + (ret, Some(r_addr), Gas::new(0), Bytes::new()) } - + } else { + (InstructionResult::Revert, None, Gas::new(0), Bytes::new()) } } - fn call(&mut self, input: &mut CallInputs, interp: &mut Interpreter, output_info: (usize, usize), state: &mut S) -> (InstructionResult, Gas, Bytes) { + fn call( + &mut self, + input: &mut CallInputs, + interp: &mut Interpreter, + output_info: (usize, usize), + state: &mut S, + ) -> (InstructionResult, Gas, Bytes) { let res = if is_precompile(input.contract, self.precompiles.len()) { self.call_precompile(input, state) + } else if unsafe { IS_FAST_CALL_STATIC } { + self.call_forbid_control_leak(input, state) } else { - if unsafe { IS_FAST_CALL_STATIC } { - self.call_forbid_control_leak(input, state) - } else { - self.call_allow_control_leak(input, interp, output_info, state) - } + self.call_allow_control_leak(input, interp, output_info, state) }; unsafe { diff --git a/src/evm/middlewares/call_printer.rs b/src/evm/middlewares/call_printer.rs index 73705a612..08fd7bc42 100644 --- a/src/evm/middlewares/call_printer.rs +++ b/src/evm/middlewares/call_printer.rs @@ -20,24 +20,17 @@ pub struct CallPrinter { impl CallPrinter { pub fn new() -> Self { - Self { - layer: 0, - data: "".to_string(), - } + Self::default() } pub fn register_input(&mut self, input: &EVMInput) { self.layer = 0; - self.data = "".to_string(); - self.data.push_str( - format!( - "[{:?}]=>{:?} {}", - input.get_caller(), - input.get_contract(), - hex::encode(input.to_bytes()) - ) - .as_str(), - ) + self.data = format!( + "[{:?}]=>{:?} {}", + input.get_caller(), + input.get_contract(), + hex::encode(input.to_bytes()) + ); } pub fn get_trace(&self) -> String { @@ -65,12 +58,13 @@ where host: &mut FuzzHost, state: &mut S, ) { + // handle CALL / CALLCODE / DELEGATECALL / STATICCALL let (arg_offset, arg_len) = match unsafe { *interp.instruction_pointer } { + // CALL / CALLCODE 0xf1 | 0xf2 => (interp.stack.peek(3).unwrap(), interp.stack.peek(4).unwrap()), + // DELEGATECALL / STATICCALL 0xf4 | 0xfa => (interp.stack.peek(2).unwrap(), interp.stack.peek(3).unwrap()), - _ => { - return; - } + _ => return, }; self.layer += 1; diff --git a/src/evm/middlewares/coverage.rs b/src/evm/middlewares/coverage.rs index 253634deb..a46f2fc06 100644 --- a/src/evm/middlewares/coverage.rs +++ b/src/evm/middlewares/coverage.rs @@ -1,9 +1,8 @@ use crate::evm::host::FuzzHost; -use crate::evm::input::{ConciseEVMInput, EVMInput, EVMInputT}; +use crate::evm::input::{ConciseEVMInput, EVMInputT}; use crate::evm::middlewares::middleware::{Middleware, MiddlewareType}; -use crate::evm::srcmap::parser::SourceMapAvailability::Available; use crate::evm::srcmap::parser::{ - pretty_print_source_map, SourceMapAvailability, SourceMapLocation, SourceMapWithCode, + pretty_print_source_map, SourceMapAvailability, SourceMapWithCode, }; use crate::evm::types::{is_zero, EVMAddress, ProjectSourceMapTy}; use crate::evm::vm::IN_DEPLOY; @@ -13,17 +12,16 @@ use crate::state::{HasCaller, HasCurrentInputIdx, HasItyState}; use itertools::Itertools; use libafl::inputs::Input; use libafl::prelude::{HasCorpus, HasMetadata, State}; -use revm_interpreter::opcode::{INVALID, JUMPDEST, JUMPI, REVERT, STOP}; +use revm_interpreter::opcode::{INVALID, JUMPDEST, JUMPI, STOP}; use revm_interpreter::Interpreter; use revm_primitives::Bytecode; use serde::Serialize; use serde_json; use std::collections::{HashMap, HashSet}; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::fs; use std::fs::OpenOptions; use std::io::Write; -use std::ops::AddAssign; use std::path::Path; use std::time::{SystemTime, UNIX_EPOCH}; @@ -48,7 +46,7 @@ pub fn instructions_pc(bytecode: &Bytecode) -> (HashSet, HashSet, } complete_bytes.push(i); i += 1; - if op >= 0x60 && op <= 0x7f { + if (0x60..=0x7f).contains(&op) { i += op as usize - 0x5f; } } @@ -59,7 +57,7 @@ pub fn instructions_pc(bytecode: &Bytecode) -> (HashSet, HashSet, ) } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Coverage { pub pc_coverage: HashMap>, pub total_instr_set: HashMap>, @@ -67,13 +65,12 @@ pub struct Coverage { pub jumpi_coverage: HashMap>, pub skip_pcs: HashMap>, pub work_dir: String, - pub sourcemap: ProjectSourceMapTy, pub address_to_name: HashMap, pub pc_info: HashMap<(EVMAddress, usize), SourceMapWithCode>, } -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Serialize, Default)] pub struct CoverageResult { pub instruction_coverage: usize, pub total_instructions: usize, @@ -85,29 +82,53 @@ pub struct CoverageResult { impl CoverageResult { pub fn new() -> Self { - Self { - instruction_coverage: 0, - total_instructions: 0, - branch_coverage: 0, - total_branches: 0, - uncovered: HashSet::new(), - uncovered_pc: vec![], - } + Self::default() } } -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Serialize, Default)] pub struct CoverageReport { pub coverage: HashMap, } -impl CoverageReport { - pub fn new() -> Self { - Self { - coverage: HashMap::new(), +impl Display for CoverageReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (addr, cov) in &self.coverage { + writeln!(f, "Contract: {}", addr)?; + writeln!( + f, + "Instruction Coverage: {}/{} ({:.2}%) ", + cov.instruction_coverage, + cov.total_instructions, + (cov.instruction_coverage * 100) as f64 / cov.total_instructions as f64 + )?; + writeln!( + f, + "Branch Coverage: {}/{} ({:.2}%) ", + cov.branch_coverage, + cov.total_branches, + (cov.branch_coverage * 100) as f64 / cov.total_branches as f64 + )?; + + if !cov.uncovered.is_empty() { + writeln!(f, "Uncovered Code:")?; + for uncovered in &cov.uncovered { + writeln!(f, "{}", uncovered.to_string())?; + } + } + + writeln!(f, "Uncovered PCs: {:?}", cov.uncovered_pc)?; + writeln!(f, "--------------------------------")?; } + Ok(()) } +} +impl CoverageReport { + pub fn new() -> Self { + Self::default() + } + /* pub fn to_string(&self) -> String { let mut s = String::new(); for (addr, cov) in &self.coverage { @@ -125,19 +146,19 @@ impl CoverageReport { (cov.branch_coverage * 100) as f64 / cov.total_branches as f64 )); - if cov.uncovered.len() > 0 { - s.push_str(&format!("Uncovered Code:\n")); + if !cov.uncovered.is_empty() { + s.push_str("Uncovered Code:\n"); for uncovered in &cov.uncovered { s.push_str(&format!("{}\n\n", uncovered.to_string())); } } s.push_str(&format!("Uncovered PCs: {:?}\n", cov.uncovered_pc)); - s.push_str(&format!("--------------------------------\n")); + s.push_str("--------------------------------\n"); } s } - + */ pub fn dump_file(&self, work_dir: String) { let mut text_file = OpenOptions::new() .write(true) @@ -146,7 +167,7 @@ impl CoverageReport { .truncate(true) .open(format!( "{}/cov_{}.txt", - work_dir.clone(), + work_dir, SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -201,22 +222,17 @@ impl Coverage { } Self { - pc_coverage: HashMap::new(), - total_instr_set: HashMap::new(), - total_jumpi_set: Default::default(), - jumpi_coverage: Default::default(), - skip_pcs: Default::default(), work_dir, sourcemap, address_to_name, - pc_info: Default::default(), + ..Self::default() } } pub fn record_instruction_coverage(&mut self) { let mut report = CoverageReport::new(); - /// Figure out covered and not covered instructions + // Figure out covered and not covered instructions let default_skipper = HashSet::new(); for (addr, all_pcs) in &self.total_instr_set { @@ -231,7 +247,7 @@ impl Coverage { .clone(); // Handle Instruction Coverage - let mut real_covered: HashSet = + let real_covered: HashSet = covered.difference(skip_pcs).cloned().collect(); let uncovered_pc = all_pcs.difference(&real_covered).cloned().collect_vec(); report.coverage.insert( @@ -248,8 +264,7 @@ impl Coverage { let mut result_ref = report.coverage.get_mut(&name).unwrap(); for pc in uncovered_pc { - if let Some(source_map) = self.pc_info.get(&(*addr, pc)).map(|x| x.clone()) - { + if let Some(source_map) = self.pc_info.get(&(*addr, pc)).cloned() { result_ref.uncovered.insert(source_map.clone()); } } @@ -290,6 +305,7 @@ where + Debug + Clone, { + #[allow(unused_variables)] unsafe fn on_step( &mut self, interp: &mut Interpreter, @@ -312,6 +328,7 @@ where } } + #[allow(unused_variables)] unsafe fn on_insert( &mut self, bytecode: &mut Bytecode, @@ -357,6 +374,7 @@ where MiddlewareType::InstructionCoverage } + #[allow(unused_variables)] unsafe fn on_return( &mut self, interp: &mut Interpreter, @@ -366,6 +384,7 @@ where } } +#[allow(unused_imports)] mod tests { use super::*; use bytes::Bytes; diff --git a/src/evm/vm.rs b/src/evm/vm.rs index 8da0d8207..9d841dab5 100644 --- a/src/evm/vm.rs +++ b/src/evm/vm.rs @@ -1,22 +1,14 @@ /// EVM executor implementation use itertools::Itertools; -use std::borrow::{Borrow, BorrowMut}; use std::cell::RefCell; -use std::cmp::{max, min}; -use std::collections::{HashMap, HashSet}; - +use std::cmp::min; use std::collections::hash_map::DefaultHasher; -use std::fmt::{Debug, Formatter}; -use std::fs::OpenOptions; - +use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; use std::hash::{Hash, Hasher}; -use std::io::Write; - use std::marker::PhantomData; use std::ops::Deref; - use std::rc::Rc; -use std::str::FromStr; use std::sync::Arc; use crate::evm::types::{float_scale_to_u512, EVMU512}; @@ -25,38 +17,34 @@ use crate::state_input::StagedVMState; use bytes::Bytes; use libafl::prelude::{HasMetadata, HasRand}; -use libafl::schedulers::Scheduler; use libafl::state::{HasCorpus, State}; -use primitive_types::{H256, U512}; -use rand::random; - -use revm::db::BenchmarkDB; -use revm_interpreter::{BytecodeLocked, CallContext, CallScheme, Contract, Gas, InstructionResult, Interpreter, Memory, Stack}; use revm_interpreter::InstructionResult::ControlLeak; -use revm_primitives::{Bytecode, LatestSpec}; +use revm_interpreter::{ + BytecodeLocked, CallContext, CallScheme, Contract, Gas, InstructionResult, Interpreter, Memory, + Stack, +}; +use revm_primitives::Bytecode; use core::ops::Range; use crate::evm::bytecode_analyzer; use crate::evm::host::{ - FuzzHost, CMP_MAP, COVERAGE_NOT_CHANGED, JMP_MAP, READ_MAP, - RET_OFFSET, RET_SIZE, STATE_CHANGE, WRITE_MAP, + FuzzHost, CMP_MAP, COVERAGE_NOT_CHANGED, JMP_MAP, READ_MAP, STATE_CHANGE, WRITE_MAP, }; -use crate::evm::input::{ConciseEVMInput, EVMInput, EVMInputT, EVMInputTy}; -use crate::evm::middlewares::middleware::{Middleware, MiddlewareType}; +use crate::evm::input::{ConciseEVMInput, EVMInputT}; +use crate::evm::middlewares::middleware::Middleware; use crate::evm::onchain::flashloan::FlashloanData; use crate::evm::types::{EVMAddress, EVMU256}; -use crate::evm::uniswap::generate_uniswap_router_call; + +use crate::evm::vm::Constraint::NoLiquidation; use crate::generic_vm::vm_executor::{ExecutionResult, GenericVM, MAP_SIZE}; use crate::generic_vm::vm_state::VMStateT; -use crate::r#const::DEBUG_PRINT_PERCENT; +use crate::invoke_middlewares; + use crate::state::{HasCaller, HasCurrentInputIdx, HasItyState}; -use serde::{Deserialize, Serialize}; use serde::de::DeserializeOwned; -use serde_traitobject::Any; -use crate::evm::vm::Constraint::NoLiquidation; -use crate::{invoke_middlewares}; +use serde::{Deserialize, Serialize}; const MAX_POST_EXECUTION: usize = 10; @@ -80,7 +68,7 @@ macro_rules! get_token_ctx { pub enum Constraint { Caller(EVMAddress), Contract(EVMAddress), - NoLiquidation + NoLiquidation, } /// A post execution context @@ -119,7 +107,6 @@ pub struct SinglePostExecution { /// Value send to contract. pub value: EVMU256, - /// Post execution related information /// Output Length pub output_len: usize, @@ -127,7 +114,6 @@ pub struct SinglePostExecution { pub output_offset: usize, } - impl SinglePostExecution { pub fn hash(&self, hasher: &mut impl Hasher) { self.program_counter.hash(hasher); @@ -156,26 +142,20 @@ impl SinglePostExecution { } fn get_interpreter(&self, bytecode: Arc) -> Interpreter { - let contract = Contract::new_with_context_analyzed( - self.input.clone(), - bytecode, - &self.get_call_ctx(), - ); + let contract = + Contract::new_with_context_analyzed(self.input.clone(), bytecode, &self.get_call_ctx()); let mut stack = Stack::new(); for v in &self.stack.data { - stack.push(v.clone()); + let _ = stack.push(*v); } - Interpreter { - instruction_pointer: unsafe { - contract.bytecode.as_ptr().add(self.program_counter) - }, + instruction_pointer: unsafe { contract.bytecode.as_ptr().add(self.program_counter) }, instruction_result: self.instruction_result, gas: Gas::new(0), memory: self.memory.clone(), - stack: stack, + stack, return_data_buffer: Bytes::new(), return_range: self.return_range.clone(), is_static: self.is_static, @@ -247,7 +227,6 @@ pub struct EVMState { pub typed_bug: HashSet, } - pub trait EVMStateT { fn get_constraints(&self) -> Vec; } @@ -261,7 +240,6 @@ impl EVMStateT for EVMState { } } - impl Default for EVMState { /// Default VM state, containing empty state, no post execution context, /// and no flashloan usage @@ -298,12 +276,18 @@ impl VMStateT for EVMState { /// This can also used to check whether a state is intermediate state (i.e., not yet /// finished execution) fn has_post_execution(&self) -> bool { - self.post_execution.len() > 0 + !self.post_execution.is_empty() } /// Get length needed for return data length of the call that leads to control leak fn get_post_execution_needed_len(&self) -> usize { - self.post_execution.last().unwrap().pes.first().unwrap().output_len + self.post_execution + .last() + .unwrap() + .pes + .first() + .unwrap() + .output_len } /// Get the PC of last post execution context @@ -339,9 +323,7 @@ impl VMStateT for EVMState { fn is_subset_of(&self, other: &Self) -> bool { self.state.iter().all(|(k, v)| { other.state.get(k).map_or(false, |v2| { - v.iter().all(|(k, v)| { - v2.get(k).map_or(false, |v2| v == v2) - }) + v.iter().all(|(k, v)| v2.get(k).map_or(false, |v2| v == v2)) }) }) } @@ -400,13 +382,11 @@ where phandom: PhantomData<(I, S, VS, CI)>, } - pub fn is_reverted_or_control_leak(ret: &InstructionResult) -> bool { - match *ret { - InstructionResult::Return | InstructionResult::Stop - | InstructionResult::SelfDestruct => false, - _ => true, - } + !matches!( + *ret, + InstructionResult::Return | InstructionResult::Stop | InstructionResult::SelfDestruct + ) } /// Execution result that may have control leaked @@ -466,6 +446,7 @@ where /// `post_exec` is the post execution context to use, if any /// If `post_exec` is `None`, then the execution is from the beginning, otherwise it is from /// the post execution context. + #[allow(clippy::too_many_arguments)] pub fn execute_from_pc( &mut self, call_ctx: &CallContext, @@ -473,8 +454,8 @@ where data: Bytes, input: &I, post_exec: Option, - mut state: &mut S, - cleanup: bool + state: &mut S, + cleanup: bool, ) -> IntermediateExecutionResult { // Initial setups if cleanup { @@ -496,13 +477,13 @@ where let mut repeats = input.get_repeat(); // Get the bytecode - let mut bytecode = match self - .host - .code - .get(&call_ctx.code_address) { + let bytecode = match self.host.code.get(&call_ctx.code_address) { Some(i) => i.clone(), None => { - println!("no code @ {:?}, did you forget to deploy?", call_ctx.code_address); + println!( + "no code @ {:?}, did you forget to deploy?", + call_ctx.code_address + ); return IntermediateExecutionResult { output: Bytes::new(), new_state: EVMState::default(), @@ -519,20 +500,19 @@ where // If there is a post execution context, then we need to create the interpreter from // the post execution context repeats = 1; - unsafe { - // setup the pc, memory, and stack as the post execution context - let mut interp = post_exec_ctx.get_interpreter(bytecode); - // set return buffer as the input - // we remove the first 4 bytes because the first 4 bytes is the function hash (00000000 here) - interp.return_data_buffer = data.slice(4..); - let target_len = min(post_exec_ctx.output_len, interp.return_data_buffer.len()); - interp - .memory - .set(post_exec_ctx.output_offset, &interp.return_data_buffer[..target_len]); - interp - } - } else { + // setup the pc, memory, and stack as the post execution context + let mut interp = post_exec_ctx.get_interpreter(bytecode); + // set return buffer as the input + // we remove the first 4 bytes because the first 4 bytes is the function hash (00000000 here) + interp.return_data_buffer = data.slice(4..); + let target_len = min(post_exec_ctx.output_len, interp.return_data_buffer.len()); + interp.memory.set( + post_exec_ctx.output_offset, + &interp.return_data_buffer[..target_len], + ); + interp + } else { // if there is no post execution context, then we create the interpreter from the // beginning let call = Contract::new_with_context_analyzed(data, bytecode, call_ctx); @@ -541,7 +521,7 @@ where // Execute the contract for `repeats` times or until revert let mut r = InstructionResult::Stop; - for v in 0..repeats - 1 { + for _v in 0..repeats - 1 { // println!("repeat: {:?}", v); r = self.host.run_inspect(&mut interp, state); interp.stack.data.clear(); @@ -595,6 +575,7 @@ where } /// Conduct a fast call that does not write to the feedback + #[allow(unused)] fn fast_call( &mut self, address: EVMAddress, @@ -613,7 +594,7 @@ where self.host .code .get(&address) - .expect(&*format!("no code {:?}", address)) + .unwrap_or_else(|| panic!("no code {:?}", address)) .clone(), &CallContext { address, @@ -659,11 +640,11 @@ where .clone() }; - let mut r = None; + let r; let mut is_step = input.is_step(); let mut data = Bytes::from(input.to_bytes()); // use direct data (mostly used for debugging) if there is no data - if data.len() == 0 { + if data.is_empty() { data = Bytes::from(input.get_direct_data()); } @@ -672,11 +653,11 @@ where loop { // Execute the transaction let exec_res = if is_step { - let mut post_exec = vm_state.post_execution.pop().unwrap().clone(); + let post_exec = vm_state.post_execution.pop().unwrap().clone(); let mut local_res = None; for mut pe in post_exec.pes { // we need push the output of CALL instruction - pe.stack.push(EVMU256::from(1)); + let _ = pe.stack.push(EVMU256::from(1)); let res = self.execute_from_pc( &pe.get_call_ctx(), &vm_state, @@ -684,7 +665,7 @@ where input, Some(pe), state, - cleanup + cleanup, ); data = Bytes::from([vec![0; 4], res.output.to_vec()].concat()); local_res = Some(res); @@ -711,11 +692,15 @@ where input, None, state, - cleanup + cleanup, ) }; - let need_step = exec_res.new_state.post_execution.len() > 0 && exec_res.new_state.post_execution.last().unwrap().must_step; - if (exec_res.ret == InstructionResult::Return || exec_res.ret == InstructionResult::Stop) && need_step { + let need_step = !exec_res.new_state.post_execution.is_empty() + && exec_res.new_state.post_execution.last().unwrap().must_step; + if (exec_res.ret == InstructionResult::Return + || exec_res.ret == InstructionResult::Stop) + && need_step + { is_step = true; data = Bytes::from([vec![0; 4], exec_res.output.to_vec()].concat()); // we dont need to clean up bug info and state info @@ -727,7 +712,7 @@ where } let mut r = r.unwrap(); match r.ret { - ControlLeak | InstructionResult::ArbitraryExternalCallAddressBounded(_,_) => unsafe { + ControlLeak | InstructionResult::ArbitraryExternalCallAddressBounded(_, _) => { if r.new_state.post_execution.len() + 1 > MAX_POST_EXECUTION { return ExecutionResult { output: r.output.to_vec(), @@ -736,35 +721,39 @@ where additional_info: None, }; } - let leak_ctx = self.host.leak_ctx - .clone(); + let leak_ctx = self.host.leak_ctx.clone(); r.new_state.post_execution.push(PostExecutionCtx { pes: leak_ctx, must_step: match r.ret { ControlLeak => false, - InstructionResult::ArbitraryExternalCallAddressBounded(_,_) => true, + InstructionResult::ArbitraryExternalCallAddressBounded(_, _) => true, _ => unreachable!(), }, constraints: match r.ret { ControlLeak => vec![], InstructionResult::ArbitraryExternalCallAddressBounded(caller, target) => { - vec![Constraint::Caller(caller), Constraint::Contract(target), NoLiquidation] + vec![ + Constraint::Caller(caller), + Constraint::Contract(target), + NoLiquidation, + ] } _ => unreachable!(), }, - }); - }, + } _ => {} } r.new_state.bug_hit = vm_state.bug_hit || self.host.bug_hit; r.new_state.selfdestruct_hit = vm_state.selfdestruct_hit || self.host.selfdestruct_hit; r.new_state.typed_bug = HashSet::from_iter( - vm_state.typed_bug.iter().cloned().chain( - self.host.current_typed_bug.iter().cloned() - ) + vm_state + .typed_bug + .iter() + .cloned() + .chain(self.host.current_typed_bug.iter().cloned()), ); // println!("r.ret: {:?}", r.ret); @@ -772,14 +761,16 @@ where unsafe { ExecutionResult { output: r.output.to_vec(), - reverted: match r.ret { - InstructionResult::Return | InstructionResult::Stop | InstructionResult::ControlLeak - | InstructionResult::SelfDestruct - | InstructionResult::ArbitraryExternalCallAddressBounded(_,_) => false, - _ => true, - }, + reverted: !matches!( + r.ret, + InstructionResult::Return + | InstructionResult::Stop + | InstructionResult::ControlLeak + | InstructionResult::SelfDestruct + | InstructionResult::ArbitraryExternalCallAddressBounded(_, _) + ), new_state: StagedVMState::new_with_state( - VMStateT::as_any(&mut r.new_state) + VMStateT::as_any(&r.new_state) .downcast_ref_unchecked::() .clone(), ), @@ -806,7 +797,8 @@ where pub static mut IN_DEPLOY: bool = false; -impl GenericVM, I, S, CI> +impl + GenericVM, I, S, CI> for EVMExecutor where I: VMInputT + EVMInputT + 'static, @@ -822,7 +814,7 @@ where + Debug + 'static, VS: VMStateT + Default + 'static, - CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { /// Deploy a contract fn deploy( @@ -862,7 +854,13 @@ where let mut contract_code = Bytecode::new_raw(interp.return_value()); bytecode_analyzer::add_analysis_result_to_state(&contract_code, state); unsafe { - invoke_middlewares!(&mut contract_code, deployed_address, &mut self.host, state, on_insert); + invoke_middlewares!( + &mut contract_code, + deployed_address, + &mut self.host, + state, + on_insert + ); } self.host.set_code(deployed_address, contract_code, state); Some(deployed_address) @@ -954,9 +952,7 @@ where unreachable!("liquidate should be handled by middleware"); } EVMInputTy::ABI => self.execute_abi(input, state), - EVMInputTy::ArbitraryCallBoundedAddr => { - self.execute_abi(input, state) - }, + EVMInputTy::ArbitraryCallBoundedAddr => self.execute_abi(input, state), } } @@ -980,7 +976,8 @@ where self.host.randomness = vec![9]; } - let res = data.iter() + let res = data + .iter() .map(|(address, by)| { let ctx = CallContext { address: *address, @@ -989,8 +986,8 @@ where apparent_value: Default::default(), scheme: CallScheme::StaticCall, }; - let code = self.host.code.get(&address).expect("no code").clone(); - let call = Contract::new_with_context_analyzed(by.clone(), code.clone(), &ctx); + let code = self.host.code.get(address).expect("no code").clone(); + let call = Contract::new_with_context_analyzed(by.clone(), code, &ctx); let mut interp = Interpreter::new(call, 1e10 as u64, false); let ret = self.host.run_inspect(&mut interp, state); if ret == InstructionResult::Revert { @@ -1028,6 +1025,7 @@ where } } +#[allow(unused_imports)] mod tests { use crate::evm::host::{FuzzHost, JMP_MAP}; use crate::evm::input::{ConciseEVMInput, EVMInput, EVMInputTy}; @@ -1052,10 +1050,11 @@ mod tests { if !path.exists() { std::fs::create_dir(path).unwrap(); } - let mut evm_executor: EVMExecutor = EVMExecutor::new( - FuzzHost::new(Arc::new(StdScheduler::new()), "work_dir".to_string()), - generate_random_address(&mut state), - ); + let mut evm_executor: EVMExecutor = + EVMExecutor::new( + FuzzHost::new(Arc::new(StdScheduler::new()), "work_dir".to_string()), + generate_random_address(&mut state), + ); let mut observers = tuple_list!(); let mut vm_state = EVMState::new(); diff --git a/src/executor.rs b/src/executor.rs index 5d4c1aa62..eea426d92 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -13,7 +13,6 @@ use serde::Serialize; use std::fmt::Debug; use std::ops::Deref; use std::rc::Rc; -use crate::evm::input::EVMInput; use crate::generic_vm::vm_executor::GenericVM; use crate::generic_vm::vm_state::VMStateT; @@ -30,8 +29,9 @@ where VS: Default + VMStateT, Addr: Serialize + DeserializeOwned + Debug + Clone, Loc: Serialize + DeserializeOwned + Debug + Clone, - CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { + #[allow(clippy::type_complexity)] /// The VM executor pub vm: Rc>>, /// Observers (e.g., coverage) @@ -47,7 +47,7 @@ where VS: Default + VMStateT, Addr: Serialize + DeserializeOwned + Debug + Clone, Loc: Serialize + DeserializeOwned + Debug + Clone, - CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("FuzzExecutor") @@ -65,9 +65,10 @@ where VS: Default + VMStateT, Addr: Serialize + DeserializeOwned + Debug + Clone, Loc: Serialize + DeserializeOwned + Debug + Clone, - CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { /// Create a new [`FuzzExecutor`] + #[allow(clippy::type_complexity)] pub fn new( vm_executor: Rc>>, observers: OT, @@ -90,7 +91,7 @@ where Addr: Serialize + DeserializeOwned + Debug + Clone, Loc: Serialize + DeserializeOwned + Debug + Clone, Out: Default, - CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { /// Run the VM to execute the input fn run_target( @@ -117,7 +118,7 @@ where VS: Default + VMStateT, Addr: Serialize + DeserializeOwned + Debug + Clone, Loc: Serialize + DeserializeOwned + Debug + Clone, - CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { /// Get the observers fn observers(&self) -> &OT { diff --git a/src/fuzzer.rs b/src/fuzzer.rs index af43c0778..453d96c37 100644 --- a/src/fuzzer.rs +++ b/src/fuzzer.rs @@ -40,7 +40,7 @@ use serde::de::DeserializeOwned; use serde::Serialize; use std::hash::{Hash, Hasher}; -const STATS_TIMEOUT_DEFAULT: Duration = Duration::from_millis(100); +const STATS_TIMEOUT_DEFAULT: Duration = Duration::from_millis(10000); pub static mut RUN_FOREVER: bool = false; pub static mut ORACLE_OUTPUT: String = String::new(); @@ -56,6 +56,7 @@ pub static mut ORACLE_OUTPUT: String = String::new(); /// VS: The VM state type /// Addr: The address type (e.g., H160) /// Loc: The call target location type (e.g., H160) +#[allow(clippy::type_complexity)] #[derive(Debug)] pub struct ItyFuzzer<'a, VS, Loc, Addr, Out, CS, IS, F, IF, IFR, I, OF, S, OT, CI> where @@ -540,6 +541,7 @@ where } return Ok((res, None)); + /* // Not interesting self.feedback.discard_metadata(state, &input)?; @@ -556,8 +558,8 @@ where }, )?; } - Ok((res, None)) + */ } }; unsafe { diff --git a/src/fuzzers/evm_fuzzer.rs b/src/fuzzers/evm_fuzzer.rs index e00b842b8..5e5b4215c 100644 --- a/src/fuzzers/evm_fuzzer.rs +++ b/src/fuzzers/evm_fuzzer.rs @@ -1,3 +1,4 @@ +use crate::evm::gpt::get_gpt_generated_corpus; #[allow(unused_imports)] use crate::evm::presets::pair::PairPreset; use crate::{ @@ -321,7 +322,7 @@ pub fn evm_fuzzer( )); let mut fuzzer = ItyFuzzer::new( - scheduler, + scheduler.clone(), &infant_scheduler, wrapped_feedback, infant_feedback, @@ -330,6 +331,15 @@ pub fn evm_fuzzer( config.work_dir, ); + let initial_vm_state = artifacts.initial_state.clone(); + + // test gpt corpus + get_gpt_generated_corpus(state, initial_vm_state.clone(), &config.contract_loader) + .into_iter() + .for_each(|input| { + let _ = fuzzer.evaluate_input_events(state, &mut executor, &mut mgr, input, true); + }); + match config.replay_file { None => { fuzzer @@ -347,7 +357,6 @@ pub fn evm_fuzzer( .host .add_middlewares(printer.clone()); - let initial_vm_state = artifacts.initial_state.clone(); for file in glob(files.as_str()).expect("Failed to read glob pattern") { let mut f = File::open(file.expect("glob issue")).expect("Failed to open file"); let mut transactions = String::new(); diff --git a/src/generic_vm/vm_executor.rs b/src/generic_vm/vm_executor.rs index add96e152..629837f4a 100644 --- a/src/generic_vm/vm_executor.rs +++ b/src/generic_vm/vm_executor.rs @@ -2,10 +2,10 @@ use crate::generic_vm::vm_state::VMStateT; use crate::state_input::StagedVMState; +use crate::input::ConciseSerde; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::fmt::Debug; -use crate::input::ConciseSerde; pub const MAP_SIZE: usize = 4096; @@ -16,7 +16,7 @@ where Addr: Serialize + DeserializeOwned + Debug, Loc: Serialize + DeserializeOwned + Debug, Out: Default, - CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { pub output: Out, pub reverted: bool, @@ -31,7 +31,7 @@ where Addr: Serialize + DeserializeOwned + Debug, Loc: Serialize + DeserializeOwned + Debug, Out: Default, - CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { pub fn empty_result() -> Self { Self { @@ -59,7 +59,12 @@ pub trait GenericVM { Out: Default, CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde; - fn fast_static_call(&mut self, data: &Vec<(Addr, By)>, vm_state: &VS, state: &mut S) -> Vec + fn fast_static_call( + &mut self, + data: &Vec<(Addr, By)>, + vm_state: &VS, + state: &mut S, + ) -> Vec where VS: VMStateT, Addr: Serialize + DeserializeOwned + Debug, diff --git a/tests/evm/taints-1/test.sol b/tests/evm/taints-1/test.sol index b174be04f..6cdfb8eda 100644 --- a/tests/evm/taints-1/test.sol +++ b/tests/evm/taints-1/test.sol @@ -5,12 +5,11 @@ import "../../../solidity_utils/lib.sol"; contract main { // solution: a = 1 - bytes32 public answer = 0x60298f78cc0b47170ba79c10aa3851d7648bd96f2f8e46a19dbc777c36fb0c00; + bytes32 private answer = 0x60298f78cc0b47170ba79c10aa3851d7648bd96f2f8e46a19dbc777c36fb0c00; // Magic word is "Solidity" function guess(string memory _word) public { - if (keccak256(abi.encodePacked(_word)) == answer) { - typed_bug("0x3322"); - } + require(keccak256(abi.encodePacked(_word)) == answer, "Wrong guess"); + typed_bug("0x3322"); } } diff --git a/tests/smartian/transform_smartian.py b/tests/smartian/transform_smartian.py new file mode 100644 index 000000000..3a8751e6c --- /dev/null +++ b/tests/smartian/transform_smartian.py @@ -0,0 +1,83 @@ +import os +import subprocess +import sys + +sourcePath = "/Users/doudou/Dev/Smartian-Artifact/benchmarks/B1/" +targetPath = "/Users/doudou/Dev/ityfuzz/tests/smartian/test" +workPath = "/Users/doudou/Dev/ityfuzz/cli" + + +def clean(): + os.system(f"rm -rf {targetPath}") + os.system(f"mkdir -p {targetPath}") + + +def copy(filename): + source = f"{sourcePath}/abi/{filename}.abi" + target = f"{targetPath}/{filename}.abi" + os.system(f"cp {source} {target}") + source = f"{sourcePath}/bin/{filename}.bin" + target = f"{targetPath}/{filename}.bin" + os.system(f"cp {source} {target}") + + +def deal(filename): + print(f"dealing {filename}") + clean() + copy(filename) + os.chdir(workPath) + os.environ["RUSTFLAGS"] = "-A warnings" + proc = subprocess.Popen( + [ + "cargo", + "run", + "-q", + "--bin", + "cli", + "evm", + "-t", + "../tests/smartian/test/*", + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + try: + stdout, stderr = proc.communicate(timeout=35) + except subprocess.TimeoutExpired: + proc.kill() + stdout, stderr = proc.communicate() + + if stderr: + return f"{filename}#error" + out = stdout.decode() + if not out: + return f"{filename}#noOutput" + + if "Found violations" in out: + return f"{filename}#solved" + + output = out.split("\n") + output.reverse() + for line in output: + if "Covered" in line: + msg = line.split(": ")[1] + return f"{filename}#{msg}" + + return f"{filename}#notFound" + + +def main(): + for file in os.listdir(f"{sourcePath}/abi"): + filename = file.split(".")[0] + result = deal(filename) + print(result) + + +if __name__ == "__main__": + if len(sys.argv) > 1: + filename = sys.argv[1] + result = deal(filename) + print(result) + else: + main()