From 7fa7420b270735f4b0bbeed3553f10982fa6de17 Mon Sep 17 00:00:00 2001 From: jqhc Date: Tue, 18 Jul 2023 13:42:17 -0400 Subject: [PATCH 1/7] Added reentrancy feedback --- src/feedback.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/feedback.rs b/src/feedback.rs index cb2b4edb9..4617a050f 100644 --- a/src/feedback.rs +++ b/src/feedback.rs @@ -515,3 +515,80 @@ where Ok(()) } } + +#[cfg(feature = "reentrancy")] +pub struct ReentrancyFeedback { + /// write map of the current execution + written: bool, + phantom: PhantomData<(VS, Loc, Addr, Out)>, +} + +#[cfg(feature = "reentrancy")] +impl Debug for ReentrancyFeedback { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ReentrancyFeedback").finish() + } +} + +#[cfg(feature = "reentrancy")] +impl Named for ReentrancyFeedback { + fn name(&self) -> &str { + "ReentrancyFeedback" + } +} + +#[cfg(feature = "reentrancy")] +impl ReentrancyFeedback { + pub fn new(written: bool) -> Self { + Self { + written, + phantom: PhantomData, + } + } +} + +#[cfg(feature = "reentrancy")] +impl Feedback for ReentrancyFeedback +where + S: State + HasClientPerfMonitor + HasExecutionResult, + I: VMInputT, + VS: Default + VMStateT, + Addr: Serialize + DeserializeOwned + Debug + Clone, + Loc: Serialize + DeserializeOwned + Debug + Clone, + Out: Default, +{ + fn init_state(&mut self, _state: &mut S) -> Result<(), Error> { + Ok(()) + } + + // simply returns true if written is true, i.e. there has been a write after a control leak. + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EMI, + _input: &I, + _observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EMI: EventFirer, + OT: ObserversTuple, + { + let mut interesting = self.written; + self.written = false; + Ok(interesting) + } + + fn append_metadata( + &mut self, + _state: &mut S, + _testcase: &mut Testcase, + ) -> Result<(), Error> { + Ok(()) + } + + fn discard_metadata(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> { + Ok(()) + } +} + From bad088ca5d6819c7896961bbb776c41e2b5ef419 Mon Sep 17 00:00:00 2001 From: jqhc Date: Tue, 18 Jul 2023 15:09:46 -0400 Subject: [PATCH 2/7] reentrancy feedback updates in host.rs --- src/evm/host.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/evm/host.rs b/src/evm/host.rs index d6285a3d5..9468dc4cc 100644 --- a/src/evm/host.rs +++ b/src/evm/host.rs @@ -55,6 +55,9 @@ pub static mut STATE_CHANGE: bool = false; pub const RW_SKIPPER_PERCT_IDX: usize = 100; pub const RW_SKIPPER_AMT: usize = MAP_SIZE - RW_SKIPPER_PERCT_IDX; +// reentrancy +pub static mut WRITTEN: bool = false; + // How mant iterations the coverage is the same pub static mut COVERAGE_NOT_CHANGED: u32 = 0; pub static mut RET_SIZE: usize = 0; @@ -455,6 +458,9 @@ where JMP_MAP[idx] = if value_changed { 1 } else { 0 }; STATE_CHANGE |= value_changed; + + #[cfg(feature = "reentrancy")] + WRITTEN = true; } #[cfg(feature = "dataflow")] From aea4393c4263f58216453b985e74bc4c4fd18050 Mon Sep 17 00:00:00 2001 From: jqhc Date: Mon, 7 Aug 2023 09:58:35 -0400 Subject: [PATCH 3/7] Added executor functionality - Added global variable WRITTEN for reentrancy feedback - Reentrancy feedback now takes an executor and uses it to check for reentrancy after control leaks - Added reentrancy checking to CmpFeedback - Reentrancy feedback uses scheduler to vote for states - Moved feedback global variable updates into middleware.rs instead of evm_fuzzer.rs --- src/evm/middlewares/middleware.rs | 133 ++++++++++++++- src/evm/vm.rs | 4 + src/feedback.rs | 91 ++++++++-- src/fuzzers/evm_fuzzer.rs | 275 ++++++++++++++++-------------- src/generic_vm/vm_executor.rs | 1 + 5 files changed, 353 insertions(+), 151 deletions(-) diff --git a/src/evm/middlewares/middleware.rs b/src/evm/middlewares/middleware.rs index c646301e2..a6c21d159 100644 --- a/src/evm/middlewares/middleware.rs +++ b/src/evm/middlewares/middleware.rs @@ -1,5 +1,7 @@ -use crate::evm::host::FuzzHost; +use crate::evm::host::{FuzzHost, JMP_MAP, READ_MAP, WRITE_MAP, CMP_MAP, STATE_CHANGE, + WRITTEN, RET_SIZE, RET_OFFSET}; use crate::evm::input::{EVMInput, EVMInputT}; +use crate::generic_vm::vm_executor::MAP_SIZE; use crate::generic_vm::vm_state::VMStateT; use crate::input::VMInputT; use crate::state::{HasCaller, HasItyState}; @@ -10,7 +12,7 @@ use libafl::inputs::Input; use libafl::schedulers::Scheduler; use libafl::state::{HasCorpus, HasMetadata, State}; use primitive_types::{H160, U256, U512}; -use revm::{Bytecode, Interpreter}; +use revm::{Bytecode, Interpreter, Host}; use serde::{Deserialize, Serialize}; use std::clone::Clone; @@ -80,12 +82,137 @@ where I: VMInputT + EVMInputT, VS: VMStateT, { + // called on every instruction unsafe fn on_step( &mut self, interp: &mut Interpreter, host: &mut FuzzHost, state: &mut S, - ); + ) { + macro_rules! fast_peek { + ($idx:expr) => { + interp.stack.data()[interp.stack.len() - 1 - $idx] + }; + } + + match *interp.instruction_pointer { + 0x57 => { // JUMPI + let br = fast_peek!(1); + let jump_dest = if br.is_zero() { + 1 + } else { + fast_peek!(0).as_u64() + }; + let idx = (interp.program_counter() * (jump_dest as usize)) % MAP_SIZE; + if JMP_MAP[idx] == 0 { + *host.coverage_changed = true; + } + if JMP_MAP[idx] < 255 { + JMP_MAP[idx] += 1; + } + + #[cfg(feature = "cmp")] + { + let idx = (interp.program_counter()) % MAP_SIZE; + CMP_MAP[idx] = br; + } + } + + #[cfg(any(feature = "dataflow", feature = "cmp", feature = "reentrancy"))] + 0x55 => { // SSTORE + #[cfg(feature = "dataflow")] + let value = fast_peek!(1); + { + let mut key = fast_peek!(0); + let v = u256_to_u8!(value) + 1; + WRITE_MAP[process_rw_key!(key)] = v; + } + let res = host.sload(interp.contract.address, fast_peek!(0)); + let value_changed = res.expect("sload failed").0 != value; + + let idx = interp.program_counter() % MAP_SIZE; + JMP_MAP[idx] = if value_changed { 1 } else { 0 }; + + STATE_CHANGE |= value_changed; + + WRITTEN = true; + } + + #[cfg(feature = "dataflow")] + 0x54 => { // SLOAD + let mut key = fast_peek!(0); + READ_MAP[process_rw_key!(key)] = true; + } + + #[cfg(feature = "cmp")] + 0x10 | 0x12 => { // LT | SLT + let v1 = fast_peek!(0); + let v2 = fast_peek!(1); + let abs_diff = if v1 >= v2 { + if v1 - v2 != U256::zero() { + v1 - v2 + } else { + U256::from(1) + } + } else { + U256::zero() + }; + let idx = interp.program_counter() % MAP_SIZE; + if abs_diff < CMP_MAP[idx] { + CMP_MAP[idx] = abs_diff; + } + } + + #[cfg(feature = "cmp")] + 0x11 | 0x13 => { // GT | SGT + let v1 = fast_peek!(0); + let v2 = fast_peek!(1); + let abs_diff = if v1 <= v2 { + if v2 - v1 != U256::zero() { + v2 - v1 + } else { + U256::from(1) + } + } else { + U256::zero() + }; + let idx = interp.program_counter() % MAP_SIZE; + if abs_diff < CMP_MAP[idx] { + CMP_MAP[idx] = abs_diff; + } + } + + #[cfg(feature = "cmp")] + 0x14 => { // EQ + let v1 = fast_peek!(0); + let v2 = fast_peek!(1); + let abs_diff = if v1 < v2 { + (v2 - v1) % (U256::max_value() - 1) + 1 + } else { + (v1 - v2) % (U256::max_value() - 1) + 1 + }; + let idx = interp.program_counter() % MAP_SIZE; + if abs_diff < CMP_MAP[idx] { + CMP_MAP[idx] = abs_diff; + } + } + + 0xf1 | 0xf2 | 0xf4 | 0xfa => { // CALL | CALLCODE | DELEGATECALL | STATICCALL + let offset_of_ret_size: usize = match *interp.instruction_pointer { + 0xf1 | 0xf2 => 6, + 0xf4 | 0xfa => 5, + _ => unreachable!(), + }; + unsafe { + RET_OFFSET = fast_peek!(offset_of_ret_size - 1).as_usize(); + RET_SIZE = fast_peek!(offset_of_ret_size).as_usize(); + } + *host._pc = interp.program_counter(); + } + + _ => {} + } + } unsafe fn on_insert(&mut self, bytecode: &mut Bytecode, diff --git a/src/evm/vm.rs b/src/evm/vm.rs index 38d6872fd..0a168a078 100644 --- a/src/evm/vm.rs +++ b/src/evm/vm.rs @@ -794,6 +794,10 @@ where unsafe { &mut CMP_MAP } } + fn get_written(&self) -> &'static mut bool { + unsafe { &mut WRITTEN } + } + fn state_changed(&self) -> bool { unsafe { STATE_CHANGE } } diff --git a/src/feedback.rs b/src/feedback.rs index 4617a050f..f790c8357 100644 --- a/src/feedback.rs +++ b/src/feedback.rs @@ -438,6 +438,16 @@ where { let mut cmp_interesting = false; let cov_interesting = false; + let mut reentrancy_interesting = false; + + // checking for reentrancy + let written = self.vm.deref().borrow_mut().get_written(); + let new_state = _state.get_execution_result().new_state.state; + while new_state.has_post_execution() { + // now execute again, checking for writes to EVM memory. + self.vm.deref().borrow_mut().execute(_input, _state); + } + reentrancy_interesting = written; // check if the current distance is smaller than the min_map for i in 0..MAP_SIZE { @@ -465,6 +475,12 @@ where .vote(state.get_infant_state_state(), input.get_state_idx()); } + // if reentrancy has occurred, vote for the state (tentative) + if reentrancy_interesting { + self.scheduler + .vote(state.get_infant_state_state(), input.get_state_idx()); + } + unsafe { let cur_read_map = self.vm.deref().borrow_mut().get_read(); let cur_write_map = self.vm.deref().borrow_mut().get_write(); @@ -494,10 +510,12 @@ where for i in 0..MAP_SIZE { cur_write_map[i] = 0; } - if df_interesting || pc_interesting { + if df_interesting || pc_interesting { // || reentrancy_interesting { self.known_states.insert(hash); return Ok(true); } + + } } Ok(false) @@ -516,39 +534,67 @@ where } } -#[cfg(feature = "reentrancy")] -pub struct ReentrancyFeedback { - /// write map of the current execution +////////////////////////////////////////////////////////////////////////////// + +// #[cfg(feature = "reentrancy")] +pub struct ReentrancyFeedback +where + I: VMInputT, + VS: Default + VMStateT, + Addr: Serialize + DeserializeOwned + Debug + Clone, + Loc: Serialize + DeserializeOwned + Debug + Clone, +{ + /// write status of the current execution written: bool, + executor: Rc>>, phantom: PhantomData<(VS, Loc, Addr, Out)>, } -#[cfg(feature = "reentrancy")] -impl Debug for ReentrancyFeedback { +// #[cfg(feature = "reentrancy")] +impl Debug for ReentrancyFeedback +where + I: VMInputT, + VS: Default + VMStateT, + Addr: Serialize + DeserializeOwned + Debug + Clone, + Loc: Serialize + DeserializeOwned + Debug + Clone, +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("ReentrancyFeedback").finish() } } -#[cfg(feature = "reentrancy")] -impl Named for ReentrancyFeedback { +// #[cfg(feature = "reentrancy")] +impl Named for ReentrancyFeedback +where + I: VMInputT, + VS: Default + VMStateT, + Addr: Serialize + DeserializeOwned + Debug + Clone, + Loc: Serialize + DeserializeOwned + Debug + Clone, +{ fn name(&self) -> &str { "ReentrancyFeedback" } } -#[cfg(feature = "reentrancy")] -impl ReentrancyFeedback { - pub fn new(written: bool) -> Self { +// #[cfg(feature = "reentrancy")] +impl ReentrancyFeedback +where + I: VMInputT, + VS: Default + VMStateT, + Addr: Serialize + DeserializeOwned + Debug + Clone, + Loc: Serialize + DeserializeOwned + Debug + Clone, +{ + pub fn new(written: bool, executor: &EVMExecutor) -> Self { Self { written, - phantom: PhantomData, + executor, + phantom: Default::default(), } } } -#[cfg(feature = "reentrancy")] -impl Feedback for ReentrancyFeedback +// #[cfg(feature = "reentrancy")] +impl Feedback for ReentrancyFeedback where S: State + HasClientPerfMonitor + HasExecutionResult, I: VMInputT, @@ -574,9 +620,20 @@ where EMI: EventFirer, OT: ObserversTuple, { - let mut interesting = self.written; self.written = false; - Ok(interesting) + + // state after executing. should have been executed before is_interesting was called. + let new_state = _state.get_execution_result().new_state.state; + + // if there is a post execution context, there has been a new control leak. + while new_state.has_post_execution() { + // now execute again, checking for writes to EVM memory. + self.executor.deref().borrow().execute(_input, _state); + if self.written { + Ok(true) + } + } + Ok(false) } fn append_metadata( @@ -592,3 +649,5 @@ where } } +///////////////////////////////////////////////////////////////////////////////////////////// + diff --git a/src/fuzzers/evm_fuzzer.rs b/src/fuzzers/evm_fuzzer.rs index 3da9c3240..f67153c0e 100644 --- a/src/fuzzers/evm_fuzzer.rs +++ b/src/fuzzers/evm_fuzzer.rs @@ -1,17 +1,19 @@ use bytes::Bytes; use std::cell::RefCell; +use std::collections::HashMap; use std::fs::File; use std::io::Read; +use std::path::Path; use std::rc::Rc; use std::str::FromStr; use std::sync::Arc; use crate::{ evm::contract_utils::FIX_DEPLOYER, evm::host::FuzzHost, evm::vm::EVMExecutor, - executor::FuzzExecutor, fuzzer::ItyFuzzer, rand_utils::fixed_address, + executor::FuzzExecutor, fuzzer::ItyFuzzer, }; use libafl::feedbacks::Feedback; -use libafl::prelude::ShMemProvider; +use libafl::prelude::{HasMetadata, ShMemProvider}; use libafl::prelude::{QueueScheduler, SimpleEventManager}; use libafl::stages::{CalibrationStage, StdMutationalStage}; use libafl::{ @@ -19,8 +21,9 @@ use libafl::{ Evaluator, Fuzzer, }; use glob::glob; +use itertools::Itertools; -use crate::evm::host::{ACTIVE_MATCH_EXT_CALL, CMP_MAP, JMP_MAP}; +use crate::evm::host::{ACTIVE_MATCH_EXT_CALL, CMP_MAP, JMP_MAP, WRITTEN, PANIC_ON_BUG, WRITE_RELATIONSHIPS}; use crate::evm::host::{CALL_UNTIL}; use crate::evm::vm::EVMState; use crate::feedback::{CmpFeedback, OracleFeedback}; @@ -31,16 +34,26 @@ use crate::state_input::StagedVMState; use crate::evm::config::Config; use crate::evm::corpus_initializer::EVMCorpusInitializer; -use crate::evm::input::{EVMInput, EVMInputTy}; +use crate::evm::input::{ConciseEVMInput, EVMInput, EVMInputTy}; use crate::evm::mutator::{AccessPattern, FuzzMutator}; use crate::evm::onchain::flashloan::Flashloan; use crate::evm::onchain::onchain::OnChain; +use crate::evm::onchain::selfdestruct::{Selfdestruct}; use crate::evm::presets::pair::PairPreset; -use crate::evm::types::{EVMFuzzMutator, EVMFuzzState}; +use crate::evm::types::{EVMAddress, EVMFuzzMutator, EVMFuzzState, EVMU256, fixed_address}; use primitive_types::{H160, U256}; -use revm::{BlockEnv, Bytecode}; -use crate::evm::middlewares::instruction_coverage::InstructionCoverage; +use revm_primitives::{BlockEnv, Bytecode, Env}; +use revm_primitives::bitvec::view::BitViewSized; +use crate::evm::abi::ABIAddressToInstanceMap; +use crate::evm::feedbacks::Sha3WrappedFeedback; +use crate::evm::middlewares::coverage::Coverage; +use crate::evm::middlewares::branch_coverage::BranchCoverage; +use crate::evm::middlewares::sha3_bypass::{Sha3Bypass, Sha3TaintAnalysis}; +use crate::evm::oracles::echidna::EchidnaOracle; +use crate::evm::srcmap::parser::BASE_PATH; +use crate::fuzzer::{REPLAY, RUN_FOREVER}; +use crate::input::ConciseSerde; struct ABIConfig { abi: String, @@ -53,30 +66,39 @@ struct ContractInfo { } pub fn evm_fuzzer( - config: Config, EVMInput, EVMFuzzState>, state: &mut EVMFuzzState + config: Config, EVMInput, EVMFuzzState, ConciseEVMInput>, state: &mut EVMFuzzState ) { - let cov_middleware = Rc::new(RefCell::new(InstructionCoverage::new())); + // create work dir if not exists + let path = Path::new(config.work_dir.as_str()); + if !path.exists() { + std::fs::create_dir(path).unwrap(); + } + + let cov_middleware = Rc::new(RefCell::new(Coverage::new())); let monitor = SimpleMonitor::new(|s| println!("{}", s)); let mut mgr = SimpleEventManager::new(monitor); let infant_scheduler = SortedDroppingScheduler::new(); + let mut scheduler = QueueScheduler::new(); let jmps = unsafe { &mut JMP_MAP }; let cmps = unsafe { &mut CMP_MAP }; - let jmp_observer = StdMapObserver::new("jmp_labels", jmps); - let mut feedback = MaxMapFeedback::new(&jmp_observer); - let calibration = CalibrationStage::new(&feedback); - - let mut scheduler = QueueScheduler::new(); + let written = unsafe { &mut WRITTEN }; // for reentrancy + let jmp_observer = StdMapObserver::new("jmp", jmps); - let mutator: EVMFuzzMutator<'_> = FuzzMutator::new(&infant_scheduler); - - let std_stage = StdMutationalStage::new(mutator); - let mut stages = tuple_list!(calibration, std_stage); let deployer = fixed_address(FIX_DEPLOYER); - let mut fuzz_host = FuzzHost::new(Arc::new(scheduler.clone())); - + let mut fuzz_host = FuzzHost::new(Arc::new(scheduler.clone()), config.work_dir.clone()); fuzz_host.set_concolic_enabled(config.concolic); + fuzz_host.set_spec_id(config.spec_id); + + if config.selfdestruct_oracle { + //Selfdestruct middlewares + let mid = Rc::new(RefCell::new( + Selfdestruct::::new(), + )); + fuzz_host.add_middlewares(mid.clone()); + // Selfdestruct end + } let onchain_middleware = match config.onchain.clone() { Some(onchain) => { @@ -101,6 +123,26 @@ pub fn evm_fuzzer( } }; + if config.write_relationship { + unsafe { + WRITE_RELATIONSHIPS = true; + } + } + + unsafe { + BASE_PATH = config.base_path; + } + + if config.run_forever { + unsafe { + RUN_FOREVER = true; + } + } + + unsafe { + PANIC_ON_BUG = config.panic_on_bug; + } + if config.flashloan { // we should use real balance of tokens in the contract instead of providing flashloan // to contract as well for on chain env @@ -128,13 +170,21 @@ pub fn evm_fuzzer( )); } } + let sha3_taint = Rc::new(RefCell::new(Sha3TaintAnalysis::new())); + + if config.sha3_bypass { + fuzz_host.add_middlewares(Rc::new(RefCell::new(Sha3Bypass::new(sha3_taint.clone())))); + } - let mut evm_executor: EVMExecutor = + let mut evm_executor: EVMExecutor = EVMExecutor::new(fuzz_host, deployer); if config.replay_file.is_some() { // add coverage middleware for replay evm_executor.host.add_middlewares(cov_middleware.clone()); + unsafe { + REPLAY = true; + } } let mut corpus_initializer = EVMCorpusInitializer::new( @@ -147,7 +197,17 @@ pub fn evm_fuzzer( #[cfg(feature = "use_presets")] corpus_initializer.register_preset(&PairPreset {}); - corpus_initializer.initialize(config.contract_info); + let artifacts = corpus_initializer.initialize(&mut config.contract_loader.clone()); + + let mut instance_map = ABIAddressToInstanceMap::new(); + artifacts.address_to_abi_object.iter().for_each( + |(addr, abi)| { + instance_map.map.insert(addr.clone(), abi.clone()); + } + ); + state.add_metadata( + instance_map + ); evm_executor.host.initialize(state); @@ -155,27 +215,75 @@ pub fn evm_fuzzer( let evm_executor_ref = Rc::new(RefCell::new(evm_executor)); + let mut feedback = MaxMapFeedback::new(&jmp_observer); + feedback + .init_state(state) + .expect("Failed to init state"); + let calibration = CalibrationStage::new(&feedback); + let mutator: EVMFuzzMutator<'_> = FuzzMutator::new(&infant_scheduler); + + let std_stage = StdMutationalStage::new(mutator); + let mut stages = tuple_list!(calibration, std_stage); + let mut executor = FuzzExecutor::new(evm_executor_ref.clone(), tuple_list!(jmp_observer)); #[cfg(feature = "deployer_is_attacker")] state.add_caller(&deployer); - feedback - .init_state(state) - .expect("Failed to init state"); let infant_feedback = CmpFeedback::new(cmps, &infant_scheduler, evm_executor_ref.clone()); let mut oracles = config.oracle; + + if config.echidna_oracle { + let echidna_oracle = EchidnaOracle::new( + artifacts.address_to_abi.iter() + .map( + |(address, abis)| { + abis.iter().filter( + |abi| { + abi.function_name.starts_with("echidna_") + && abi.abi == "()" + } + ).map( + |abi| (address.clone(), abi.function.to_vec()) + ).collect_vec() + } + ).flatten().collect_vec(), + + artifacts.address_to_abi.iter() + .map( + |(address, abis)| { + abis.iter().filter( + |abi| { + abi.function_name.starts_with("echidna_") + && abi.abi == "()" + } + ).map( + |abi| (abi.function.to_vec(), abi.function_name.clone()) + ).collect_vec() + } + ).flatten().collect::, String>>(), + ); + oracles.push(Rc::new(RefCell::new(echidna_oracle))); + } + + let mut producers = config.producers; let objective = OracleFeedback::new(&mut oracles, &mut producers, evm_executor_ref.clone()); + let wrapped_feedback = Sha3WrappedFeedback::new( + feedback, + sha3_taint, + evm_executor_ref.clone(), + config.sha3_bypass + ); let mut fuzzer = ItyFuzzer::new( scheduler, &infant_scheduler, - feedback, + wrapped_feedback, infant_feedback, objective, - config.corpus_path, + config.work_dir, ); match config.replay_file { None => { @@ -184,125 +292,28 @@ pub fn evm_fuzzer( .expect("Fuzzing failed"); } Some(files) => { + 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(); f.read_to_string(&mut transactions) .expect("Failed to read file"); - let mut vm_state = StagedVMState::new_with_state(EVMState::new()); + let mut vm_state = initial_vm_state.clone(); let mut idx = 0; for txn in transactions.split("\n") { idx += 1; - let splitter = txn.split(" ").collect::>(); - if splitter.len() < 4 { + // let splitter = txn.split(" ").collect::>(); + if txn.len() < 4 { continue; } // [is_step] [caller] [target] [input] [value] - unsafe {CALL_UNTIL = u32::MAX;} - - let inp = match splitter[0] { - "abi" => { - let caller = H160::from_str(splitter[1]).unwrap(); - let contract = H160::from_str(splitter[2]).unwrap(); - let input = hex::decode(splitter[3]).unwrap(); - let value = U256::from_str_radix(splitter[4], 10).unwrap(); - let liquidation_percent = splitter[5].parse::().unwrap_or(0); - let warp_to = splitter[6].parse::().unwrap_or(0); - let repeat = splitter[7].parse::().unwrap_or(0); - let reentrancy_call_limits = splitter[8].parse::().unwrap_or(u32::MAX); - let is_step = splitter[9].parse::().unwrap_or(false); - - unsafe {CALL_UNTIL = reentrancy_call_limits;} - EVMInput { - caller, - contract, - data: None, - sstate: vm_state.clone(), - sstate_idx: 0, - txn_value: if value == U256::zero() { - None - } else { - Some(value) - }, - step: is_step, - env: revm::Env { - cfg: Default::default(), - block: BlockEnv { - number: U256::from(warp_to), - coinbase: Default::default(), - timestamp: U256::from(warp_to * 1000), - difficulty: Default::default(), - prevrandao: None, - basefee: Default::default(), - gas_limit: Default::default(), - }, - tx: Default::default(), - }, - access_pattern: Rc::new(RefCell::new(AccessPattern::new())), - #[cfg(feature = "flashloan_v2")] - liquidation_percent, - - #[cfg(feature = "flashloan_v2")] - input_type: EVMInputTy::ABI, - direct_data: if input.len() == 1 && input[0] == 0 { - Bytes::new() - } else { - Bytes::from(input.clone()) - }, - randomness: vec![], - repeat, - } - } - "borrow" => { - let caller = H160::from_str(splitter[1]).unwrap(); - let contract = H160::from_str(splitter[2]).unwrap(); - let randomness = hex::decode(splitter[3]).unwrap(); - let value = U256::from_str(splitter[4]).unwrap(); - let _liquidation_percent = splitter[5].parse::().unwrap_or(0); - let warp_to = splitter[6].parse::().unwrap_or(0); - EVMInput { - caller, - contract, - data: None, - sstate: vm_state.clone(), - sstate_idx: 0, - txn_value: if value == U256::zero() { - None - } else { - Some(value) - }, - step: false, - env: revm::Env { - cfg: Default::default(), - block: BlockEnv { - number: U256::from(warp_to), - coinbase: Default::default(), - timestamp: U256::from(warp_to * 1000), - difficulty: Default::default(), - prevrandao: None, - basefee: Default::default(), - gas_limit: Default::default(), - }, - tx: Default::default(), - }, - access_pattern: Rc::new(RefCell::new(AccessPattern::new())), - #[cfg(feature = "flashloan_v2")] - liquidation_percent: 0, - #[cfg(feature = "flashloan_v2")] - input_type: EVMInputTy::Borrow, - direct_data: Bytes::new(), - randomness, - repeat: 1, - } - } - _ => { - unreachable!() - } - }; + let (inp, call_until) = ConciseEVMInput::deserialize_concise(txn.as_bytes()) + .to_input(vm_state.clone()); + unsafe {CALL_UNTIL = call_until;} fuzzer .evaluate_input_events(state, &mut executor, &mut mgr, inp, false) @@ -328,7 +339,7 @@ pub fn evm_fuzzer( } // dump coverage: - cov_middleware.borrow_mut().record_instruction_coverage(); + cov_middleware.borrow_mut().record_instruction_coverage(&artifacts.address_to_sourcemap); } } } diff --git a/src/generic_vm/vm_executor.rs b/src/generic_vm/vm_executor.rs index 7ad883f32..1a02419a5 100644 --- a/src/generic_vm/vm_executor.rs +++ b/src/generic_vm/vm_executor.rs @@ -68,5 +68,6 @@ pub trait GenericVM { fn get_read(&self) -> &'static mut [bool; MAP_SIZE]; fn get_write(&self) -> &'static mut [u8; MAP_SIZE]; fn get_cmp(&self) -> &'static mut [SlotTy; MAP_SIZE]; + fn get_written(&self) -> &'static mut bool; fn state_changed(&self) -> bool; } From 57287daa507f21194b7e61e60d5b75790c74b9a4 Mon Sep 17 00:00:00 2001 From: jqhc Date: Mon, 7 Aug 2023 12:04:10 -0400 Subject: [PATCH 4/7] Fixed typing/compatibility issues Added CI type support for Reentrancy feedback --- src/feedback.rs | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/feedback.rs b/src/feedback.rs index 8d2c97833..8a6aa5232 100644 --- a/src/feedback.rs +++ b/src/feedback.rs @@ -504,11 +504,14 @@ where // if reentrancy has occurred, vote for the state (tentative) if reentrancy_interesting { + println!("Voted for {} because of REENTRANCY", input.get_state_idx()); self.scheduler - .vote(state.get_infant_state_state(), input.get_state_idx()); + .vote(state.get_infant_state_state(), input.get_state_idx(), 3); } unsafe { + let cur_read_map = self.vm.deref().borrow_mut().get_read(); + let cur_write_map = self.vm.deref().borrow_mut().get_write(); // hack to account for saving reentrancy without dataflow let pc_interesting = state .get_execution_result() @@ -559,26 +562,28 @@ where ////////////////////////////////////////////////////////////////////////////// // #[cfg(feature = "reentrancy")] -pub struct ReentrancyFeedback +pub struct ReentrancyFeedback where - I: VMInputT, + I: VMInputT, VS: Default + VMStateT, Addr: Serialize + DeserializeOwned + Debug + Clone, Loc: Serialize + DeserializeOwned + Debug + Clone, + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { /// write status of the current execution written: bool, - executor: Rc>>, + vm: Rc>>, phantom: PhantomData<(VS, Loc, Addr, Out)>, } // #[cfg(feature = "reentrancy")] -impl Debug for ReentrancyFeedback +impl Debug for ReentrancyFeedback where - I: VMInputT, + I: VMInputT, VS: Default + VMStateT, Addr: Serialize + DeserializeOwned + Debug + Clone, Loc: Serialize + DeserializeOwned + Debug + Clone, + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("ReentrancyFeedback").finish() @@ -586,12 +591,13 @@ where } // #[cfg(feature = "reentrancy")] -impl Named for ReentrancyFeedback +impl Named for ReentrancyFeedback where - I: VMInputT, + I: VMInputT, VS: Default + VMStateT, Addr: Serialize + DeserializeOwned + Debug + Clone, Loc: Serialize + DeserializeOwned + Debug + Clone, + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { fn name(&self) -> &str { "ReentrancyFeedback" @@ -599,31 +605,33 @@ where } // #[cfg(feature = "reentrancy")] -impl ReentrancyFeedback +impl ReentrancyFeedback where - I: VMInputT, + I: VMInputT, VS: Default + VMStateT, Addr: Serialize + DeserializeOwned + Debug + Clone, Loc: Serialize + DeserializeOwned + Debug + Clone, + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { - pub fn new(written: bool, executor: &EVMExecutor) -> Self { + pub fn new(written: bool, vm: &GenericVM) -> Self { Self { written, - executor, + vm, phantom: Default::default(), } } } // #[cfg(feature = "reentrancy")] -impl Feedback for ReentrancyFeedback +impl Feedback for ReentrancyFeedback where - S: State + HasClientPerfMonitor + HasExecutionResult, - I: VMInputT, + S: State + HasClientPerfMonitor + HasExecutionResult, + I: VMInputT, VS: Default + VMStateT, Addr: Serialize + DeserializeOwned + Debug + Clone, Loc: Serialize + DeserializeOwned + Debug + Clone, Out: Default, + CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { fn init_state(&mut self, _state: &mut S) -> Result<(), Error> { Ok(()) @@ -650,7 +658,7 @@ where // if there is a post execution context, there has been a new control leak. while new_state.has_post_execution() { // now execute again, checking for writes to EVM memory. - self.executor.deref().borrow().execute(_input, _state); + self.vm.deref().borrow().execute(_input, _state); if self.written { Ok(true) } From 0646156cadae1e5ef453eb2f70b366fb98fa5f66 Mon Sep 17 00:00:00 2001 From: jqhc Date: Mon, 7 Aug 2023 13:50:52 -0400 Subject: [PATCH 5/7] Fixed compiler error --- src/feedback.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/feedback.rs b/src/feedback.rs index 8a6aa5232..17adea6ac 100644 --- a/src/feedback.rs +++ b/src/feedback.rs @@ -613,7 +613,7 @@ where Loc: Serialize + DeserializeOwned + Debug + Clone, CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { - pub fn new(written: bool, vm: &GenericVM) -> Self { + pub fn new(written: bool, vm: &dyn GenericVM) -> Self { Self { written, vm, From d9e6b15992e5eb273a1454f249499b670d245429 Mon Sep 17 00:00:00 2001 From: jqhc Date: Mon, 7 Aug 2023 14:12:21 -0400 Subject: [PATCH 6/7] Added unsafe to middleware --- src/evm/middlewares/middleware.rs | 193 +++++++++++++++--------------- 1 file changed, 97 insertions(+), 96 deletions(-) diff --git a/src/evm/middlewares/middleware.rs b/src/evm/middlewares/middleware.rs index 54b4c4874..93727084e 100644 --- a/src/evm/middlewares/middleware.rs +++ b/src/evm/middlewares/middleware.rs @@ -101,123 +101,124 @@ where interp.stack.data()[interp.stack.len() - 1 - $idx] }; } + unsafe { + match *interp.instruction_pointer { + 0x57 => { // JUMPI + let br = fast_peek!(1); + let jump_dest = if br.is_zero() { + 1 + } else { + fast_peek!(0).as_u64() + }; + let idx = (interp.program_counter() * (jump_dest as usize)) % MAP_SIZE; + if JMP_MAP[idx] == 0 { + *host.coverage_changed = true; + } + if JMP_MAP[idx] < 255 { + JMP_MAP[idx] += 1; + } - match *interp.instruction_pointer { - 0x57 => { // JUMPI - let br = fast_peek!(1); - let jump_dest = if br.is_zero() { - 1 - } else { - fast_peek!(0).as_u64() - }; - let idx = (interp.program_counter() * (jump_dest as usize)) % MAP_SIZE; - if JMP_MAP[idx] == 0 { - *host.coverage_changed = true; - } - if JMP_MAP[idx] < 255 { - JMP_MAP[idx] += 1; + #[cfg(feature = "cmp")] + { + let idx = (interp.program_counter()) % MAP_SIZE; + CMP_MAP[idx] = br; + } } - #[cfg(feature = "cmp")] - { - let idx = (interp.program_counter()) % MAP_SIZE; - CMP_MAP[idx] = br; + #[cfg(any(feature = "dataflow", feature = "cmp", feature = "reentrancy"))] + 0x55 => { // SSTORE + #[cfg(feature = "dataflow")] + let value = fast_peek!(1); + { + let mut key = fast_peek!(0); + let v = u256_to_u8!(value) + 1; + WRITE_MAP[process_rw_key!(key)] = v; + } + let res = host.sload(interp.contract.address, fast_peek!(0)); + let value_changed = res.expect("sload failed").0 != value; + + let idx = interp.program_counter() % MAP_SIZE; + JMP_MAP[idx] = if value_changed { 1 } else { 0 }; + + STATE_CHANGE |= value_changed; + + WRITTEN = true; } - } - #[cfg(any(feature = "dataflow", feature = "cmp", feature = "reentrancy"))] - 0x55 => { // SSTORE #[cfg(feature = "dataflow")] - let value = fast_peek!(1); - { + 0x54 => { // SLOAD let mut key = fast_peek!(0); - let v = u256_to_u8!(value) + 1; - WRITE_MAP[process_rw_key!(key)] = v; + READ_MAP[process_rw_key!(key)] = true; } - let res = host.sload(interp.contract.address, fast_peek!(0)); - let value_changed = res.expect("sload failed").0 != value; - - let idx = interp.program_counter() % MAP_SIZE; - JMP_MAP[idx] = if value_changed { 1 } else { 0 }; - - STATE_CHANGE |= value_changed; - - WRITTEN = true; - } - - #[cfg(feature = "dataflow")] - 0x54 => { // SLOAD - let mut key = fast_peek!(0); - READ_MAP[process_rw_key!(key)] = true; - } - #[cfg(feature = "cmp")] - 0x10 | 0x12 => { // LT | SLT - let v1 = fast_peek!(0); - let v2 = fast_peek!(1); - let abs_diff = if v1 >= v2 { - if v1 - v2 != U256::zero() { - v1 - v2 + #[cfg(feature = "cmp")] + 0x10 | 0x12 => { // LT | SLT + let v1 = fast_peek!(0); + let v2 = fast_peek!(1); + let abs_diff = if v1 >= v2 { + if v1 - v2 != U256::zero() { + v1 - v2 + } else { + U256::from(1) + } } else { - U256::from(1) + U256::zero() + }; + let idx = interp.program_counter() % MAP_SIZE; + if abs_diff < CMP_MAP[idx] { + CMP_MAP[idx] = abs_diff; } - } else { - U256::zero() - }; - let idx = interp.program_counter() % MAP_SIZE; - if abs_diff < CMP_MAP[idx] { - CMP_MAP[idx] = abs_diff; } - } - #[cfg(feature = "cmp")] - 0x11 | 0x13 => { // GT | SGT - let v1 = fast_peek!(0); - let v2 = fast_peek!(1); - let abs_diff = if v1 <= v2 { - if v2 - v1 != U256::zero() { - v2 - v1 + #[cfg(feature = "cmp")] + 0x11 | 0x13 => { // GT | SGT + let v1 = fast_peek!(0); + let v2 = fast_peek!(1); + let abs_diff = if v1 <= v2 { + if v2 - v1 != U256::zero() { + v2 - v1 + } else { + U256::from(1) + } } else { - U256::from(1) + U256::zero() + }; + let idx = interp.program_counter() % MAP_SIZE; + if abs_diff < CMP_MAP[idx] { + CMP_MAP[idx] = abs_diff; } - } else { - U256::zero() - }; - let idx = interp.program_counter() % MAP_SIZE; - if abs_diff < CMP_MAP[idx] { - CMP_MAP[idx] = abs_diff; } - } - #[cfg(feature = "cmp")] - 0x14 => { // EQ - let v1 = fast_peek!(0); - let v2 = fast_peek!(1); - let abs_diff = if v1 < v2 { - (v2 - v1) % (U256::max_value() - 1) + 1 - } else { - (v1 - v2) % (U256::max_value() - 1) + 1 - }; - let idx = interp.program_counter() % MAP_SIZE; - if abs_diff < CMP_MAP[idx] { - CMP_MAP[idx] = abs_diff; + #[cfg(feature = "cmp")] + 0x14 => { // EQ + let v1 = fast_peek!(0); + let v2 = fast_peek!(1); + let abs_diff = if v1 < v2 { + (v2 - v1) % (U256::max_value() - 1) + 1 + } else { + (v1 - v2) % (U256::max_value() - 1) + 1 + }; + let idx = interp.program_counter() % MAP_SIZE; + if abs_diff < CMP_MAP[idx] { + CMP_MAP[idx] = abs_diff; + } } - } - 0xf1 | 0xf2 | 0xf4 | 0xfa => { // CALL | CALLCODE | DELEGATECALL | STATICCALL - let offset_of_ret_size: usize = match *interp.instruction_pointer { - 0xf1 | 0xf2 => 6, - 0xf4 | 0xfa => 5, - _ => unreachable!(), - }; - unsafe { - RET_OFFSET = fast_peek!(offset_of_ret_size - 1).as_usize(); - RET_SIZE = fast_peek!(offset_of_ret_size).as_usize(); + 0xf1 | 0xf2 | 0xf4 | 0xfa => { // CALL | CALLCODE | DELEGATECALL | STATICCALL + let offset_of_ret_size: usize = match *interp.instruction_pointer { + 0xf1 | 0xf2 => 6, + 0xf4 | 0xfa => 5, + _ => unreachable!(), + }; + unsafe { + RET_OFFSET = fast_peek!(offset_of_ret_size - 1).as_usize(); + RET_SIZE = fast_peek!(offset_of_ret_size).as_usize(); + } + *host._pc = interp.program_counter(); } - *host._pc = interp.program_counter(); - } - _ => {} + _ => {} + } } } From 5d670a7f58d06a47b27d27db2ada16f14035c146 Mon Sep 17 00:00:00 2001 From: jqhc Date: Mon, 7 Aug 2023 14:43:37 -0400 Subject: [PATCH 7/7] More bug fixes in middleware and reentrancy feedback --- src/evm/middlewares/middleware.rs | 45 +++++++++++++++++++------------ src/feedback.rs | 11 ++++---- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/evm/middlewares/middleware.rs b/src/evm/middlewares/middleware.rs index 93727084e..18f91c6a2 100644 --- a/src/evm/middlewares/middleware.rs +++ b/src/evm/middlewares/middleware.rs @@ -1,10 +1,12 @@ use crate::evm::host::{FuzzHost, JMP_MAP, READ_MAP, WRITE_MAP, CMP_MAP, STATE_CHANGE, WRITTEN, RET_SIZE, RET_OFFSET}; use crate::evm::input::{ConciseEVMInput, EVMInput, EVMInputT}; +use crate::evm::types::{as_u64, bytes_to_u64, EVMAddress, EVMU256, generate_random_address, is_zero}; use crate::generic_vm::vm_executor::MAP_SIZE; use crate::generic_vm::vm_state::VMStateT; use crate::input::VMInputT; use crate::state::{HasCaller, HasItyState}; +use crate::evm::host::FuzzHost; use bytes::Bytes; use libafl::corpus::{Corpus, Testcase}; @@ -19,7 +21,7 @@ use std::clone::Clone; use std::fmt::Debug; use std::time::Duration; -use revm_interpreter::Interpreter; +use revm_interpreter::{Host, Interpreter}; use revm_primitives::Bytecode; use crate::evm::types::{EVMAddress, EVMU256}; @@ -96,6 +98,11 @@ where host: &mut FuzzHost, state: &mut S, ) { + macro_rules! u256_to_u8 { + ($key:ident) => { + (as_u64($key >> 4) % 254) as u8 + }; + } macro_rules! fast_peek { ($idx:expr) => { interp.stack.data()[interp.stack.len() - 1 - $idx] @@ -105,10 +112,10 @@ where match *interp.instruction_pointer { 0x57 => { // JUMPI let br = fast_peek!(1); - let jump_dest = if br.is_zero() { + let jump_dest = if is_zero(br) { 1 } else { - fast_peek!(0).as_u64() + as_u64(fast_peek!(0)) }; let idx = (interp.program_counter() * (jump_dest as usize)) % MAP_SIZE; if JMP_MAP[idx] == 0 { @@ -134,7 +141,10 @@ where let v = u256_to_u8!(value) + 1; WRITE_MAP[process_rw_key!(key)] = v; } - let res = host.sload(interp.contract.address, fast_peek!(0)); + let res = as Host>::sload( + host, + interp.contract.address, + fast_peek!(0)); let value_changed = res.expect("sload failed").0 != value; let idx = interp.program_counter() % MAP_SIZE; @@ -156,13 +166,13 @@ where let v1 = fast_peek!(0); let v2 = fast_peek!(1); let abs_diff = if v1 >= v2 { - if v1 - v2 != U256::zero() { + if v1 - v2 != EVMU256::ZERO { v1 - v2 } else { - U256::from(1) + EVMU256::from(1) } } else { - U256::zero() + EVMU256::ZERO }; let idx = interp.program_counter() % MAP_SIZE; if abs_diff < CMP_MAP[idx] { @@ -175,13 +185,13 @@ where let v1 = fast_peek!(0); let v2 = fast_peek!(1); let abs_diff = if v1 <= v2 { - if v2 - v1 != U256::zero() { + if v2 - v1 != EVMU256::ZERO { v2 - v1 } else { U256::from(1) } } else { - U256::zero() + EVMU256::ZERO }; let idx = interp.program_counter() % MAP_SIZE; if abs_diff < CMP_MAP[idx] { @@ -193,11 +203,8 @@ where 0x14 => { // EQ let v1 = fast_peek!(0); let v2 = fast_peek!(1); - let abs_diff = if v1 < v2 { - (v2 - v1) % (U256::max_value() - 1) + 1 - } else { - (v1 - v2) % (U256::max_value() - 1) + 1 - }; + let abs_diff = + (if v1 < v2 {v2 - v1} else {v1 - v2}) % (EVMU256::MAX - EVMU256::from(1)) + 1; let idx = interp.program_counter() % MAP_SIZE; if abs_diff < CMP_MAP[idx] { CMP_MAP[idx] = abs_diff; @@ -211,10 +218,14 @@ where _ => unreachable!(), }; unsafe { - RET_OFFSET = fast_peek!(offset_of_ret_size - 1).as_usize(); - RET_SIZE = fast_peek!(offset_of_ret_size).as_usize(); + RET_OFFSET = as_u64(fast_peek!(offset_of_ret_size - 1)) as usize; + RET_SIZE = as_u64(fast_peek!(offset_of_ret_size)) as usize; } - *host._pc = interp.program_counter(); + host._pc = interp.program_counter(); + } + + 0xf0 | 0xf5 => { // CREATE, CREATE2 + host._pc = interp.program_counter(); } _ => {} diff --git a/src/feedback.rs b/src/feedback.rs index 17adea6ac..00a8d9661 100644 --- a/src/feedback.rs +++ b/src/feedback.rs @@ -471,13 +471,12 @@ where let mut reentrancy_interesting = false; // checking for reentrancy - let written = self.vm.deref().borrow_mut().get_written(); let new_state = _state.get_execution_result().new_state.state; while new_state.has_post_execution() { // now execute again, checking for writes to EVM memory. self.vm.deref().borrow_mut().execute(_input, _state); } - reentrancy_interesting = written; + reentrancy_interesting = *self.vm.deref().borrow_mut().get_written(); // check if the current distance is smaller than the min_map for i in 0..MAP_SIZE { @@ -613,7 +612,9 @@ where Loc: Serialize + DeserializeOwned + Debug + Clone, CI: Serialize + DeserializeOwned + Debug + Clone + ConciseSerde, { - pub fn new(written: bool, vm: &dyn GenericVM) -> Self { + pub fn new( + written: bool, + vm: Rc>>) -> Self { Self { written, vm, @@ -660,10 +661,10 @@ where // now execute again, checking for writes to EVM memory. self.vm.deref().borrow().execute(_input, _state); if self.written { - Ok(true) + return Ok(true); } } - Ok(false) + return Ok(false); } fn append_metadata(