From 9c4299288508c02a17399bcb14eac065be887380 Mon Sep 17 00:00:00 2001 From: Michael Rodler Date: Mon, 26 Apr 2021 13:22:40 +0200 Subject: [PATCH 1/2] basic fuzzing support with cargo fuzz --- res/big.abi | 216 ++++++++++++++++++ tests/Cargo.toml | 1 + tests/README.md | 38 +++ tests/fuzz/.gitignore | 4 + tests/fuzz/Cargo.toml | 26 +++ ...h-651f6fb1e4f699cffb23f8cb616f11590d81f5dd | Bin 0 -> 353 bytes .../fuzz_targets/fixed_abi_decode_random.rs | 17 ++ tests/src/fuzztests.rs | 44 ++++ tests/src/lib.rs | 2 + 9 files changed, 348 insertions(+) create mode 100644 res/big.abi create mode 100644 tests/README.md create mode 100644 tests/fuzz/.gitignore create mode 100644 tests/fuzz/Cargo.toml create mode 100644 tests/fuzz/artifacts/fixed_abi_decode_random/crash-651f6fb1e4f699cffb23f8cb616f11590d81f5dd create mode 100644 tests/fuzz/fuzz_targets/fixed_abi_decode_random.rs create mode 100644 tests/src/fuzztests.rs diff --git a/res/big.abi b/res/big.abi new file mode 100644 index 0000000000..932da0b375 --- /dev/null +++ b/res/big.abi @@ -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" + } +] diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 6151a5a711..8cb3862afb 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -10,3 +10,4 @@ ethabi-derive = { path = "../derive" } ethabi-contract = { path = "../contract" } hex = "0.4" hex-literal = "0.3" +lazy_static = "1" diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000000..08f6f8213f --- /dev/null +++ b/tests/README.md @@ -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); +} +``` diff --git a/tests/fuzz/.gitignore b/tests/fuzz/.gitignore new file mode 100644 index 0000000000..572e03bdf3 --- /dev/null +++ b/tests/fuzz/.gitignore @@ -0,0 +1,4 @@ + +target +corpus +artifacts diff --git a/tests/fuzz/Cargo.toml b/tests/fuzz/Cargo.toml new file mode 100644 index 0000000000..67a924aeca --- /dev/null +++ b/tests/fuzz/Cargo.toml @@ -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 diff --git a/tests/fuzz/artifacts/fixed_abi_decode_random/crash-651f6fb1e4f699cffb23f8cb616f11590d81f5dd b/tests/fuzz/artifacts/fixed_abi_decode_random/crash-651f6fb1e4f699cffb23f8cb616f11590d81f5dd new file mode 100644 index 0000000000000000000000000000000000000000..88baa20496f08fc465add9962d77afab987e3853 GIT binary patch literal 353 zcmezP?+Yv2Wd>S<|7ZZ+K7SN|E{lcF$iU!(MVKNXG&lZ7Hw?v%{^+t;_#j6D0HN+I A-~a#s literal 0 HcmV?d00001 diff --git a/tests/fuzz/fuzz_targets/fixed_abi_decode_random.rs b/tests/fuzz/fuzz_targets/fixed_abi_decode_random.rs new file mode 100644 index 0000000000..2f23942bed --- /dev/null +++ b/tests/fuzz/fuzz_targets/fixed_abi_decode_random.rs @@ -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 = load_abi().1; +} + +fuzz_target!(|data: &[u8]| { + if data.len() > 2 { + run_fuzzcase_on_contract_functions(&FUNCTIONS[0..], data, false); + } +}); diff --git a/tests/src/fuzztests.rs b/tests/src/fuzztests.rs new file mode 100644 index 0000000000..a425056b9b --- /dev/null +++ b/tests/src/fuzztests.rs @@ -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) { + let contract: ethabi::Contract = { + let b = include_bytes!("../../res/big.abi"); + ethabi::Contract::load(&b[0..]).unwrap() + }; + let mut funcs: Vec = 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); +} diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 13acefe3d6..67b5e86d56 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -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}; From 0db115e78171054fd9463a9be746002ca3fc6982 Mon Sep 17 00:00:00 2001 From: Michael Rodler Date: Mon, 26 Apr 2021 13:23:11 +0200 Subject: [PATCH 2/2] Fixed out of bounds access resulting in a panic during decoding of dynamic arrays --- ethabi/src/decoder.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ethabi/src/decoder.rs b/ethabi/src/decoder.rs index 0b613123d2..b907f415f4 100644 --- a/ethabi/src/decoder.rs +++ b/ethabi/src/decoder.rs @@ -161,8 +161,15 @@ fn decode_param(param: &ParamType, data: &[u8], offset: usize) -> Result { 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![];