From 01cb8ca558079b83a1bc6008c51722a84d59a53d Mon Sep 17 00:00:00 2001 From: Nick Hynes Date: Sun, 6 Oct 2019 02:19:28 +0000 Subject: [PATCH] Add honggfuzz fuzzing --- .gitignore | 4 ++++ Cargo.toml | 2 +- fuzz/Cargo.toml | 14 ++++++++++++++ fuzz/README.md | 18 ++++++++++++++++++ fuzz/run.sh | 8 ++++++++ fuzz/src/bin/create_contract.rs | 21 +++++++++++++++++++++ fuzz/src/bin/tx.rs | 16 ++++++++++++++++ src/block.rs | 6 +++++- src/test/client.rs | 16 ++++++++-------- tests/create.rs | 12 +++++++++--- tests/runtime_ethereum.rs | 2 +- tests/storage_expiry.rs | 29 +++++++++++++++++------------ 12 files changed, 122 insertions(+), 26 deletions(-) create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/README.md create mode 100755 fuzz/run.sh create mode 100644 fuzz/src/bin/create_contract.rs create mode 100644 fuzz/src/bin/tx.rs diff --git a/.gitignore b/.gitignore index 73d45ed02..5fb46daf0 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,7 @@ cobertura.xml # in case credentials are put in the repo for using # inside the docker container .git-credentials + +fuzz/Cargo.lock +fuzz/artifacts +fuzz/hfuzz_* diff --git a/Cargo.toml b/Cargo.toml index 512fdbd40..7742eeb3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,4 +83,4 @@ members = [ "gateway", "genesis", ] -exclude = ["tests"] +exclude = ["tests", "fuzz"] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 000000000..fd29136ef --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "fuzz" +version = "0.1.0" +authors = ["Oasis Labs "] +edition = "2018" +publish = false + +[dependencies] +# arbitrary = "0.2" +runtime-ethereum = { path = "..", features = ["test"] } +honggfuzz = "0.5" + +[patch.crates-io] +ring = { git = "https://github.com/akash-fortanix/ring", branch = "sgx-target" } diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 000000000..4a6db0815 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,18 @@ +# Runtime fuzzing infra + +The runtime is fuzzed using [honggfuzz](https://github.com/google/honggfuzz) via [cargo hfuzz](https://crates.io/crates/honggfuzz). + +Available fuzzing targets are in `src/bin/*.rs`. +Currently there are two targets: +* `create_contract`, which creates a new contract and, if that succeeds, calls it +* `tx`, which sends random transactions to random addresses + +To begin fuzzing, + +1. install the hfuzz [dependencies](https://github.com/rust-fuzz/honggfuzz-rs#dependencies) +2. `cargo install honggfuzz` +3. ensure that runtime is buildable (i.e. build or otherwise obtain Ekiden enclaves) +4. `./run.sh ` + +At the current time, both targets have been fuzzed for over 1m iterations with no crashes found. +While encouraging, don't let this stop you from trying! diff --git a/fuzz/run.sh b/fuzz/run.sh new file mode 100755 index 000000000..1482c4edf --- /dev/null +++ b/fuzz/run.sh @@ -0,0 +1,8 @@ +repo_root=$(git rev-parse --show-toplevel) + +export KM_ENCLAVE_PATH="$repo_root/.ekiden/target/x86_64-fortanix-unknown-sgx/debug/ekiden-keymanager-runtime.sgxs" +export RUSTFLAGS='-Ctarget-feature=+aes,+ssse3' + +cd "$repo_root/fuzz" + +cargo hfuzz run $1 diff --git a/fuzz/src/bin/create_contract.rs b/fuzz/src/bin/create_contract.rs new file mode 100644 index 000000000..63aa600f3 --- /dev/null +++ b/fuzz/src/bin/create_contract.rs @@ -0,0 +1,21 @@ +fn main() { + let mut client = runtime_ethereum::test::Client::new(); + loop { + honggfuzz::fuzz!(|params: ( + Vec, + [u8; 32], + Option, + Option, + Vec, + [u8; 32] + )| { + let (code, balance, expiry, c10lity, call_data, call_value) = params; + let addr = + match client.create_contract_with_header(code, &balance.into(), expiry, c10lity) { + Ok((_hash, addr)) => addr, + Err(_) => return, + }; + client.call(&addr, call_data, &call_value.into()).ok(); + }); + } +} diff --git a/fuzz/src/bin/tx.rs b/fuzz/src/bin/tx.rs new file mode 100644 index 000000000..645c79988 --- /dev/null +++ b/fuzz/src/bin/tx.rs @@ -0,0 +1,16 @@ +fn main() { + let mut client = runtime_ethereum::test::Client::new(); + loop { + honggfuzz::fuzz!(|params: ([u8; 20], Vec, [u8; 32])| { + let (addr, data, value) = params; + client + .send( + Some(&addr.into()), + data, + &value.into(), + None, /* nonce */ + ) + .ok(); + }); + } +} diff --git a/src/block.rs b/src/block.rs index 6d9db80e3..7a801346b 100644 --- a/src/block.rs +++ b/src/block.rs @@ -39,7 +39,11 @@ impl EthereumBatchHandler { impl BatchHandler for EthereumBatchHandler { fn start_batch(&self, ctx: &mut TxnContext) { - let logger = get_logger("ethereum/block"); + let logger = if cfg!(fuzzing) { + Logger::root(slog::Discard, slog::o!()) + } else { + get_logger("ethereum/block") + }; info!(logger, "Computing new block"; "round" => ctx.header.round + 1); diff --git a/src/test/client.rs b/src/test/client.rs index cdf44c058..a4c6768cb 100644 --- a/src/test/client.rs +++ b/src/test/client.rs @@ -28,7 +28,7 @@ use ethcore::{ }; use ethereum_types::{Address, H256, U256}; use ethkey::{KeyPair, Secret}; - +use failure::Fallible; use io_context::Context as IoContext; use keccak_hash::keccak; use runtime_ethereum_api::ExecutionResult; @@ -62,7 +62,7 @@ pub struct Client { /// In-memory MKVS. pub mkvs: Option, /// Key manager client. - pub km_client: Arc, + pub km_client: Arc, /// Results. pub results: HashMap, } @@ -210,13 +210,13 @@ impl Client { balance: &U256, expiry: Option, confidentiality: Option, - ) -> (H256, Address) { + ) -> Fallible<(H256, Address)> { let mut data = Self::make_header(expiry, confidentiality); data.extend(code); let (hash, address) = self .send(None, data, balance, None) - .expect("deployment should succeed"); - (hash, address.unwrap()) + .map_err(failure::err_msg)?; + Ok((hash, address.unwrap())) } /// Returns the receipt for the given transaction hash. @@ -252,12 +252,12 @@ impl Client { } /// Returns the return value of the contract's method. - pub fn call(&mut self, contract: &Address, data: Vec, value: &U256) -> Vec { + pub fn call(&mut self, contract: &Address, data: Vec, value: &U256) -> Fallible> { let (hash, _) = self .send(Some(contract), data, value, None) - .expect("call should succeed"); + .map_err(failure::err_msg)?; let result = self.result(hash); - result.output + Ok(result.output) } /// Sends a transaction onchain that updates the blockchain, analagous to the web3.js send(). diff --git a/tests/create.rs b/tests/create.rs index 9f1d33ad9..9027db16c 100644 --- a/tests/create.rs +++ b/tests/create.rs @@ -83,7 +83,9 @@ fn create_and_set_storage() { .encode_input(&[]) .unwrap(); - let result = client.call(&deployed_addr, retrieve_a_data, &0.into()); + let result = client + .call(&deployed_addr, retrieve_a_data, &0.into()) + .unwrap(); U256::from(result.as_slice()) }; @@ -101,7 +103,9 @@ fn create_and_set_storage() { .encode_input(&[]) .unwrap(); - let result = client.call(&deployed_addr, retrieve_a_data, &0.into()); + let result = client + .call(&deployed_addr, retrieve_a_data, &0.into()) + .unwrap(); U256::from(result.as_slice()) }; @@ -119,7 +123,9 @@ fn create_and_set_storage() { .encode_input(&[]) .unwrap(); - let result = client.call(&deployed_addr, retrieve_a_data, &0.into()); + let result = client + .call(&deployed_addr, retrieve_a_data, &0.into()) + .unwrap(); (ðabi::decode( &[ethabi::ParamType::Array(Box::new(ethabi::ParamType::Uint( 256, diff --git a/tests/runtime_ethereum.rs b/tests/runtime_ethereum.rs index bc186a27a..e51b4a0a9 100644 --- a/tests/runtime_ethereum.rs +++ b/tests/runtime_ethereum.rs @@ -81,7 +81,7 @@ fn test_solidity_x_contract_call() { contract_b )) .unwrap(); - let output = client.call(&contract_a, data, &U256::zero()); + let output = client.call(&contract_a, data, &U256::zero()).unwrap(); // expected output is 42 assert_eq!( diff --git a/tests/storage_expiry.rs b/tests/storage_expiry.rs index 7f2874897..c8d63293b 100644 --- a/tests/storage_expiry.rs +++ b/tests/storage_expiry.rs @@ -15,6 +15,7 @@ fn get_counter<'a>(contract: &Address, client: &mut test::Client) -> U256 { U256::from( client .call(contract, sighash_data, &U256::zero()) + .unwrap() .as_slice(), ) } @@ -55,12 +56,14 @@ fn test_invalid_expiry() { // attempt to deploy counter contract with invalid expiry let deploy_expiry = now - 1; - let (tx_hash, _) = client.create_contract_with_header( - contracts::counter::solidity_initcode(), - &U256::zero(), - Some(deploy_expiry), - None, - ); + let (tx_hash, _) = client + .create_contract_with_header( + contracts::counter::solidity_initcode(), + &U256::zero(), + Some(deploy_expiry), + None, + ) + .unwrap(); // check that deploy failed (0 status code) let status = client.result(tx_hash).status_code; @@ -77,12 +80,14 @@ fn test_expiry() { // deploy counter contract with expiry let duration = 31557600; - let (_, contract) = client.create_contract_with_header( - contracts::counter::solidity_initcode(), - &U256::zero(), - Some(deploy_time + duration), - None, - ); + let (_, contract) = client + .create_contract_with_header( + contracts::counter::solidity_initcode(), + &U256::zero(), + Some(deploy_time + duration), + None, + ) + .unwrap(); // check expiry let expiry = client.storage_expiry(contract);