Skip to content

Commit

Permalink
Support discovery of SELFDESTRUCT (#88)
Browse files Browse the repository at this point in the history
* Support discovery of SELFDESTRUCT

* fix integration_test:test_one check

* 为selfdestruct单独建立测试合约

* fix oracle result type

* fix test type check

---------

Co-authored-by: xForce <cc@sexsec.com>
  • Loading branch information
ByteSecurity and bitfacai authored Jun 28, 2023
1 parent e5826df commit fc7cb50
Show file tree
Hide file tree
Showing 12 changed files with 226 additions and 6 deletions.
8 changes: 8 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use ityfuzz::evm::middlewares::middleware::Middleware;
use ityfuzz::evm::onchain::endpoints::{Chain, OnChainConfig};
use ityfuzz::evm::onchain::flashloan::{DummyPriceOracle, Flashloan};
use ityfuzz::evm::oracles::bug::BugOracle;
use ityfuzz::evm::oracles::selfdestruct::SelfdestructOracle;
use ityfuzz::evm::oracles::erc20::IERC20OracleFlashloan;
use ityfuzz::evm::oracles::v2_pair::PairBalanceOracle;
use ityfuzz::evm::producers::erc20::ERC20Producer;
Expand Down Expand Up @@ -123,6 +124,9 @@ struct Args {
#[arg(long, default_value = "false")]
panic_on_bug: bool,

#[arg(long, default_value = "true")]
selfdestruct_oracle: bool,

///Enable oracle for detecting whether typed_bug() is called
#[arg(long, default_value = "true")]
typed_bug_oracle: bool,
Expand Down Expand Up @@ -253,6 +257,9 @@ fn main() {
}
}
}
if args.selfdestruct_oracle {
oracles.push(Rc::new(RefCell::new(SelfdestructOracle::new())));
}

if args.typed_bug_oracle {
oracles.push(Rc::new(RefCell::new(TypedBugOracle::new())));
Expand Down Expand Up @@ -332,6 +339,7 @@ fn main() {
},
replay_file: args.replay_file,
flashloan_oracle,
selfdestruct_oracle:args.selfdestruct_oracle,
work_dir: args.work_dir,
write_relationship: args.write_relationship,
run_forever: args.run_forever,
Expand Down
2 changes: 1 addition & 1 deletion integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_one(path):
shell=True
)

if b"target hit" not in p.stderr and b"bug() hit" not in p.stdout and b"[typed_bug]" not in p.stdout:
if b"target hit" not in p.stderr and b"bug() hit" not in p.stdout and b"[typed_bug]" not in p.stdout and b"[selfdestruct]" not in p.stdout:
print(p.stderr.decode("utf-8"))
print(p.stdout.decode("utf-8"))
raise Exception(f"Failed to fuzz {path}")
Expand Down
1 change: 1 addition & 0 deletions src/evm/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub struct Config<VS, Addr, Code, By, Loc, SlotTy, Out, I, S> {
pub price_oracle: Box<dyn PriceOracle>,
pub replay_file: Option<String>,
pub flashloan_oracle: Rc<RefCell<IERC20OracleFlashloan>>,
pub selfdestruct_oracle: bool,
pub work_dir: String,
pub write_relationship: bool,
pub run_forever: bool,
Expand Down
5 changes: 4 additions & 1 deletion src/evm/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,14 @@ where
pub logs: HashSet<u64>,
// set_code data
pub setcode_data: HashMap<EVMAddress, Bytecode>,
// selftdestruct
pub selfdestruct_hit:bool,
// relations file handle
relations_file: std::fs::File,
// Filter duplicate relations
relations_hash: HashSet<u64>,
/// Known typed bugs, used for filtering in duplicate bugs
known_typed_bugs: HashSet<u64>,

}

impl<VS, I, S> Debug for FuzzHost<VS, I, S>
Expand Down Expand Up @@ -182,6 +183,7 @@ where
#[cfg(feature = "print_logs")]
logs: Default::default(),
setcode_data:self.setcode_data.clone(),
selfdestruct_hit:self.selfdestruct_hit,
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 @@ -232,6 +234,7 @@ where
#[cfg(feature = "print_logs")]
logs: Default::default(),
setcode_data:HashMap::new(),
selfdestruct_hit:false,
relations_file: std::fs::File::create(format!("{}/relations.log", workdir)).unwrap(),
relations_hash: HashSet::new(),
current_typed_bug: Default::default(),
Expand Down
1 change: 1 addition & 0 deletions src/evm/middlewares/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub enum MiddlewareType {
OnChain,
Concolic,
Flashloan,
Selfdestruct,
InstructionCoverage
}

Expand Down
1 change: 1 addition & 0 deletions src/evm/onchain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod abi_decompiler;
pub mod endpoints;
pub mod flashloan;
pub mod onchain;
pub mod selfdestruct;
97 changes: 97 additions & 0 deletions src/evm/onchain/selfdestruct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@

