Skip to content
This repository has been archived by the owner on Oct 22, 2023. It is now read-only.

Add honggfuzz fuzzing #923

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion .buildkite/rust/build_runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export KM_ENCLAVE_PATH="$src_dir/target/x86_64-fortanix-unknown-sgx/debug/ekiden
case $variant in
elf)
# Build non-SGX runtime.
cargo build --locked -p runtime-ethereum
cargo build --locked -p runtime-ethereum -p fuzz
;;
sgxs)
# Build SGX runtime.
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
hfuzz_*
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ heapsize = { git = "https://github.com/oasislabs/heapsize", branch = "sgx-target
members = [
"api",
"common",
"fuzz",
"gateway",
"genesis",
]
Expand Down
13 changes: 13 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "fuzz"
version = "0.1.0"
authors = ["Oasis Labs <feedback@oasislabs.com>"]
edition = "2018"
publish = false

[dependencies]
runtime-ethereum = { path = "..", features = ["test"] }
honggfuzz = "0.5"

[patch.crates-io]
ring = { git = "https://github.com/akash-fortanix/ring", branch = "sgx-target" }
18 changes: 18 additions & 0 deletions fuzz/README.md
Original file line number Diff line number Diff line change
@@ -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. `python3 fuzz.py <target name>`

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!
29 changes: 29 additions & 0 deletions fuzz/fuzz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env python3

import argparse
import subprocess
import os
import os.path as osp

KM_ENV = 'KM_ENCLAVE_PATH'
REPO_ROOT = osp.abspath(osp.join(osp.dirname(__file__), '..'))


def main():
parser = argparse.ArgumentParser()
parser.add_argument('target', help='The name of the fuzz target to run')
args = parser.parse_args()
run(f'cargo hfuzz run {args.target}')


def run(cmd):
env = dict(os.environ)
if KM_ENV not in env:
env[KM_ENV] = osp.join(REPO_ROOT, '.ekiden', 'target',
'x86_64-fortanix-unknown-sgx', 'debug',
'ekiden-keymanager-runtime.sgxs')
subprocess.run(cmd, env=env, cwd=REPO_ROOT, shell=True, check=True)


if __name__ == '__main__':
main()
21 changes: 21 additions & 0 deletions fuzz/src/bin/create_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
fn main() {
let mut client = runtime_ethereum::test::Client::new();
loop {
honggfuzz::fuzz!(|params: (
Vec<u8>,
[u8; 32],
Option<u64>,
Option<bool>,
Vec<u8>,
[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();
});
}
}
16 changes: 16 additions & 0 deletions fuzz/src/bin/tx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
fn main() {
let mut client = runtime_ethereum::test::Client::new();
loop {
honggfuzz::fuzz!(|params: ([u8; 20], Vec<u8>, [u8; 32])| {
let (addr, data, value) = params;
client
.send(
Some(&addr.into()),
data,
&value.into(),
None, /* nonce */
)
.ok();
});
}
}
6 changes: 5 additions & 1 deletion src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
16 changes: 8 additions & 8 deletions src/test/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -62,7 +62,7 @@ pub struct Client {
/// In-memory MKVS.
pub mkvs: Option<UrkelTree>,
/// Key manager client.
pub km_client: Arc<KeyManagerClient>,
pub km_client: Arc<dyn KeyManagerClient>,
/// Results.
pub results: HashMap<H256, ExecutionResult>,
}
Expand Down Expand Up @@ -210,13 +210,13 @@ impl Client {
balance: &U256,
expiry: Option<u64>,
confidentiality: Option<bool>,
) -> (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.
Expand Down Expand Up @@ -252,12 +252,12 @@ impl Client {
}

/// Returns the return value of the contract's method.
pub fn call(&mut self, contract: &Address, data: Vec<u8>, value: &U256) -> Vec<u8> {
pub fn call(&mut self, contract: &Address, data: Vec<u8>, value: &U256) -> Fallible<Vec<u8>> {
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().
Expand Down
12 changes: 9 additions & 3 deletions tests/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
};

Expand All @@ -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())
};

Expand All @@ -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();
(&ethabi::decode(
&[ethabi::ParamType::Array(Box::new(ethabi::ParamType::Uint(
256,
Expand Down
2 changes: 1 addition & 1 deletion tests/runtime_ethereum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
29 changes: 17 additions & 12 deletions tests/storage_expiry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)
}
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down