diff --git a/.gitignore b/.gitignore index 71ec3d4cd..92fd3dae1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ bin corpus .idea .vscode +.DS_Store node_modules cache *.bin diff --git a/Cargo.toml b/Cargo.toml index 4a2f98a76..76283c27a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,15 +8,7 @@ edition = "2021" criterion = "0.4.0" [features] -default = [ - "cmp", - "dataflow", - "evm", - "print_txn_corpus", - "full_trace", - "force_cache", - "real_balance", -] +default = ["cmp", "dataflow", "evm", "print_txn_corpus", "full_trace"] evm = [] cmp = [] dataflow = [] @@ -52,16 +44,16 @@ bytes = { version = "1.2.1", features = ["serde"] } retry = "2.0.0" serde_cbor = "0.11.2" clap = { version = "4.4.4", features = ["derive"] } -sentry = "0.29.1" +sentry = "0.32.1" rlp = "0.5.2" ethers = "2.0.7" hex = "0.4" primitive-types = { version = "0.12.1", features = ["rlp", "serde"] } -libafl = "0.11.1" -libafl_bolts = "0.11.1" +libafl = "=0.11.1" +libafl_bolts = "=0.11.1" rand = "0.8.5" -nix = "0.24" +nix = "0.27.1" serde = "1.0.147" serde_traitobject = "0.2.8" serde_json = "1.0.73" diff --git a/integration_test.py b/integration_test.py index beba36794..9189fcc60 100644 --- a/integration_test.py +++ b/integration_test.py @@ -72,7 +72,6 @@ def test_one(path): if "taint" in path: cmd.append("--sha3-bypass") - print(" ".join(cmd)) p = subprocess.run( @@ -145,6 +144,8 @@ def test_onchain(test): etherscan_key, "--work-dir", f"w_{name}", + "--onchain-builder", + "https://solc-builder.dev.infra.fuzz.land/" # "--run-forever" ] @@ -152,6 +153,8 @@ def test_onchain(test): # try 3 times in case of rpc failure for i in range(3): + print(f"=== Testing onchain for contracts: {name}, try {i}") + print(" ".join(cmd)) p = subprocess.run( " ".join(cmd), stdout=subprocess.PIPE, @@ -186,20 +189,6 @@ def test_onchain(test): def build_fuzzer(): - # build fuzzer - subprocess.run( - [ - "cargo", - "build", - "--release", - "--features", - "cmp dataflow evm print_txn_corpus full_trace", - "--no-default-features", - ] - ) - - -def build_flash_loan_v2_fuzzer(): # build fuzzer subprocess.run( [ @@ -227,13 +216,12 @@ def build_flash_loan_v2_fuzzer(): else: actions = ["onchain", "offchain"] + build_fuzzer() if "offchain" in actions: - build_fuzzer() with multiprocessing.Pool(3) as p: p.map(test_one, glob.glob("./tests/evm/*", recursive=True)) if "onchain" in actions: - build_flash_loan_v2_fuzzer() tests = read_onchain_tests() with multiprocessing.Pool(10) as p: p.map(test_onchain, tests) diff --git a/offchain_config_1434_0_1701112172878_63428.json b/offchain_config_1434_0_1701112172878_63428.json new file mode 100644 index 000000000..81b8bc5f4 --- /dev/null +++ b/offchain_config_1434_0_1701112172878_63428.json @@ -0,0 +1 @@ +{"main.sol":{"HelloWorld":{"address":"0xe2f299db16d6263a0547edcda60a36abd4ab53ce","constructor_args":"0x"}}} \ No newline at end of file diff --git a/onchain_tests.txt b/onchain_tests.txt index 130c269bc..610d95a36 100644 --- a/onchain_tests.txt +++ b/onchain_tests.txt @@ -25,4 +25,4 @@ SellToken_exp bsc 28168034 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c,0xa645995e Shadowfi_exp bsc 20969095 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c,0x10bc28d2810dD462E16facfF18f78783e859351b,0xF9e3151e813cd6729D52d9A0C3ee69F22CcE650A RFB_exp bsc 23649423 0x26f1457f067bF26881F311833391b52cA871a4b5,0x03184AAA6Ad4F7BE876423D9967d1467220a544e,0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c,0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4 BIGFI_exp bsc 26685503 0x55d398326f99059fF775485246999027B3197955,0x28ec0B36F0819ecB5005cAB836F4ED5a2eCa4D13,0xd3d4B46Db01C006Fb165879f343fc13174a1cEeB,0xA269556EdC45581F355742e46D2d722c5F3f551a -Axioma_exp bsc 27620320 0x2C25aEe99ED08A61e7407A5674BC2d1A72B5D8E3,0xB6CF5b77B92a722bF34f6f5D6B1Fe4700908935E,0x6a3Fa7D2C71fd7D44BF3a2890aA257F34083c90f +Axioma_exp bsc 27620320 0x2C25aEe99ED08A61e7407A5674BC2d1A72B5D8E3,0xB6CF5b77B92a722bF34f6f5D6B1Fe4700908935E,0x6a3Fa7D2C71fd7D44BF3a2890aA257F34083c90f \ No newline at end of file diff --git a/src/evm/blaz/builder.rs b/src/evm/blaz/builder.rs index 8046d9155..dfc85715b 100644 --- a/src/evm/blaz/builder.rs +++ b/src/evm/blaz/builder.rs @@ -80,6 +80,7 @@ impl BuildJob { } pub fn onchain_job(&self, chain: String, addr: EVMAddress) -> Option { + debug!("will get onchain_job: {:?}", addr); if let Some(replacement) = self.replacements.get(&addr) { return replacement.clone(); } diff --git a/src/evm/config.rs b/src/evm/config.rs index 5dd77fdf7..8d38d6a35 100644 --- a/src/evm/config.rs +++ b/src/evm/config.rs @@ -86,6 +86,7 @@ pub struct Config { pub typed_bug: bool, pub arbitrary_external_call: bool, pub math_calculate_oracle: bool, + pub math_calculate_report_when_no_srcmap: bool, pub builder: Option, pub local_files_basedir_pattern: Option, pub load_corpus: String, diff --git a/src/evm/host.rs b/src/evm/host.rs index 142cd2d66..ec80eb666 100644 --- a/src/evm/host.rs +++ b/src/evm/host.rs @@ -201,8 +201,8 @@ where pub current_self_destructs: Vec<(EVMAddress, usize)>, // arbitrary calls pub current_arbitrary_calls: Vec<(EVMAddress, EVMAddress, usize)>, - // integer_overflow - pub current_integer_overflow: HashSet<(EVMAddress, usize, &'static str)>, + // integer_overflow / precision_loss + pub current_math_error: HashSet<(EVMAddress, usize, &'static str)>, // relations file handle relations_file: std::fs::File, // Filter duplicate relations @@ -289,7 +289,7 @@ where setcode_data: self.setcode_data.clone(), current_self_destructs: self.current_self_destructs.clone(), current_arbitrary_calls: self.current_arbitrary_calls.clone(), - current_integer_overflow: self.current_integer_overflow.clone(), + current_math_error: self.current_math_error.clone(), relations_file: self.relations_file.try_clone().unwrap(), relations_hash: self.relations_hash.clone(), current_typed_bug: self.current_typed_bug.clone(), @@ -349,7 +349,7 @@ where setcode_data: HashMap::new(), current_self_destructs: Default::default(), current_arbitrary_calls: Default::default(), - current_integer_overflow: Default::default(), + current_math_error: Default::default(), relations_file: std::fs::File::create(format!("{}/relations.log", workdir)).unwrap(), relations_hash: HashSet::new(), current_typed_bug: Default::default(), diff --git a/src/evm/input.rs b/src/evm/input.rs index 3ac1e7527..128381b3b 100644 --- a/src/evm/input.rs +++ b/src/evm/input.rs @@ -144,6 +144,7 @@ pub struct EVMInput { pub repeat: usize, /// Swap data + #[serde(skip_deserializing)] pub swap_data: HashMap, } @@ -193,6 +194,7 @@ pub struct ConciseEVMInput { pub return_data: Option>, /// Swap data + #[serde(skip_deserializing)] pub swap_data: HashMap, } diff --git a/src/evm/middlewares/coverage.rs b/src/evm/middlewares/coverage.rs index b7e1ab7b3..3d113f84d 100644 --- a/src/evm/middlewares/coverage.rs +++ b/src/evm/middlewares/coverage.rs @@ -8,6 +8,7 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; +use bytes::Bytes; use itertools::Itertools; use libafl::{schedulers::Scheduler, state::HasMetadata}; use revm_interpreter::{ @@ -33,11 +34,11 @@ pub static mut EVAL_COVERAGE: bool = false; /// Finds all PCs (offsets of bytecode) that are instructions / JUMPDEST /// Returns a tuple of (instruction PCs, JUMPI PCs, Skip PCs) -pub fn instructions_pc(bytecode: &Bytecode) -> (HashSet, HashSet, HashSet) { +pub fn instructions_pc(bytecode: &Bytes) -> (HashSet, HashSet, HashSet) { let mut complete_bytes = vec![]; let mut skip_instructions = HashSet::new(); let mut total_jumpi_set = HashSet::new(); - all_bytecode(&bytecode.bytes().to_vec()).iter().for_each(|(pc, op)| { + all_bytecode(&bytecode.to_vec()).iter().for_each(|(pc, op)| { if *op == JUMPDEST || *op == STOP || *op == INVALID { skip_instructions.insert(*pc); } @@ -318,7 +319,7 @@ where bytecode: &mut Bytecode, address: EVMAddress, ) { - let (pcs, jumpis, mut skip_pcs) = instructions_pc(&bytecode.clone()); + let (pcs, jumpis, mut skip_pcs) = instructions_pc(&bytecode.bytecode); // find all skipping PCs pcs.iter().for_each( @@ -374,7 +375,7 @@ mod tests { Bytes::from( hex::decode("60806040526004361061004e5760003560e01c80632d2c55651461008d578063819d4cc6146100de5780638980f11f146101005780638b21f170146101205780639342c8f41461015457600080fd5b36610088576040513481527f27f12abfe35860a9a927b465bb3d4a9c23c8428174b83f278fe45ed7b4da26629060200160405180910390a1005b600080fd5b34801561009957600080fd5b506100c17f0000000000000000000000003e40d73eb977dc6a537af587d48316fee66e9c8c81565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156100ea57600080fd5b506100fe6100f93660046106bb565b610182565b005b34801561010c57600080fd5b506100fe61011b3660046106bb565b61024e565b34801561012c57600080fd5b506100c17f000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe8481565b34801561016057600080fd5b5061017461016f3660046106f3565b610312565b6040519081526020016100d5565b6040518181526001600160a01b0383169033907f6a30e6784464f0d1f4158aa4cb65ae9239b0fa87c7f2c083ee6dde44ba97b5e69060200160405180910390a36040516323b872dd60e01b81523060048201526001600160a01b037f0000000000000000000000003e40d73eb977dc6a537af587d48316fee66e9c8c81166024830152604482018390528316906323b872dd90606401600060405180830381600087803b15801561023257600080fd5b505af1158015610246573d6000803e3d6000fd5b505050505050565b6000811161029a5760405162461bcd60e51b815260206004820152601460248201527316915493d7d49150d3d591549657d05353d5539560621b60448201526064015b60405180910390fd5b6040518181526001600160a01b0383169033907faca8fb252cde442184e5f10e0f2e6e4029e8cd7717cae63559079610702436aa9060200160405180910390a361030e6001600160a01b0383167f0000000000000000000000003e40d73eb977dc6a537af587d48316fee66e9c8c83610418565b5050565b6000336001600160a01b037f000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe8416146103855760405162461bcd60e51b81526020600482015260166024820152754f4e4c595f4c49444f5f43414e5f574954484452415760501b6044820152606401610291565b478281116103935780610395565b825b91508115610412577f000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe846001600160a01b0316634ad509b2836040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103f857600080fd5b505af115801561040c573d6000803e3d6000fd5b50505050505b50919050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b17905261046a90849061046f565b505050565b60006104c4826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166105419092919063ffffffff16565b80519091501561046a57808060200190518101906104e2919061070c565b61046a5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610291565b6060610550848460008561055a565b90505b9392505050565b6060824710156105bb5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610291565b843b6106095760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610291565b600080866001600160a01b03168587604051610625919061075e565b60006040518083038185875af1925050503d8060008114610662576040519150601f19603f3d011682016040523d82523d6000602084013e610667565b606091505b5091509150610677828286610682565b979650505050505050565b60608315610691575081610553565b8251156106a15782518084602001fd5b8160405162461bcd60e51b8152600401610291919061077a565b600080604083850312156106ce57600080fd5b82356001600160a01b03811681146106e557600080fd5b946020939093013593505050565b60006020828403121561070557600080fd5b5035919050565b60006020828403121561071e57600080fd5b8151801515811461055357600080fd5b60005b83811015610749578181015183820152602001610731565b83811115610758576000848401525b50505050565b6000825161077081846020870161072e565b9190910192915050565b602081526000825180602084015261079981604085016020870161072e565b601f01601f1916919091016040019291505056fea2646970667358221220c0f03149dd58fa21e9bfb72a010b74b1e518d704a2d63d8cc44c0ad3a2f573da64736f6c63430008090033").unwrap() ) - )); + ).bytecode); assert_eq!(pcs.len(), 1107); } diff --git a/src/evm/middlewares/math_calculate.rs b/src/evm/middlewares/math_calculate.rs new file mode 100644 index 000000000..abd19a36f --- /dev/null +++ b/src/evm/middlewares/math_calculate.rs @@ -0,0 +1,133 @@ +use std::{collections::HashSet, fmt::Debug, str::FromStr}; + +use libafl::schedulers::Scheduler; +use revm_interpreter::Interpreter; +use revm_primitives::{keccak256, B256}; +use serde::Serialize; +use tracing::debug; + +use crate::evm::{ + host::FuzzHost, + middlewares::middleware::{Middleware, MiddlewareType}, + onchain::endpoints::{Chain, OnChainConfig}, + srcmap::{SourceCodeResult, SOURCE_MAP_PROVIDER}, + types::{EVMAddress, EVMFuzzState}, + uniswap::{get_uniswap_info, UniswapProvider}, +}; + +#[derive(Serialize, Debug, Clone, Default)] +pub struct MathCalculateMiddleware { + report_when_no_srcmap: bool, + whitelist: HashSet, + pub fp: HashSet<(EVMAddress, usize)>, + pair_hash: B256, +} + +impl MathCalculateMiddleware { + pub fn new(onchain: Option, report_when_no_srcmap: bool) -> Self { + if let Some(OnChainConfig { chain_name, .. }) = onchain { + let chain = &Chain::from_str(&chain_name).unwrap(); + let info = get_uniswap_info(&UniswapProvider::UniswapV2, chain); + let whitelist = HashSet::from([info.router]); + let pair_hash = keccak256(&info.pair_bytecode); + return Self { + report_when_no_srcmap, + whitelist, + fp: HashSet::new(), + pair_hash, + }; + } + Self::default() + } +} + +impl Middleware for MathCalculateMiddleware +where + SC: Scheduler + Clone, +{ + unsafe fn on_step(&mut self, interp: &mut Interpreter, host: &mut FuzzHost, _state: &mut EVMFuzzState) { + let addr = interp.contract.code_address; + let pc = interp.program_counter(); + macro_rules! check { + ($overflow_fn: ident, $op: expr) => { + if self.whitelist.contains(&interp.contract.code_address) { + return; + } + let (l, r) = (interp.stack.peek(0).unwrap(), interp.stack.peek(1).unwrap()); + let overflow = if $op == "/" {l < r || r == alloy_primitives::Uint::ZERO } else { l.$overflow_fn(r).1 }; + if !overflow || + // already in fp + self.fp.contains(&(addr, pc)) || + // already reported + host.current_math_error.contains(&(addr, pc, $op)) + { + return; + } + let bytecode = host.code.get(&addr).unwrap(); + if bytecode.hash() == self.pair_hash { + // add whitelist for uniswap pair + debug!("add overflow whitelist for uniswap pair: {:?}", addr); + self.whitelist.insert(addr); + return; + } + if host.current_math_error.contains(&(addr, pc, $op)) { + // already reported + return; + } + match SOURCE_MAP_PROVIDER.lock().unwrap().get_source_code(&addr, pc) { + SourceCodeResult::NoSourceMap => { + // no srcmap + if self.report_when_no_srcmap { + debug!("contract {:?} maybe math error on pc[{pc:x}]: {} {} {}", addr, l, $op, r); + host.current_math_error.insert((addr, pc, $op)); + } else { + self.fp.insert((addr, pc)); + } + } + SourceCodeResult::NoSourceCode | SourceCodeResult::SourceCodeNoPcMatch(_) => { + // fp because of solc generated code + self.fp.insert((addr, pc)); + } + SourceCodeResult::SourceCode(source_code) => { + if let Some(pos) = source_code.find($op) && pos != 0 { + // real bug + debug!("contract {:?} math error on pc[{pc:x}]: {} {} {} {source_code:?}", addr, l, $op, r); + host.current_math_error.insert((addr, pc, $op)); + } else { + // fp + self.fp.insert((addr, pc)); + } + } + } + }; + } + match *interp.instruction_pointer { + 0x01 => { + // +ADD + check!(overflowing_add, "+"); + } + 0x02 => { + // *MUL + check!(overflowing_mul, "*"); + } + 0x03 => { + // -SUB + check!(overflowing_sub, "-"); + } + 0x04 | 0x05 => { + // DIV/ SDIV + // overflowing_add for placeholder, not used + check!(overflowing_add, "/"); + } + 0x0a => { + // ** EXP + check!(overflowing_pow, "**"); + } + _ => {} + } + } + + fn get_type(&self) -> MiddlewareType { + MiddlewareType::IntegerOverflow + } +} diff --git a/src/evm/middlewares/mod.rs b/src/evm/middlewares/mod.rs index 49b1f16c9..ab3f80793 100644 --- a/src/evm/middlewares/mod.rs +++ b/src/evm/middlewares/mod.rs @@ -1,6 +1,7 @@ pub mod call_printer; pub mod cheatcode; pub mod coverage; +pub mod math_calculate; pub mod middleware; pub mod reentrancy; pub mod sha3_bypass; diff --git a/src/evm/mod.rs b/src/evm/mod.rs index 5e314d5c2..e55c3a132 100644 --- a/src/evm/mod.rs +++ b/src/evm/mod.rs @@ -178,6 +178,9 @@ pub struct EvmArgs { #[arg(long, short, default_value = "high_confidence")] detectors: String, // <- internally this is known as oracles + #[arg(long, short, default_value = "false")] + math_calculate_report_when_no_srcmap: bool, + // /// Matching style for state comparison oracle (Select from "Exact", // /// "DesiredContain", "StateContain") // #[arg(long, default_value = "Exact")] @@ -195,7 +198,7 @@ pub struct EvmArgs { write_relationship: bool, /// Do not quit when a bug is found, continue find new bugs - #[arg(long, default_value = "false")] + #[arg(long, default_value = "false", short = 'f')] run_forever: bool, /// random seed @@ -281,6 +284,7 @@ enum EVMTargetType { } impl EVMTargetType { + #[allow(dead_code)] fn as_str(&self) -> &'static str { match self { EVMTargetType::Glob => "glob", @@ -318,6 +322,7 @@ enum OracleType { } impl OracleType { + #[allow(dead_code)] fn as_str(&self) -> &'static str { match self { OracleType::ERC20 => "erc20", @@ -393,10 +398,6 @@ impl OracleType { pub fn evm_main(args: EvmArgs) { let target = args.target.clone(); let work_dir = args.work_dir.clone(); - let work_path = Path::new(work_dir.as_str()); - if !work_path.exists() { - std::fs::create_dir(work_path).unwrap(); - } let mut target_type: EVMTargetType = match args.target_type { Some(v) => EVMTargetType::from_str(v.as_str()), @@ -717,6 +718,7 @@ pub fn evm_main(args: EvmArgs) { typed_bug: oracle_types.contains(&OracleType::TypedBug), arbitrary_external_call: oracle_types.contains(&OracleType::ArbitraryCall), math_calculate_oracle: oracle_types.contains(&OracleType::MathCalculate), + math_calculate_report_when_no_srcmap: args.math_calculate_report_when_no_srcmap, builder, local_files_basedir_pattern: match target_type { EVMTargetType::Glob => Some(args.target), @@ -747,7 +749,13 @@ pub fn evm_main(args: EvmArgs) { let json_str = serde_json::to_string(&abis_map).expect("Failed to serialize ABI map to JSON"); - let abis_json = format!("{}/abis.json", args.work_dir.clone().as_str()); + let work_dir = args.work_dir.clone(); + + let path = Path::new(work_dir.as_str()); + if !path.exists() { + std::fs::create_dir_all(path).unwrap(); + } + let abis_json = format!("{}/abis.json", work_dir.as_str()); let mut file = OpenOptions::new() .create(true) diff --git a/src/evm/onchain/mod.rs b/src/evm/onchain/mod.rs index 57851c333..d6ce41763 100644 --- a/src/evm/onchain/mod.rs +++ b/src/evm/onchain/mod.rs @@ -48,6 +48,7 @@ use crate::{ pub static mut BLACKLIST_ADDR: Option> = None; pub static mut WHITELIST_ADDR: Option> = None; +#[cfg(feature = "force_cache")] const UNBOUND_THRESHOLD: usize = 30; pub struct OnChain { @@ -319,11 +320,19 @@ impl OnChain { bytecode_analyzer::add_analysis_result_to_state(&contract_code, state); host.set_codedata(address_h160, contract_code.clone()); } - if unsafe { IS_FAST_CALL } || self.blacklist.contains(&address_h160) { + debug!("load code for {:?}", address_h160); + if unsafe { IS_FAST_CALL } || self.blacklist.contains(&address_h160) || !should_setup_abi { + debug!( + "return due to {} or {} or {}", + unsafe { IS_FAST_CALL }, + self.blacklist.contains(&address_h160), + !should_setup_abi + ); return; } // setup abi + debug!("setup abi for {:?}", address_h160); self.loaded_abi.insert(address_h160); let mut parsed_abi = vec![]; @@ -331,6 +340,7 @@ impl OnChain { parsed_abi = abis.clone(); } else { let mut abi = None; + debug!("try use abi from builder"); if let Some(builder) = &self.builder { debug!("onchain job {:?}", address_h160); let build_job = builder.onchain_job(self.endpoint.chain_name.clone(), address_h160); diff --git a/src/evm/oracles/erc20.rs b/src/evm/oracles/erc20.rs index 4703bf19d..f53285447 100644 --- a/src/evm/oracles/erc20.rs +++ b/src/evm/oracles/erc20.rs @@ -108,13 +108,16 @@ impl let (_out, mut state) = ctx.call_post_batch_dyn(&liquidation_txs); + let is_reverted = _out.iter().any(|(_, is_success)| *is_success == false); + // Record the swap info for generating foundry in the future. - state.swap_data = ctx.fuzz_state.get_execution_result().new_state.state.swap_data.clone(); - for (target, mut abi, _) in swap_infos { - state.swap_data.push(&target, &mut abi); + if !is_reverted { + state.swap_data = ctx.fuzz_state.get_execution_result().new_state.state.swap_data.clone(); + for (target, mut abi, _) in swap_infos { + state.swap_data.push(&target, &mut abi); + } + ctx.fuzz_state.get_execution_result_mut().new_state.state = state; } - - ctx.fuzz_state.get_execution_result_mut().new_state.state = state; } let exec_res = ctx.fuzz_state.get_execution_result_mut(); diff --git a/src/evm/oracles/math_calculate.rs b/src/evm/oracles/math_calculate.rs new file mode 100644 index 000000000..ee0e6960f --- /dev/null +++ b/src/evm/oracles/math_calculate.rs @@ -0,0 +1,94 @@ +use std::{ + collections::{hash_map::DefaultHasher, HashMap}, + hash::{Hash, Hasher}, +}; + +use bytes::Bytes; +use itertools::Itertools; +use revm_primitives::Bytecode; + +use crate::{ + evm::{ + input::{ConciseEVMInput, EVMInput}, + oracle::EVMBugResult, + oracles::MATH_CALCULATE_BUG_IDX, + types::{EVMAddress, EVMFuzzState, EVMOracleCtx, EVMU256}, + vm::EVMState, + }, + oracle::{Oracle, OracleCtx}, + state::HasExecutionResult, +}; + +pub struct MathCalculateOracle { + pub address_to_name: HashMap, +} + +impl MathCalculateOracle { + pub fn new(address_to_name: HashMap) -> Self { + Self { address_to_name } + } +} + +impl + Oracle, EVMInput, EVMFuzzState, ConciseEVMInput> + for MathCalculateOracle +{ + 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 { + let mut bug_indexes = Vec::new(); + for (addr, pc, op) in ctx.post_state.math_error.clone().into_iter() { + let mut hasher = DefaultHasher::new(); + addr.hash(&mut hasher); + pc.hash(&mut hasher); + let real_bug_idx = hasher.finish() << (8 + MATH_CALCULATE_BUG_IDX); + let name = self + .address_to_name + .get(&addr) + .unwrap_or(&format!("{:?}", addr)) + .clone(); + if op == "/" { + EVMBugResult::new( + "Loss of Accuracy".to_string(), + real_bug_idx, + format!("PrecisionLoss: {addr:?} , PC: {pc:x}, OP: {op:?}"), + ConciseEVMInput::from_input(ctx.input, ctx.fuzz_state.get_execution_result()), + None, + Some(name.clone()), + ) + .push_to_output(); + bug_indexes.push(real_bug_idx); + continue; + } + + EVMBugResult::new( + "IntegerOverflow".to_string(), + real_bug_idx, + format!("IntegerOverflow: {addr:?} , PC: {pc:x}, OP: {op:?} "), + ConciseEVMInput::from_input(ctx.input, ctx.fuzz_state.get_execution_result()), + None, + Some(name.clone()), + ) + .push_to_output(); + bug_indexes.push(real_bug_idx); + } + bug_indexes.into_iter().unique().filter(|x| *x != 0).collect_vec() + } +} diff --git a/src/evm/oracles/mod.rs b/src/evm/oracles/mod.rs index 036f5c522..674da0257 100644 --- a/src/evm/oracles/mod.rs +++ b/src/evm/oracles/mod.rs @@ -5,6 +5,7 @@ pub mod echidna; pub mod erc20; pub mod function; pub mod invariant; +pub mod math_calculate; pub mod reentrancy; pub mod selfdestruct; pub mod state_comp; @@ -21,7 +22,7 @@ pub static STATE_COMP_BUG_IDX: u64 = 7; pub static ARB_CALL_BUG_IDX: u64 = 8; pub static REENTRANCY_BUG_IDX: u64 = 9; pub static INVARIANT_BUG_IDX: u64 = 10; -pub static INTEGER_OVERFLOW_BUG_IDX: u64 = 11; +pub static MATH_CALCULATE_BUG_IDX: u64 = 11; /// Divide a U512 by another U512 and return a string with the decimal point at /// the correct position For example, 1000 / 3 = 333.333, then a = 1000e6, b = diff --git a/src/evm/uniswap/mod.rs b/src/evm/uniswap/mod.rs index 385c9cdb5..8e88394b7 100644 --- a/src/evm/uniswap/mod.rs +++ b/src/evm/uniswap/mod.rs @@ -10,6 +10,7 @@ use std::{ use alloy_primitives::hex; use serde::{Deserialize, Serialize}; +use tracing::warn; use super::types::checksum; use crate::{ @@ -333,7 +334,10 @@ pub fn get_uniswap_info(provider: &UniswapProvider, chain: &Chain) -> UniswapInf init_code_hash: hex::decode("96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f").unwrap(), pair_bytecode: hex::decode(ETH_UNISWAPV2_PAIR_BYTECODE).unwrap(), }, - _ => panic!("Uniswap provider {:?} @ chain {:?} not supported", provider, chain), + _ => { + warn!("Uniswap provider {:?} @ chain {:?} not supported", provider, chain); + UniswapInfo::default() + } } } pub const BSC_PANCAKEV2_PAIR_BYTECODE: &str = include_str!("bsc_pancakeV2_pair.bin"); diff --git a/src/evm/vm.rs b/src/evm/vm.rs index fa3ff884c..9991ce441 100644 --- a/src/evm/vm.rs +++ b/src/evm/vm.rs @@ -261,7 +261,7 @@ pub struct EVMState { pub arbitrary_calls: HashSet<(EVMAddress, EVMAddress, usize)>, // integer overflow in sol #[serde(skip)] - pub integer_overflow: HashSet<(EVMAddress, usize, &'static str)>, + pub math_error: HashSet<(EVMAddress, usize, &'static str)>, #[serde(skip)] pub reentrancy_metadata: ReentrancyData, #[serde(skip)] @@ -451,6 +451,7 @@ macro_rules! init_host { ($host:expr) => { $host.current_self_destructs = vec![]; $host.current_arbitrary_calls = vec![]; + $host.current_math_error = HashSet::new(); $host.call_count = 0; $host.jumpi_trace = 37; $host.current_typed_bug = vec![]; @@ -563,6 +564,7 @@ where self.host.jumpi_trace = 37; self.host.current_self_destructs = vec![]; self.host.current_arbitrary_calls = vec![]; + self.host.current_math_error = HashSet::new(); // Initially, there is no state change unsafe { STATE_CHANGE = false; @@ -831,12 +833,12 @@ where .chain(self.host.current_arbitrary_calls.iter().cloned()), ); - r.new_state.integer_overflow = HashSet::from_iter( + r.new_state.math_error = HashSet::from_iter( vm_state - .integer_overflow + .math_error .iter() .cloned() - .chain(self.host.current_integer_overflow.iter().cloned()), + .chain(self.host.current_math_error.iter().cloned()), ); unsafe { @@ -1080,6 +1082,7 @@ where self.host.evmstate = vm_state.as_any().downcast_ref_unchecked::().clone(); self.host.current_self_destructs = vec![]; self.host.current_arbitrary_calls = vec![]; + self.host.current_math_error = HashSet::new(); self.host.call_count = 0; self.host.jumpi_trace = 37; self.host.current_typed_bug = vec![]; diff --git a/src/fuzzer.rs b/src/fuzzer.rs index cd7d68adf..81b90ba16 100644 --- a/src/fuzzer.rs +++ b/src/fuzzer.rs @@ -598,11 +598,15 @@ where if !path.exists() { std::fs::create_dir_all(path).unwrap(); } - let mut file = File::create(format!("{}/{}", vulns_dir, bug_idxs.clone())).unwrap(); - file.write_all(data.as_bytes()).unwrap(); - let mut replayable_file = - File::create(format!("{}/{}_replayable", vulns_dir, bug_idxs)).unwrap(); - replayable_file.write_all(txn_json.as_bytes()).unwrap(); + + // println!("bug_idxs: {}", bug_idxs); + for bug_idx in bug_idxs.split(",") { + let mut file = File::create(format!("{}/{}", vulns_dir, bug_idx.clone())).unwrap(); + file.write_all(data.as_bytes()).unwrap(); + let mut replayable_file = + File::create(format!("{}/{}_replayable", vulns_dir, bug_idx)).unwrap(); + replayable_file.write_all(txn_json.as_bytes()).unwrap(); + } } // dump_file!(state, vulns_dir, false); } diff --git a/src/fuzzers/evm_fuzzer.rs b/src/fuzzers/evm_fuzzer.rs index c34e49b92..039cbfcff 100644 --- a/src/fuzzers/evm_fuzzer.rs +++ b/src/fuzzers/evm_fuzzer.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::HashMap, fs::File, io::Read, ops::Deref, path::Path, process::exit, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, fs::File, io::Read, ops::Deref, process::exit, rc::Rc}; use bytes::Bytes; use glob::glob; @@ -42,6 +42,7 @@ use crate::{ call_printer::CallPrinter, cheatcode::Cheatcode, coverage::{Coverage, EVAL_COVERAGE}, + math_calculate::MathCalculateMiddleware, middleware::Middleware, reentrancy::ReentrancyTracer, sha3_bypass::{Sha3Bypass, Sha3TaintAnalysis}, @@ -53,6 +54,7 @@ use crate::{ arb_call::ArbitraryCallOracle, echidna::EchidnaOracle, invariant::InvariantOracle, + math_calculate::MathCalculateOracle, reentrancy::ReentrancyOracle, selfdestruct::SelfdestructOracle, typed_bug::TypedBugOracle, @@ -88,9 +90,6 @@ pub fn evm_fuzzer( ) { info!("\n\n ================ EVM Fuzzer Start ===================\n\n"); - // create work dir if not exists - let path = Path::new(config.work_dir.as_str()); - let monitor = SimpleMonitor::new(|s| info!("{}", s)); let mut mgr = SimpleEventManager::new(monitor); let infant_scheduler = SortedDroppingScheduler::new(); @@ -179,6 +178,15 @@ pub fn evm_fuzzer( fuzz_host.add_middlewares(Rc::new(RefCell::new(ReentrancyTracer::new()))); } + if config.math_calculate_oracle { + debug!("math_calculate oracle enabled"); + let integer_overflow_middleware = Rc::new(RefCell::new(MathCalculateMiddleware::new( + config.onchain, + config.math_calculate_report_when_no_srcmap, + ))); + fuzz_host.add_middlewares(integer_overflow_middleware); + } + let mut evm_executor: EVMQueueExecutor = EVMExecutor::new(fuzz_host, deployer); if config.replay_file.is_some() { @@ -417,6 +425,12 @@ pub fn evm_fuzzer( )))); } + if config.math_calculate_oracle { + oracles.push(Rc::new(RefCell::new(MathCalculateOracle::new( + artifacts.address_to_name.clone(), + )))); + } + if let Some(m) = onchain_middleware.clone() { m.borrow_mut().add_abi(artifacts.address_to_abi.clone()); }