use crate::evm::input::{EVMInput, EVMInputT, EVMInputTy};
use crate::evm::middlewares::middleware::CallMiddlewareReturn::ReturnSuccess;
use crate::evm::middlewares::middleware::{Middleware, MiddlewareOp, MiddlewareType, add_corpus};
use crate::evm::host::FuzzHost;
use crate::generic_vm::vm_state::VMStateT;
use libafl::inputs::Input;
use libafl::prelude::{HasCorpus, State, HasMetadata};
use crate::state::{HasCaller, HasItyState};
use crate::evm::types::{convert_u256_to_h160, EVMAddress, EVMU256};
use std::fmt::Debug;
use crate::input::VMInputT;
use revm_interpreter::Interpreter;
use revm_primitives::Bytecode;
use crate::evm::abi::get_abi_type_boxed;
use crate::evm::mutator::AccessPattern;
use crate::state_input::StagedVMState;

use std::rc::Rc;
use std::str::FromStr;
use std::sync::Arc;
use std::cell::RefCell;

pub struct Selfdestruct<VS, I, S>
where
S: State + HasCaller<EVMAddress> + Debug + Clone + 'static,
I: VMInputT<VS, EVMAddress, EVMAddress> + EVMInputT,
VS: VMStateT,
{
_phantom: std::marker::PhantomData<(VS, I, S)>,
}

impl<VS, I, S> Selfdestruct<VS, I, S>
where
S: State + HasCaller<EVMAddress> + HasCorpus<I> + Debug + Clone + 'static,
I: VMInputT<VS, EVMAddress, EVMAddress> + EVMInputT,
VS: VMStateT,
{
pub fn new() -> Self {
Self {
_phantom: std::marker::PhantomData,
}
}
}

impl<VS, I, S> Debug for Selfdestruct<VS, I, S>
where
S: State + HasCaller<EVMAddress> + Debug + Clone + 'static,
I: VMInputT<VS, EVMAddress, EVMAddress> + EVMInputT,
VS: VMStateT,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Selfdestruct")
.finish()
}
}


impl<VS, I, S> Middleware<VS, I, S> for Selfdestruct<VS, I, S>
where
S: State
+ Debug
+ HasCaller<EVMAddress>
+ HasCorpus<I>
+ HasItyState<EVMAddress, EVMAddress, VS>
+ HasMetadata
+ Clone
+ 'static,
I: Input + VMInputT<VS, EVMAddress, EVMAddress> + EVMInputT + 'static,
VS: VMStateT,
{
unsafe fn on_step(&mut self, interp: &mut Interpreter, host: &mut FuzzHost<VS, I, S>, state: &mut S)
where
S: HasCaller<EVMAddress>,
{


let offset_of_arg_offset = match *interp.instruction_pointer {
// detect whether it mutates token balance
0xff => {
host.selfdestruct_hit = true;

}
_ => {
return;
}
};
}

unsafe fn on_insert(&mut self, bytecode: &mut Bytecode, address: EVMAddress, host: &mut FuzzHost<VS, I, S>, state: &mut S) {

}

fn get_type(&self) -> MiddlewareType {
return MiddlewareType::Selfdestruct;
}
}
3 changes: 2 additions & 1 deletion src/evm/oracles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ pub mod function;
pub mod v2_pair;
pub mod bug;
pub mod typed_bug;

pub mod selfdestruct;

pub static ERC20_BUG_IDX: u64 = 0;
pub static FUNCTION_BUG_IDX: u64 = 1;
pub static V2_PAIR_BUG_IDX: u64 = 2;
pub static BUG_BUG_IDX: u64 = 3;
pub static TYPED_BUG_BUG_IDX: u64 = 4;
pub static SELFDESTRUCT_BUG_IDX: u64 = 5;
63 changes: 63 additions & 0 deletions src/evm/oracles/selfdestruct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::evm::input::EVMInput;
use crate::evm::oracle::dummy_precondition;
use crate::evm::oracles::erc20::ORACLE_OUTPUT;
use crate::evm::producers::pair::PairProducer;
use crate::evm::types::{EVMAddress, EVMFuzzState, EVMOracleCtx, EVMU256};
use crate::evm::vm::EVMState;
use crate::oracle::{Oracle, OracleCtx, Producer};
use crate::state::HasExecutionResult;
use bytes::Bytes;
use revm_primitives::Bytecode;
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ops::Deref;
use std::rc::Rc;
use crate::evm::oracles::SELFDESTRUCT_BUG_IDX;

pub struct SelfdestructOracle;

impl SelfdestructOracle {
pub fn new() -> Self {
Self {}
}

}

