From 3bff8f200fd6c8dc5285001c0c985ca46165ded1 Mon Sep 17 00:00:00 2001 From: Chaofan Shou Date: Mon, 4 Sep 2023 10:39:22 -0700 Subject: [PATCH] Fix Arbitrary External Call bug (#187) * fix sourcemap issue * add two oracles * add returns to call printer * bug fix * on return removal * bugfix * support arb calls in vm * revm bump * bump revm * add arbitrary_external_call oracle --- cli/src/evm.rs | 4 + externals/revm | 2 +- src/evm/concolic/concolic_host.rs | 1 + src/evm/config.rs | 1 + src/evm/host.rs | 47 +++++++--- src/evm/middlewares/call_printer.rs | 15 +++- src/evm/middlewares/coverage.rs | 7 -- src/evm/middlewares/middleware.rs | 3 +- src/evm/middlewares/sha3_bypass.rs | 14 +-- src/evm/mutator.rs | 11 ++- src/evm/onchain/flashloan.rs | 8 -- src/evm/onchain/onchain.rs | 7 -- src/evm/oracles/arb_call.rs | 130 ++++++++++++++++++++++++++++ src/evm/oracles/arb_transfer.rs | 110 +++++++++++++++++++++++ src/evm/oracles/erc20.rs | 5 +- src/evm/oracles/mod.rs | 4 +- src/evm/vm.rs | 44 +++++----- src/feedback.rs | 2 +- src/fuzzers/evm_fuzzer.rs | 11 +++ 19 files changed, 352 insertions(+), 74 deletions(-) create mode 100644 src/evm/oracles/arb_call.rs create mode 100644 src/evm/oracles/arb_transfer.rs diff --git a/cli/src/evm.rs b/cli/src/evm.rs index 9fb6d2b82..c2d191a16 100644 --- a/cli/src/evm.rs +++ b/cli/src/evm.rs @@ -176,6 +176,9 @@ pub struct EvmArgs { #[arg(long, default_value = "true")] selfdestruct_oracle: bool, + #[arg(long, default_value = "true")] + arbitrary_external_call_oracle: bool, + #[arg(long, default_value = "true")] echidna_oracle: bool, @@ -561,6 +564,7 @@ pub fn evm_main(args: EvmArgs) { spec_id: args.spec_id, typed_bug: args.typed_bug_oracle, selfdestruct_bug: args.selfdestruct_oracle, + arbitrary_external_call: args.arbitrary_external_call_oracle, builder, }; diff --git a/externals/revm b/externals/revm index 65834920c..3fe63c5cf 160000 --- a/externals/revm +++ b/externals/revm @@ -1 +1 @@ -Subproject commit 65834920c043bd35e7172f833f87370ffd0aeb8e +Subproject commit 3fe63c5cf25882ee73d46b5262f98f3809aa75a0 diff --git a/src/evm/concolic/concolic_host.rs b/src/evm/concolic/concolic_host.rs index 0a987fd9e..7ab9b066e 100644 --- a/src/evm/concolic/concolic_host.rs +++ b/src/evm/concolic/concolic_host.rs @@ -1326,6 +1326,7 @@ where interp: &mut Interpreter, host: &mut FuzzHost, state: &mut S, + by: &Bytes ) { self.pop_ctx(); } diff --git a/src/evm/config.rs b/src/evm/config.rs index 0ca8b7a41..0cf9a7a95 100644 --- a/src/evm/config.rs +++ b/src/evm/config.rs @@ -74,5 +74,6 @@ pub struct Config { pub only_fuzz: HashSet, pub typed_bug: bool, pub selfdestruct_bug: bool, + pub arbitrary_external_call: bool, pub builder: Option, } diff --git a/src/evm/host.rs b/src/evm/host.rs index cd04a9feb..c4c7057c4 100644 --- a/src/evm/host.rs +++ b/src/evm/host.rs @@ -122,7 +122,7 @@ where pub _pc: usize, pub pc_to_addresses: HashMap<(EVMAddress, usize), HashSet>, pub pc_to_create: HashMap<(EVMAddress, usize), usize>, - pub pc_to_call_hash: HashMap<(EVMAddress, usize), HashSet>>, + pub pc_to_call_hash: HashMap<(EVMAddress, usize, usize), HashSet>>, pub middlewares_enabled: bool, pub middlewares: Rc>>>>>, @@ -149,6 +149,8 @@ where pub setcode_data: HashMap, // selftdestruct pub current_self_destructs: Vec<(EVMAddress, usize)>, + // arbitrary calls + pub current_arbitrary_calls: Vec<(EVMAddress, EVMAddress, usize)>, // relations file handle relations_file: std::fs::File, // Filter duplicate relations @@ -168,6 +170,8 @@ where /// For future continue executing when control leak happens pub leak_ctx: Vec, + + pub jumpi_trace: usize, } impl Debug for FuzzHost @@ -227,6 +231,7 @@ where logs: Default::default(), setcode_data: self.setcode_data.clone(), current_self_destructs: self.current_self_destructs.clone(), + current_arbitrary_calls: self.current_arbitrary_calls.clone(), relations_file: self.relations_file.try_clone().unwrap(), relations_hash: self.relations_hash.clone(), current_typed_bug: self.current_typed_bug.clone(), @@ -237,6 +242,7 @@ where leak_ctx: self.leak_ctx.clone(), mapping_sstore_pcs: self.mapping_sstore_pcs.clone(), mapping_sstore_pcs_to_slot: self.mapping_sstore_pcs_to_slot.clone(), + jumpi_trace: self.jumpi_trace, } } } @@ -244,7 +250,7 @@ where // hack: I don't want to change evm internal to add a new type of return // this return type is never used as we disabled gas pub static mut ACTIVE_MATCH_EXT_CALL: bool = false; -const CONTROL_LEAK_DETECTION: bool = true; +const CONTROL_LEAK_DETECTION: bool = false; const UNBOUND_CALL_THRESHOLD: usize = 3; // if a PC transfers control to >2 addresses, we consider call at this PC to be unbounded @@ -289,6 +295,7 @@ where logs: Default::default(), setcode_data: HashMap::new(), current_self_destructs: Default::default(), + current_arbitrary_calls: Default::default(), relations_file: std::fs::File::create(format!("{}/relations.log", workdir)).unwrap(), relations_hash: HashSet::new(), current_typed_bug: Default::default(), @@ -299,6 +306,7 @@ where leak_ctx: vec![], mapping_sstore_pcs: Default::default(), mapping_sstore_pcs_to_slot: Default::default(), + jumpi_trace: 37, }; // ret.env.block.timestamp = EVMU256::max_value(); ret @@ -604,29 +612,33 @@ where // check whether the whole CALLDATAVALUE can be arbitrary if !self .pc_to_call_hash - .contains_key(&(input.context.caller, self._pc)) + .contains_key(&(input.context.caller, self._pc, self.jumpi_trace)) { self.pc_to_call_hash - .insert((input.context.caller, self._pc), HashSet::new()); + .insert((input.context.caller, self._pc, self.jumpi_trace), HashSet::new()); } self.pc_to_call_hash - .get_mut(&(input.context.caller, self._pc)) + .get_mut(&(input.context.caller, self._pc, self.jumpi_trace)) .unwrap() .insert(hash.to_vec()); if self .pc_to_call_hash - .get(&(input.context.caller, self._pc)) + .get(&(input.context.caller, self._pc, self.jumpi_trace)) .unwrap() .len() > UNBOUND_CALL_THRESHOLD && input_seq.len() >= 4 { - // println!("ub leak {:?} -> {:?} with {:?}", input.context.caller, input.contract, hex::encode(input.input.clone())); + self.current_arbitrary_calls.push( + (input.context.caller, input.context.address, interp.program_counter()), + ); + // println!("ub leak {:?} -> {:?} with {:?} {}", input.context.caller, input.contract, hex::encode(input.input.clone()), self.jumpi_trace); push_interp!(); return ( InstructionResult::ArbitraryExternalCallAddressBounded( input.context.caller, input.context.address, + input.transfer.value ), Gas::new(0), Bytes::new(), @@ -690,7 +702,7 @@ where // if there is code, then call the code let res = self.call_forbid_control_leak(input, state); match res.0 { - ControlLeak | InstructionResult::ArbitraryExternalCallAddressBounded(_, _) => unsafe { + ControlLeak | InstructionResult::ArbitraryExternalCallAddressBounded(_, _, _) => unsafe { unsafe { self.leak_ctx.push(SinglePostExecution::from_interp( interp, @@ -873,7 +885,11 @@ where } else { as_u64(fast_peek!(0)) }; - let idx = (interp.program_counter() * (jump_dest as usize)) % MAP_SIZE; + let _pc = interp.program_counter(); + + let (shash, _) = self.jumpi_trace.overflowing_mul(54059); + self.jumpi_trace = (shash) ^ (_pc * 76963); + let idx = (_pc * (jump_dest as usize)) % MAP_SIZE; if JMP_MAP[idx] == 0 { self.coverage_changed = true; } @@ -1286,8 +1302,19 @@ where } }; + let ret_buffer = res.2.clone(); + unsafe { - invoke_middlewares!(self, interp, state, on_return); + if self.middlewares_enabled { + for middleware in &mut self.middlewares.clone().deref().borrow_mut().iter_mut() + { + middleware + .deref() + .deref() + .borrow_mut() + .on_return(interp, self, state, &ret_buffer); + } + } } res } diff --git a/src/evm/middlewares/call_printer.rs b/src/evm/middlewares/call_printer.rs index 49d5e11c5..64991442f 100644 --- a/src/evm/middlewares/call_printer.rs +++ b/src/evm/middlewares/call_printer.rs @@ -6,6 +6,7 @@ use std::io::Write; use std::ops::AddAssign; use std::path::Path; use std::time::{SystemTime, UNIX_EPOCH}; +use bytes::Bytes; use itertools::Itertools; use libafl::inputs::Input; use libafl::prelude::{HasCorpus, HasMetadata, State}; @@ -49,6 +50,7 @@ pub struct SingleCall { pub input: String, pub value: String, pub source: Option, + pub results: String } #[derive(Clone, Debug, Serialize, Default, Deserialize)] @@ -62,6 +64,7 @@ pub struct CallPrinter { pub sourcemaps: ProjectSourceMapTy, pub current_layer: usize, pub results: CallPrinterResult, + pub offsets: usize, entry: bool } @@ -77,6 +80,7 @@ impl CallPrinter { current_layer: 0, results: Default::default(), entry: true, + offsets: 0 } } @@ -99,7 +103,7 @@ impl CallPrinter { pub fn get_trace(&self) -> String { self.results.data.iter().map(|(layer, call)| { let padding = (0..*layer).map(|_| " ").join(""); - format!("{}[{} -> {}] ({})", padding, call.caller, call.contract, call.input) + format!("{}[{} -> {}] ({}) > ({})", padding, call.caller, call.contract, call.input, call.results) }).join("\n") } @@ -173,6 +177,7 @@ impl Middleware for CallPrinter } else { None }, + results: "".to_string(), })); } @@ -241,6 +246,7 @@ impl Middleware for CallPrinter }, ).unwrap_or(vec![]); + self.offsets = 0; self.results.data.push((self.current_layer, SingleCall { call_type, caller: self.translate_address(caller), @@ -259,6 +265,7 @@ impl Middleware for CallPrinter } else { None }, + results: "".to_string(), })); } @@ -267,7 +274,13 @@ impl Middleware for CallPrinter interp: &mut Interpreter, host: &mut FuzzHost, state: &mut S, + by: &Bytes ) { + self.offsets += 1; + let l = self.results.data.len(); + self.results.data[l - self.offsets] + .1.results = hex::encode(by); + self.current_layer -= 1; } diff --git a/src/evm/middlewares/coverage.rs b/src/evm/middlewares/coverage.rs index 1cb07d58f..a509a202f 100644 --- a/src/evm/middlewares/coverage.rs +++ b/src/evm/middlewares/coverage.rs @@ -346,13 +346,6 @@ impl Middleware for Coverage fn get_type(&self) -> MiddlewareType { MiddlewareType::InstructionCoverage } - - unsafe fn on_return( - &mut self, - interp: &mut Interpreter, - host: &mut FuzzHost, - state: &mut S, - ) {} } diff --git a/src/evm/middlewares/middleware.rs b/src/evm/middlewares/middleware.rs index 9ba986d13..2c9b40f28 100644 --- a/src/evm/middlewares/middleware.rs +++ b/src/evm/middlewares/middleware.rs @@ -99,7 +99,8 @@ where interp: &mut Interpreter, host: &mut FuzzHost, state: &mut S, - ); + ret: &Bytes + ) {} unsafe fn on_insert(&mut self, bytecode: &mut Bytecode, diff --git a/src/evm/middlewares/sha3_bypass.rs b/src/evm/middlewares/sha3_bypass.rs index cc87a0284..b7db2d35f 100644 --- a/src/evm/middlewares/sha3_bypass.rs +++ b/src/evm/middlewares/sha3_bypass.rs @@ -5,6 +5,7 @@ use crate::evm::types::{as_u64, EVMAddress, EVMU256}; use crate::generic_vm::vm_state::VMStateT; use crate::input::VMInputT; use crate::state::{HasCaller, HasCurrentInputIdx, HasItyState}; +use bytes::Bytes; use itertools::Itertools; use libafl::inputs::Input; use libafl::prelude::{HasCorpus, HasMetadata, State}; @@ -385,10 +386,8 @@ where } unsafe fn on_return( - &mut self, - interp: &mut Interpreter, - host: &mut FuzzHost, - state: &mut S, + &mut self, interp: &mut Interpreter, host: &mut FuzzHost, state: &mut S, + by: &Bytes ) { self.pop_ctx(); } @@ -465,13 +464,6 @@ where MiddlewareType::Sha3Bypass } - unsafe fn on_return( - &mut self, - interp: &mut Interpreter, - host: &mut FuzzHost, - state: &mut S, - ) { - } } mod tests { diff --git a/src/evm/mutator.rs b/src/evm/mutator.rs index 64728f39a..85438e2ff 100644 --- a/src/evm/mutator.rs +++ b/src/evm/mutator.rs @@ -17,7 +17,7 @@ use crate::evm::input::EVMInputTy::Borrow; use std::fmt::Debug; use revm_interpreter::Interpreter; use crate::evm::abi::ABIAddressToInstanceMap; -use crate::evm::types::{convert_u256_to_h160, EVMAddress}; +use crate::evm::types::{convert_u256_to_h160, EVMAddress, EVMU256}; use crate::evm::vm::{Constraint, EVMState, EVMStateT}; use crate::state::HasItyState; @@ -128,6 +128,9 @@ impl<'a, VS, Loc, Addr, SC, CI> FuzzMutator<'a, VS, Loc, Addr, SC, CI> Constraint::Caller(caller) => { input.set_caller_evm(caller); } + Constraint::Value(value) => { + input.set_txn_value(value); + } Constraint::Contract(target) => { let rand_int = state.rand_mut().next(); let always_none = state.rand_mut().next() % 30 == 0; @@ -155,6 +158,7 @@ impl<'a, VS, Loc, Addr, SC, CI> FuzzMutator<'a, VS, Loc, Addr, SC, CI> input.set_liquidation_percent(0); } } + _ => {} } } } @@ -201,7 +205,7 @@ impl<'a, VS, Loc, Addr, I, S, SC, CI> Mutator for FuzzMutator<'a, VS, Loc, // if the input is a step input (resume execution from a control leak) // we should not mutate the VM state, but only mutate the bytes if input.is_step() { - return match state.rand_mut().below(100) { + let res = match state.rand_mut().below(100) { #[cfg(feature = "flashloan_v2")] 0..=5 => { let prev_percent = input.get_liquidation_percent(); @@ -218,6 +222,9 @@ impl<'a, VS, Loc, Addr, I, S, SC, CI> Mutator for FuzzMutator<'a, VS, Loc, } _ => input.mutate(state), }; + + input.set_txn_value(EVMU256::ZERO); + return res; } // if the input is to borrow token, we should mutate the randomness diff --git a/src/evm/onchain/flashloan.rs b/src/evm/onchain/flashloan.rs index 084f24f6f..2da038187 100644 --- a/src/evm/onchain/flashloan.rs +++ b/src/evm/onchain/flashloan.rs @@ -43,7 +43,6 @@ use crate::evm::types::convert_u256_to_h160; use crate::evm::types::float_scale_to_u512; use crate::evm::vm::IS_FAST_CALL_STATIC; -const UNBOUND_TRANSFER_AMT: usize = 5; macro_rules! scale { () => { EVMU512::from(1_000_000) @@ -503,13 +502,6 @@ where } - unsafe fn on_return( - &mut self, - interp: &mut Interpreter, - host: &mut FuzzHost, - state: &mut S, - ) {} - fn get_type(&self) -> MiddlewareType { return MiddlewareType::Flashloan; } diff --git a/src/evm/onchain/onchain.rs b/src/evm/onchain/onchain.rs index 82ccc2c9b..aecf11a7a 100644 --- a/src/evm/onchain/onchain.rs +++ b/src/evm/onchain/onchain.rs @@ -469,13 +469,6 @@ where } - unsafe fn on_return( - &mut self, - interp: &mut Interpreter, - host: &mut FuzzHost, - state: &mut S, - ) {} - fn get_type(&self) -> MiddlewareType { MiddlewareType::OnChain } diff --git a/src/evm/oracles/arb_call.rs b/src/evm/oracles/arb_call.rs new file mode 100644 index 000000000..46c53d401 --- /dev/null +++ b/src/evm/oracles/arb_call.rs @@ -0,0 +1,130 @@ +use std::collections::hash_map::DefaultHasher; +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; +use crate::evm::input::{ConciseEVMInput, EVMInput}; +use crate::evm::oracles::{ARB_CALL_BUG_IDX}; +use crate::evm::types::{EVMAddress, EVMFuzzState, EVMOracleCtx, EVMU256, ProjectSourceMapTy}; +use crate::evm::vm::EVMState; +use crate::oracle::{Oracle, OracleCtx}; +use bytes::Bytes; +use itertools::Itertools; +use libafl::impl_serdeany; +use libafl::prelude::HasMetadata; +use revm_primitives::{Bytecode, HashSet}; +use serde::{Deserialize, Serialize}; +use crate::evm::blaz::builder::{ArtifactInfoMetadata, BuildJobResult}; +use crate::evm::oracle::EVMBugResult; +use crate::fuzzer::ORACLE_OUTPUT; +use crate::state::HasExecutionResult; + +pub struct ArbitraryCallOracle { + pub sourcemap: ProjectSourceMapTy, + pub address_to_name: HashMap, +} + +impl ArbitraryCallOracle { + pub fn new( + sourcemap: ProjectSourceMapTy, + address_to_name: HashMap, + ) -> Self { + Self { + sourcemap, + address_to_name, + } + } +} + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct ArbitraryCallMetadata { + pub known_calls: HashMap<(EVMAddress, usize), HashSet>, +} + +impl_serdeany!(ArbitraryCallMetadata); + +impl +Oracle< + EVMState, + EVMAddress, + Bytecode, + Bytes, + EVMAddress, + EVMU256, + Vec, + EVMInput, + EVMFuzzState, + ConciseEVMInput +> for ArbitraryCallOracle +{ + fn transition(&self, ctx: &mut EVMOracleCtx<'_>, stage: u64) -> u64 { + 0 + } + + fn oracle( + &self, + ctx: &mut OracleCtx< + EVMState, + EVMAddress, + Bytecode, + Bytes, + EVMAddress, + EVMU256, + Vec, + EVMInput, + EVMFuzzState, + ConciseEVMInput + >, + stage: u64, + ) -> Vec { + if ctx.post_state.arbitrary_calls.len() > 0 { + let mut res = vec![]; + for (caller, target, pc) in ctx.post_state.arbitrary_calls.iter() { + if !ctx.fuzz_state.has_metadata::() { + ctx.fuzz_state.metadata_mut().insert(ArbitraryCallMetadata { + known_calls: HashMap::new(), + }); + } + + let mut metadata = ctx.fuzz_state.metadata_mut().get_mut::().unwrap(); + let entry = metadata.known_calls.entry((*caller, *pc)).or_insert(HashSet::new()); + if entry.len() > 3 { + continue; + } + entry.insert(*target); + let mut hasher = DefaultHasher::new(); + caller.hash(&mut hasher); + target.hash(&mut hasher); + pc.hash(&mut hasher); + let real_bug_idx = (hasher.finish() as u64) << 8 + ARB_CALL_BUG_IDX; + + let mut name = self.address_to_name + .get(caller) + .unwrap_or(&format!("{:?}", caller)) + .clone(); + + let srcmap = BuildJobResult::get_sourcemap_executor( + ctx.fuzz_state.metadata_mut().get_mut::().expect("get metadata failed") + .get_mut(caller), + ctx.executor, + caller, + &self.sourcemap, + *pc + ); + EVMBugResult::new( + "Arbitrary Call".to_string(), + real_bug_idx, + format!( + "Arbitrary call from {:?} to {:?}", + name, target + ), + ConciseEVMInput::from_input(ctx.input, ctx.fuzz_state.get_execution_result()), + srcmap, + Some(name.clone()) + ).push_to_output(); + res.push(real_bug_idx); + } + res + } else { + vec![] + } + } +} diff --git a/src/evm/oracles/arb_transfer.rs b/src/evm/oracles/arb_transfer.rs new file mode 100644 index 000000000..f42307d00 --- /dev/null +++ b/src/evm/oracles/arb_transfer.rs @@ -0,0 +1,110 @@ +use std::collections::hash_map::DefaultHasher; +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; +use crate::evm::input::{ConciseEVMInput, EVMInput}; +use crate::evm::oracles::{ARB_CALL_BUG_IDX, ARB_TRANSFER_BUG_IDX}; +use crate::evm::types::{EVMAddress, EVMFuzzState, EVMOracleCtx, EVMU256, ProjectSourceMapTy}; +use crate::evm::vm::EVMState; +use crate::oracle::{Oracle, OracleCtx}; +use bytes::Bytes; +use itertools::Itertools; +use libafl::impl_serdeany; +use libafl::prelude::HasMetadata; +use revm_primitives::{Bytecode, HashSet}; +use serde::{Deserialize, Serialize}; +use crate::evm::blaz::builder::{ArtifactInfoMetadata, BuildJobResult}; +use crate::evm::oracle::EVMBugResult; +use crate::fuzzer::ORACLE_OUTPUT; +use crate::state::HasExecutionResult; + +pub struct ArbitraryERC20TransferOracle { + pub sourcemap: ProjectSourceMapTy, + pub address_to_name: HashMap, +} + +impl ArbitraryERC20TransferOracle { + pub fn new( + sourcemap: ProjectSourceMapTy, + address_to_name: HashMap, + ) -> Self { + Self { + sourcemap, + address_to_name, + } + } +} + +impl +Oracle< + EVMState, + EVMAddress, + Bytecode, + Bytes, + EVMAddress, + EVMU256, + Vec, + EVMInput, + EVMFuzzState, + ConciseEVMInput +> for ArbitraryERC20TransferOracle +{ + fn transition(&self, ctx: &mut EVMOracleCtx<'_>, stage: u64) -> u64 { + 0 + } + + fn oracle( + &self, + ctx: &mut OracleCtx< + EVMState, + EVMAddress, + Bytecode, + Bytes, + EVMAddress, + EVMU256, + Vec, + EVMInput, + EVMFuzzState, + ConciseEVMInput + >, + stage: u64, + ) -> Vec { + if ctx.post_state.arbitrary_transfers.len() > 0 { + let mut res = vec![]; + for (caller, pc) in ctx.post_state.arbitrary_transfers.iter() { + let mut hasher = DefaultHasher::new(); + caller.hash(&mut hasher); + pc.hash(&mut hasher); + let real_bug_idx = (hasher.finish() as u64) << 8 + ARB_TRANSFER_BUG_IDX; + + let mut name = self.address_to_name + .get(caller) + .unwrap_or(&format!("{:?}", caller)) + .clone(); + + let srcmap = BuildJobResult::get_sourcemap_executor( + ctx.fuzz_state.metadata_mut().get_mut::().expect("get metadata failed") + .get_mut(caller), + ctx.executor, + caller, + &self.sourcemap, + *pc + ); + EVMBugResult::new( + "Arbitrary Transfer".to_string(), + real_bug_idx, + format!( + "Arbitrary transfer from {:?}", + name + ), + ConciseEVMInput::from_input(ctx.input, ctx.fuzz_state.get_execution_result()), + srcmap, + Some(name.clone()) + ).push_to_output(); + res.push(real_bug_idx); + } + res + } else { + vec![] + } + } +} diff --git a/src/evm/oracles/erc20.rs b/src/evm/oracles/erc20.rs index d6c65a12a..3af1faa21 100644 --- a/src/evm/oracles/erc20.rs +++ b/src/evm/oracles/erc20.rs @@ -119,9 +119,9 @@ impl Oracle, let mut liquidations_owed = Vec::new(); let mut liquidations_earned = Vec::new(); - for ((_, token), (prev_balance, new_balance)) in self.erc20_producer.deref().borrow().balances.iter() { + for ((caller, token), (prev_balance, new_balance)) in self.erc20_producer.deref().borrow().balances.iter() { let token_info = self.known_tokens.get(token).expect("Token not found"); - // ctx.fuzz_state.get_execution_result_mut().new_state.state.flashloan_data.extra_info += format!("Balance: {} -> {} for {:?} @ {:?}\n", prev_balance, new_balance, caller, token).as_str(); + // println!("Balance: {} -> {} for {:?} @ {:?}\n", prev_balance, new_balance, caller, token); if prev_balance > new_balance { liquidations_owed.push((token_info, prev_balance - new_balance)); @@ -197,6 +197,7 @@ impl Oracle, if exec_res.new_state.state.flashloan_data.earned > exec_res.new_state.state.flashloan_data.owed + && exec_res.new_state.state.flashloan_data.earned - exec_res.new_state.state.flashloan_data.owed > EVMU512::from(10_000_000_000_000_000_000_000_0u128) // > 0.1ETH { let net = exec_res.new_state.state.flashloan_data.earned - exec_res.new_state.state.flashloan_data.owed; diff --git a/src/evm/oracles/mod.rs b/src/evm/oracles/mod.rs index 00ef3a6f7..212690f2d 100644 --- a/src/evm/oracles/mod.rs +++ b/src/evm/oracles/mod.rs @@ -5,6 +5,7 @@ pub mod selfdestruct; pub mod typed_bug; pub mod v2_pair; pub mod state_comp; +pub mod arb_call; pub static ERC20_BUG_IDX: u64 = 0; pub static FUNCTION_BUG_IDX: u64 = 1; @@ -12,4 +13,5 @@ pub static V2_PAIR_BUG_IDX: u64 = 2; pub static TYPED_BUG_BUG_IDX: u64 = 4; pub static SELFDESTRUCT_BUG_IDX: u64 = 5; pub static ECHIDNA_BUG_IDX: u64 = 6; -pub static STATE_COMP_BUG_IDX: u64 = 7; \ No newline at end of file +pub static STATE_COMP_BUG_IDX: u64 = 7; +pub static ARB_CALL_BUG_IDX: u64 = 8; \ No newline at end of file diff --git a/src/evm/vm.rs b/src/evm/vm.rs index 5e623bcf3..c8205b338 100644 --- a/src/evm/vm.rs +++ b/src/evm/vm.rs @@ -32,11 +32,8 @@ use primitive_types::{H256, U512}; use rand::random; use revm::db::BenchmarkDB; -use revm_interpreter::InstructionResult::ControlLeak; -use revm_interpreter::{ - BytecodeLocked, CallContext, CallScheme, Contract, Gas, InstructionResult, Interpreter, Memory, - Stack, -}; +use revm_interpreter::{BytecodeLocked, CallContext, CallScheme, Contract, Gas, InstructionResult, Interpreter, Memory, Stack}; +use revm_interpreter::InstructionResult::{ArbitraryExternalCallAddressBounded, ControlLeak}; use revm_primitives::{Bytecode, LatestSpec}; use core::ops::Range; @@ -52,13 +49,13 @@ use crate::evm::middlewares::middleware::{Middleware, MiddlewareType}; 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::invoke_middlewares; use crate::r#const::DEBUG_PRINT_PERCENT; use crate::state::{HasCaller, HasCurrentInputIdx, HasItyState}; use serde::de::DeserializeOwned; +use crate::evm::vm::Constraint::{NoLiquidation, Value}; use serde::{Deserialize, Serialize}; pub const MEM_LIMIT: u64 = 10 * 1024; @@ -95,6 +92,7 @@ macro_rules! is_call_success { pub enum Constraint { Caller(EVMAddress), Contract(EVMAddress), + Value(EVMU256), NoLiquidation, } @@ -254,6 +252,8 @@ pub struct EVMState { /// bug type call in solidity type #[serde(skip)] pub typed_bug: HashSet<(String, (EVMAddress, usize))>, + #[serde(skip)] + pub arbitrary_calls: HashSet<(EVMAddress, EVMAddress, usize)>, } pub trait EVMStateT { @@ -273,14 +273,7 @@ impl Default for EVMState { /// Default VM state, containing empty state, no post execution context, /// and no flashloan usage fn default() -> Self { - Self { - state: HashMap::new(), - post_execution: Vec::new(), - flashloan_data: FlashloanData::new(), - bug_hit: false, - self_destruct: Default::default(), - typed_bug: Default::default(), - } + Self::new() } } @@ -368,6 +361,7 @@ impl EVMState { bug_hit: false, self_destruct: Default::default(), typed_bug: Default::default(), + arbitrary_calls: Default::default(), } } @@ -492,7 +486,9 @@ where self.host.coverage_changed = false; self.host.bug_hit = false; self.host.current_typed_bug = vec![]; + self.host.jumpi_trace = 37; self.host.current_self_destructs = vec![]; + self.host.current_arbitrary_calls = vec![]; // Initially, there is no state change unsafe { STATE_CHANGE = false; @@ -742,7 +738,7 @@ where } let mut r = r.unwrap(); match r.ret { - ControlLeak | InstructionResult::ArbitraryExternalCallAddressBounded(_, _) => unsafe { + ControlLeak | InstructionResult::ArbitraryExternalCallAddressBounded(_, _, _) => unsafe { if r.new_state.post_execution.len() + 1 > MAX_POST_EXECUTION { return ExecutionResult { output: r.output.to_vec(), @@ -756,17 +752,14 @@ where 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, + InstructionResult::ArbitraryExternalCallAddressBounded(caller, target, value) => { + vec![Constraint::Caller(caller), Constraint::Contract(target), Value(value), NoLiquidation, ] } _ => unreachable!(), @@ -790,6 +783,11 @@ where .cloned() .chain(self.host.current_self_destructs.iter().cloned()), ); + r.new_state.arbitrary_calls = HashSet::from_iter( + vm_state.arbitrary_calls.iter().cloned().chain( + self.host.current_arbitrary_calls.iter().cloned() + ) + ); // println!("r.ret: {:?}", r.ret); @@ -801,7 +799,7 @@ where | InstructionResult::Stop | InstructionResult::ControlLeak | InstructionResult::SelfDestruct - | InstructionResult::ArbitraryExternalCallAddressBounded(_, _) => false, + | InstructionResult::ArbitraryExternalCallAddressBounded(_, _,_ ) => false, _ => true, }, new_state: StagedVMState::new_with_state( @@ -1004,7 +1002,9 @@ where .downcast_ref_unchecked::() .clone(); self.host.current_self_destructs = vec![]; + self.host.current_arbitrary_calls = vec![]; self.host.call_count = 0; + self.host.jumpi_trace = 37; self.host.current_typed_bug = vec![]; self.host.randomness = vec![9]; } diff --git a/src/feedback.rs b/src/feedback.rs index c7a611715..452023639 100644 --- a/src/feedback.rs +++ b/src/feedback.rs @@ -493,7 +493,7 @@ where } unsafe { - if self.vm.deref().borrow_mut().state_changed() { + if self.vm.deref().borrow_mut().state_changed() || state.get_execution_result().new_state.state.has_post_execution() { let hash = state.get_execution_result().new_state.state.get_hash(); if self.known_states.contains(&hash) { return Ok(false); diff --git a/src/fuzzers/evm_fuzzer.rs b/src/fuzzers/evm_fuzzer.rs index 8b04697b0..2d984e9ee 100644 --- a/src/fuzzers/evm_fuzzer.rs +++ b/src/fuzzers/evm_fuzzer.rs @@ -55,6 +55,7 @@ use crate::evm::middlewares::call_printer::CallPrinter; use crate::evm::middlewares::coverage::{Coverage, EVAL_COVERAGE}; use crate::evm::middlewares::middleware::Middleware; use crate::evm::middlewares::sha3_bypass::{Sha3Bypass, Sha3TaintAnalysis}; +use crate::evm::oracles::arb_call::ArbitraryCallOracle; use crate::evm::oracles::echidna::EchidnaOracle; use crate::evm::oracles::selfdestruct::SelfdestructOracle; use crate::evm::oracles::state_comp::StateCompOracle; @@ -340,6 +341,16 @@ pub fn evm_fuzzer( oracles.push(oracle); } + if config.arbitrary_external_call { + + oracles.push(Rc::new(RefCell::new( + ArbitraryCallOracle::new( + artifacts.address_to_sourcemap.clone(), + artifacts.address_to_name.clone(), + ) + ))); + } + if config.typed_bug { oracles.push(Rc::new(RefCell::new(TypedBugOracle::new( artifacts.address_to_sourcemap.clone(),