Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ bin
corpus
.idea
.vscode
.DS_Store
node_modules
cache
*.bin
Expand Down
18 changes: 5 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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"
Expand Down
22 changes: 5 additions & 17 deletions integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ def test_one(path):
if "taint" in path:
cmd.append("--sha3-bypass")


print(" ".join(cmd))

p = subprocess.run(
Expand Down Expand Up @@ -145,13 +144,17 @@ def test_onchain(test):
etherscan_key,
"--work-dir",
f"w_{name}",
"--onchain-builder",
"https://solc-builder.dev.infra.fuzz.land/"
# "--run-forever"
]

start_time = time.time()

# 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,
Expand Down Expand Up @@ -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(
[
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions offchain_config_1434_0_1701112172878_63428.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"main.sol":{"HelloWorld":{"address":"0xe2f299db16d6263a0547edcda60a36abd4ab53ce","constructor_args":"0x"}}}
2 changes: 1 addition & 1 deletion onchain_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions src/evm/blaz/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ impl BuildJob {
}

pub fn onchain_job(&self, chain: String, addr: EVMAddress) -> Option<BuildJobResult> {
debug!("will get onchain_job: {:?}", addr);
if let Some(replacement) = self.replacements.get(&addr) {
return replacement.clone();
}
Expand Down
1 change: 1 addition & 0 deletions src/evm/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ pub struct Config<VS, Addr, Code, By, Loc, SlotTy, Out, I, S, CI> {
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<BuildJob>,
pub local_files_basedir_pattern: Option<String>,
pub load_corpus: String,
Expand Down
8 changes: 4 additions & 4 deletions src/evm/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down
2 changes: 2 additions & 0 deletions src/evm/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ pub struct EVMInput {
pub repeat: usize,

/// Swap data
#[serde(skip_deserializing)]
pub swap_data: HashMap<String, SwapInfo>,
}

Expand Down Expand Up @@ -193,6 +194,7 @@ pub struct ConciseEVMInput {
pub return_data: Option<Vec<u8>>,

/// Swap data
#[serde(skip_deserializing)]
pub swap_data: HashMap<String, SwapInfo>,
}

Expand Down
9 changes: 5 additions & 4 deletions src/evm/middlewares/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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<usize>, HashSet<usize>, HashSet<usize>) {
pub fn instructions_pc(bytecode: &Bytes) -> (HashSet<usize>, HashSet<usize>, HashSet<usize>) {
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);
}
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -374,7 +375,7 @@ mod tests {
Bytes::from(
hex::decode("60806040526004361061004e5760003560e01c80632d2c55651461008d578063819d4cc6146100de5780638980f11f146101005780638b21f170146101205780639342c8f41461015457600080fd5b36610088576040513481527f27f12abfe35860a9a927b465bb3d4a9c23c8428174b83f278fe45ed7b4da26629060200160405180910390a1005b600080fd5b34801561009957600080fd5b506100c17f0000000000000000000000003e40d73eb977dc6a537af587d48316fee66e9c8c81565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156100ea57600080fd5b506100fe6100f93660046106bb565b610182565b005b34801561010c57600080fd5b506100fe61011b3660046106bb565b61024e565b34801561012c57600080fd5b506100c17f000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe8481565b34801561016057600080fd5b5061017461016f3660046106f3565b610312565b6040519081526020016100d5565b6040518181526001600160a01b0383169033907f6a30e6784464f0d1f4158aa4cb65ae9239b0fa87c7f2c083ee6dde44ba97b5e69060200160405180910390a36040516323b872dd60e01b81523060048201526001600160a01b037f0000000000000000000000003e40d73eb977dc6a537af587d48316fee66e9c8c81166024830152604482018390528316906323b872dd90606401600060405180830381600087803b15801561023257600080fd5b505af1158015610246573d6000803e3d6000fd5b505050505050565b6000811161029a5760405162461bcd60e51b815260206004820152601460248201527316915493d7d49150d3d591549657d05353d5539560621b60448201526064015b60405180910390fd5b6040518181526001600160a01b0383169033907faca8fb252cde442184e5f10e0f2e6e4029e8cd7717cae63559079610702436aa9060200160405180910390a361030e6001600160a01b0383167f0000000000000000000000003e40d73eb977dc6a537af587d48316fee66e9c8c83610418565b5050565b6000336001600160a01b037f000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe8416146103855760405162461bcd60e51b81526020600482015260166024820152754f4e4c595f4c49444f5f43414e5f574954484452415760501b6044820152606401610291565b478281116103935780610395565b825b91508115610412577f000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe846001600160a01b0316634ad509b2836040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103f857600080fd5b505af115801561040c573d6000803e3d6000fd5b50505050505b50919050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b17905261046a90849061046f565b505050565b60006104c4826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166105419092919063ffffffff16565b80519091501561046a57808060200190518101906104e2919061070c565b61046a5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610291565b6060610550848460008561055a565b90505b9392505050565b6060824710156105bb5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610291565b843b6106095760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610291565b600080866001600160a01b03168587604051610625919061075e565b60006040518083038185875af1925050503d8060008114610662576040519150601f19603f3d011682016040523d82523d6000602084013e610667565b606091505b5091509150610677828286610682565b979650505050505050565b60608315610691575081610553565b8251156106a15782518084602001fd5b8160405162461bcd60e51b8152600401610291919061077a565b600080604083850312156106ce57600080fd5b82356001600160a01b03811681146106e557600080fd5b946020939093013593505050565b60006020828403121561070557600080fd5b5035919050565b60006020828403121561071e57600080fd5b8151801515811461055357600080fd5b60005b83811015610749578181015183820152602001610731565b83811115610758576000848401525b50505050565b6000825161077081846020870161072e565b9190910192915050565b602081526000825180602084015261079981604085016020870161072e565b601f01601f1916919091016040019291505056fea2646970667358221220c0f03149dd58fa21e9bfb72a010b74b1e518d704a2d63d8cc44c0ad3a2f573da64736f6c63430008090033").unwrap()
)
));
).bytecode);

assert_eq!(pcs.len(), 1107);
}
Expand Down
133 changes: 133 additions & 0 deletions src/evm/middlewares/math_calculate.rs
Original file line number Diff line number Diff line change
@@ -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<EVMAddress>,
pub fp: HashSet<(EVMAddress, usize)>,
pair_hash: B256,
}

impl MathCalculateMiddleware {
pub fn new(onchain: Option<OnChainConfig>, 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<SC> Middleware<SC> for MathCalculateMiddleware
where
SC: Scheduler<State = EVMFuzzState> + Clone,
{
unsafe fn on_step(&mut self, interp: &mut Interpreter, host: &mut FuzzHost<SC>, _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
}
}
1 change: 1 addition & 0 deletions src/evm/middlewares/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Loading