Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
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