Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic Fuzzing Support and a Fix for a uncovered Bug during decoding of dynamic arrays #221

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
11 changes: 9 additions & 2 deletions ethabi/src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,15 @@ fn decode_param(param: &ParamType, data: &[u8], offset: usize) -> Result<DecodeR
ParamType::FixedArray(ref t, len) => {
let is_dynamic = param.is_dynamic();

let (tail, mut new_offset) =
if is_dynamic { (&data[as_usize(&peek_32_bytes(data, offset)?)?..], 0) } else { (data, offset) };
let (tail, mut new_offset) = if is_dynamic {
let offset = as_usize(&peek_32_bytes(data, offset)?)?;
if offset > data.len() {
return Err(Error::InvalidData);
}
(&data[offset..], 0)
} else {
(data, offset)
};

let mut tokens = vec![];

Expand Down
216 changes: 216 additions & 0 deletions res/big.abi
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
[
{
"type": "fallback"
},
{
"inputs": [],
"name": "f1",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "newOwner",
"type": "address"
},
{
"internalType": "bool",
"name": "booool",
"type": "bool"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "newPhase",
"type": "uint256"
}
],
"name": "setPhase",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "i1",
"type": "uint8"
},
{
"internalType": "uint16",
"name": "i2",
"type": "uint16"
},
{
"internalType": "uint32",
"name": "i3",
"type": "uint32"
},
{
"internalType": "uint64",
"name": "i4",
"type": "uint64"
},
{
"internalType": "uint128",
"name": "i5",
"type": "uint128"
},
{
"internalType": "uint256",
"name": "i6",
"type": "uint256"
}
],
"name": "f_uints",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "int8",
"name": "i1",
"type": "int8"
},
{
"internalType": "int16",
"name": "i2",
"type": "int16"
},
{
"internalType": "int32",
"name": "i3",
"type": "int32"
},
{
"internalType": "int64",
"name": "i4",
"type": "int64"
},
{
"internalType": "int128",
"name": "i5",
"type": "int128"
},
{
"internalType": "int256",
"name": "i6",
"type": "int256"
}
],
"name": "f_ints",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "i1",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "i2",
"type": "bytes"
},
{
"internalType": "string",
"name": "i3",
"type": "string"
}
],
"name": "f_bytesandso",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint[10]",
"name": "i1",
"type": "uint[10]"
},
{
"internalType": "string[5]",
"name": "i2",
"type": "string[5]"
},
{
"internalType": "uint[]",
"name": "i3",
"type": "uint[]"
},
{
"internalType": "address[][]",
"name": "i4",
"type": "address[][]"
}
],
"name": "f_arrays",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"name": "c",
"type": "tuple[]",
"components": [
{
"name": "x",
"type": "uint256"
},
{
"name": "y",
"type": "uint256"
}
]
},
{
"name": "d",
"type": "tuple[]",
"components": [
{
"name": "x",
"type": "uint256"
},
{
"name": "y",
"type": "tuple[]",
"components": [
{
"name": "x",
"type": "uint256"
},
{
"name": "y",
"type": "string[]"
}
]
}
]
}
],
"name": "f_tuple",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
]
1 change: 1 addition & 0 deletions tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ ethabi-derive = { path = "../derive" }
ethabi-contract = { path = "../contract" }
hex = "0.4"
hex-literal = "0.3"
lazy_static = "1"
38 changes: 38 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Ethabi Test Suite

...

## Fuzz Testing

### Running Fuzz Testing

We support fuzzing the decoder with
[cargo-fuzz](https://rust-fuzz.github.io/book/cargo-fuzz.html). The fuzzing
harnesses are located in `./src/fuzz_targets`. To launch the fuzzer you can use
the following command:

```
rustup run nightly cargo fuzz run fixed_abi_decode_random
```

### Incorporating Generated Inputs Into Regular Tests

`cargo fuzz` generates crashing inputs (e.g., on a panic). Add this to the git
repository and update the `fuzztests` crate.

```sh
git add -f ./fuzz/artifacts/fixed_abi_decode_random/crash-651f6fb1e4f699cffb23f8cb616f11590d81f5dd
```

And then add a testcase to `src/fuzztests.rs` based on this template:

```rust
#[test]
fn fuzz_test_1() {
let (_contract, funcs) = load_abi();
let input =
include_bytes!("../fuzz/artifacts/fixed_abi_decode_random/crash-651f6fb1e4f699cffb23f8cb616f11590d81f5dd");

run_fuzzcase_on_contract_functions(&funcs, &input[0..], true);
}
```
4 changes: 4 additions & 0 deletions tests/fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

target
corpus
artifacts
26 changes: 26 additions & 0 deletions tests/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

[package]
name = "ethabi-tests-fuzz"
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
edition = "2018"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = "0.4"
ethabi = { path = "../../ethabi" }
ethabi-tests = { path = "../" }
lazy_static = "1"

# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[[bin]]
name = "fixed_abi_decode_random"
path = "fuzz_targets/fixed_abi_decode_random.rs"
test = false
doc = false
Binary file not shown.
17 changes: 17 additions & 0 deletions tests/fuzz/fuzz_targets/fixed_abi_decode_random.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#![no_main]
use libfuzzer_sys::fuzz_target;

#[macro_use]
extern crate lazy_static;

use ethabi_tests::fuzztests::{load_abi, run_fuzzcase_on_contract_functions};

lazy_static! {
static ref FUNCTIONS: Vec<ethabi::Function> = load_abi().1;
}

fuzz_target!(|data: &[u8]| {
if data.len() > 2 {
run_fuzzcase_on_contract_functions(&FUNCTIONS[0..], data, false);
}
});
44 changes: 44 additions & 0 deletions tests/src/fuzztests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! Testcases produced by running a fuzzer

/// function used by the fuzzing driver to run input decode for a given contract
pub fn run_fuzzcase_on_contract_functions(funcs: &[ethabi::Function], data: &[u8], output: bool) {
let n = (data[0] as usize) % funcs.len();
let func = funcs.into_iter().nth(n).unwrap();
if output {
println!("function: {}", func.signature());
println!("input: {:?}", data);
}
match func.decode_input(&data[1..]) {
Ok(dec) => {
if output {
println!("decode: {:?}", dec);
}
}
Err(e) => {
if output {
println!("error: {:?}", e);
}
}
}
}

/// load the big.abi ABI spec from the `res` directory and construct a contract and a list of
/// functions, which is sorted by signature.
pub fn load_abi() -> (ethabi::Contract, Vec<ethabi::Function>) {
let contract: ethabi::Contract = {
let b = include_bytes!("../../res/big.abi");
ethabi::Contract::load(&b[0..]).unwrap()
};
let mut funcs: Vec<ethabi::Function> = contract.functions().cloned().collect();
funcs.sort_by(|a, b| a.signature().partial_cmp(&b.signature()).unwrap());
(contract, funcs)
}

#[test]
fn fuzz_test_1() {
let (_contract, funcs) = load_abi();
let input =
include_bytes!("../fuzz/artifacts/fixed_abi_decode_random/crash-651f6fb1e4f699cffb23f8cb616f11590d81f5dd");

run_fuzzcase_on_contract_functions(&funcs, &input[0..], true);
}
2 changes: 2 additions & 0 deletions tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use_contract!(operations, "../res/Operations.abi");
use_contract!(urlhint, "../res/urlhint.abi");
use_contract!(test_rust_keywords, "../res/test_rust_keywords.abi");

pub mod fuzztests;

#[cfg(test)]
mod tests {
use crate::{eip20, validators};
Expand Down