impl Oracle<EVMState, EVMAddress, Bytecode, Bytes, EVMAddress, EVMU256, Vec<u8>, EVMInput, EVMFuzzState>
for SelfdestructOracle
{
fn transition(&self, _ctx: &mut EVMOracleCtx<'_>, _stage: u64) -> u64 {
0
}

fn oracle(
&self,
ctx: &mut OracleCtx<
EVMState,
EVMAddress,
Bytecode,
Bytes,
EVMAddress,
EVMU256,
Vec<u8>,
EVMInput,
EVMFuzzState,
>,
stage: u64,
) -> Vec<u64> {
let is_hit = ctx.post_state.selfdestruct_hit;
if is_hit {
unsafe {
ORACLE_OUTPUT = format!(
"[selfdestruct] selfdestruct() hit at contract {:?}",
ctx.input.contract
)
}
vec![SELFDESTRUCT_BUG_IDX]
}
else {
vec![]
}
}
}
12 changes: 9 additions & 3 deletions src/evm/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ pub struct EVMState {

/// Is bug() call in Solidity hit?
pub bug_hit: bool,

/// selftdestruct() call in Solidity hit?
pub selfdestruct_hit: bool,
/// bug type call in solidity type
pub typed_bug: Vec<u64>,
}
Expand All @@ -149,6 +150,7 @@ impl Default for EVMState {
post_execution: Vec::new(),
flashloan_data: FlashloanData::new(),
bug_hit: false,
selfdestruct_hit: false,
typed_bug: vec![],
}
}
Expand Down Expand Up @@ -219,6 +221,7 @@ impl EVMState {
post_execution: vec![],
flashloan_data: FlashloanData::new(),
bug_hit: false,
selfdestruct_hit: false,
typed_bug: vec![],
}
}
Expand Down Expand Up @@ -331,6 +334,7 @@ where
self.host.env = input.get_vm_env().clone();
self.host.access_pattern = input.get_access_pattern().clone();
self.host.bug_hit = false;
self.host.selfdestruct_hit = false;
self.host.current_typed_bug = vec![];
self.host.call_count = 0;
let mut repeats = input.get_repeat();
Expand Down Expand Up @@ -585,12 +589,13 @@ where
}

r.new_state.bug_hit = vm_state.bug_hit || self.host.bug_hit;
r.new_state.selfdestruct_hit = vm_state.selfdestruct_hit || self.host.selfdestruct_hit;
r.new_state.typed_bug = [vm_state.typed_bug, self.host.current_typed_bug.clone()].concat();

unsafe {
ExecutionResult {
output: r.output.to_vec(),
reverted: r.ret != InstructionResult::Return && r.ret != InstructionResult::Stop && r.ret != ControlLeak,
reverted: r.ret != InstructionResult::Return && r.ret != InstructionResult::Stop && r.ret != ControlLeak && r.ret != InstructionResult::SelfDestruct,
new_state: StagedVMState::new_with_state(
VMStateT::as_any(&mut r.new_state)
.downcast_ref_unchecked::<VS>()
Expand Down Expand Up @@ -654,7 +659,7 @@ where
return None;
}
assert_eq!(r, InstructionResult::Return);
println!("contract = {:?}", hex::encode(interp.return_value()));
println!("deployer = 0x{} contract = {:?}", hex::encode(self.deployer),hex::encode(interp.return_value()));
let contract_code = Bytecode::new_raw(interp.return_value());
bytecode_analyzer::add_analysis_result_to_state(&contract_code, state);
self.host.set_code(deployed_address, contract_code, state);
Expand Down Expand Up @@ -755,6 +760,7 @@ where
.downcast_ref_unchecked::<EVMState>()
.clone();
self.host.bug_hit = false;
self.host.selfdestruct_hit = false;
self.host.call_count = 0;
self.host.current_typed_bug = vec![];
}
Expand Down
10 changes: 10 additions & 0 deletions src/fuzzers/evm_fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use crate::evm::input::{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::{EVMAddress, EVMFuzzMutator, EVMFuzzState, EVMU256, fixed_address};
use primitive_types::{H160, U256};
Expand Down Expand Up @@ -86,6 +87,15 @@ pub fn evm_fuzzer(
let mut fuzz_host = FuzzHost::new(Arc::new(scheduler.clone()), config.work_dir.clone());
fuzz_host.set_concolic_enabled(config.concolic);

if config.selfdestruct_oracle {
//Selfdestruct middlewares
let mid = Rc::new(RefCell::new(
Selfdestruct::<EVMState, EVMInput, EVMFuzzState>::new(),
));
fuzz_host.add_middlewares(mid.clone());
// Selfdestruct end
}

let onchain_middleware = match config.onchain.clone() {
Some(onchain) => {
Some({
Expand Down
29 changes: 29 additions & 0 deletions tests/selfdestruct/test.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;

import "../../solidity_utils/lib.sol";

contract main {
address private owner;

function destruct() external {
selfdestruct(payable(msg.sender));
}

constructor() {
owner = msg.sender;
}

modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}

function admin_destruct() onlyOwner external {
selfdestruct(payable(msg.sender));
}

function getOwner() public view returns (address) {
return owner;
}
}

0 comments on commit fc7cb50

Please sign in to comment.