From 9bcaecf7c0713c03d7ba177a2572a313d08efb76 Mon Sep 17 00:00:00 2001 From: Nick Lewycky Date: Mon, 2 Dec 2019 13:15:16 -0800 Subject: [PATCH 1/3] Initial commit of a simple fuzzer based on tests/bincode.rs. --- fuzz/.gitignore | 4 ++ fuzz/Cargo.lock | 122 +++++++++++++++++++++++++++++++++ fuzz/Cargo.toml | 33 +++++++++ fuzz/fuzz_targets/bincode.rs | 128 +++++++++++++++++++++++++++++++++++ 4 files changed, 287 insertions(+) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.lock create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/bincode.rs diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..572e03b --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ + +target +corpus +artifacts diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 0000000..3190912 --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,122 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "arbitrary" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bincode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libfuzzer-sys" +version = "0.1.0" +source = "git+https://github.com/rust-fuzz/libfuzzer-sys.git#ff32cf5e5fb3e79ebd2e6325d3c24877a114e370" +dependencies = [ + "arbitrary 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde-bench" +version = "0.0.7" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde-bench-fuzz" +version = "0.0.0" +dependencies = [ + "bincode 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libfuzzer-sys 0.1.0 (git+https://github.com/rust-fuzz/libfuzzer-sys.git)", + "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", + "serde-bench 0.0.7", +] + +[[package]] +name = "serde_derive" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum arbitrary 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "64cf76cb6e2222ed0ea86b2b0ee2f71c96ec6edd5af42e84d59160e91b836ec4" +"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +"checksum bincode 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8ab639324e3ee8774d296864fbc0dbbb256cf1a41c490b94cba90c082915f92" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)" = "aa87058dce70a3ff5621797f1506cb837edd02ac4c0ae642b4542dce802908b8" +"checksum libfuzzer-sys 0.1.0 (git+https://github.com/rust-fuzz/libfuzzer-sys.git)" = "" +"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "1217f97ab8e8904b57dd22eb61cde455fa7446a9c1cf43966066da047c1f3702" +"checksum serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "a8c6faef9a2e64b0064f48570289b4bf8823b7581f1d6157c1b52152306651d0" +"checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..f117b13 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,33 @@ + +[package] +name = "serde-bench-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies.serde-bench] +path = ".." +[dependencies.libfuzzer-sys] +git = "https://github.com/rust-fuzz/libfuzzer-sys.git" +[dependencies.byteorder] +version = "1.0" +[dependencies.serde] +version = "1.0" + +[dependencies.bincode] +version = "1.0" +[dev-dependencies.serde] +version = "1.0" +features = ["derive"] + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "bincode" +path = "fuzz_targets/bincode.rs" diff --git a/fuzz/fuzz_targets/bincode.rs b/fuzz/fuzz_targets/bincode.rs new file mode 100644 index 0000000..e930150 --- /dev/null +++ b/fuzz/fuzz_targets/bincode.rs @@ -0,0 +1,128 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +struct Foo { + some_str: String, + some_u8: u8, + some_u16: u16, + some_u32: u32, + some_u64: u64, + some_i8: i8, + some_i16: i16, + some_i32: i32, + some_i64: i64, + some_bool: bool, +} + +fn serialize(foo: &Foo) -> Vec { + let bincode_bytes = bincode::serialize(&foo).unwrap(); + + let mut serde_bytes = Vec::new(); + serde_bench::serialize(&mut serde_bytes, &foo).unwrap(); + + assert_eq!(bincode_bytes, serde_bytes); + + serde_bytes +} + +fn deserialize(bytes: &Vec) -> Foo { + let bincode_foo = bincode::deserialize::(&bytes).unwrap(); + let serde_foo = serde_bench::deserialize::(&bytes).unwrap(); + assert_eq!(serde_foo, bincode_foo); + + serde_foo +} + +fn extract(data: &[u8], cursor: &mut usize, len: usize) -> Vec { + let mut v: Vec = Vec::with_capacity(len); + let (left, right) = if len + *cursor < data.len() { + (len, 0) + } else { + (data.len() - *cursor, len - (data.len() - *cursor)) + }; + for _ in 0..left { + v.push(data[*cursor]); + *cursor += 1; + } + for _ in 0..right { + v.push(0u8); + } + assert_eq!(v.len(), len); + v +} + +fn extract_u8(data: &[u8], cursor: &mut usize) -> u8 { + extract(&data, cursor, 1).pop().unwrap() +} + +fn extract_u16(data: &[u8], cursor: &mut usize) -> u16 { + let v = extract(&data, cursor, 2); + u16::from_ne_bytes([v[0], v[1]]) +} + +fn extract_u32(data: &[u8], cursor: &mut usize) -> u32 { + let v = extract(&data, cursor, 4); + u32::from_ne_bytes([v[0], v[1], v[2], v[3]]) +} + +fn extract_u64(data: &[u8], cursor: &mut usize) -> u64 { + let v = extract(&data, cursor, 8); + u64::from_ne_bytes([v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]]) +} + +fn extract_i8(data: &[u8], cursor: &mut usize) -> i8 { + extract(&data, cursor, 1).pop().unwrap() as i8 +} + +fn extract_i16(data: &[u8], cursor: &mut usize) -> i16 { + let v = extract(&data, cursor, 2); + i16::from_ne_bytes([v[0], v[1]]) +} + +fn extract_i32(data: &[u8], cursor: &mut usize) -> i32 { + let v = extract(&data, cursor, 4); + i32::from_ne_bytes([v[0], v[1], v[2], v[3]]) +} + +fn extract_i64(data: &[u8], cursor: &mut usize) -> i64 { + let v = extract(&data, cursor, 8); + i64::from_ne_bytes([v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]]) +} + +fn extract_bool(data: &[u8], cursor: &mut usize) -> bool { + extract(&data, cursor, 1).pop().unwrap() != 0 +} + +fn extract_remainder_as_string(data: &[u8], cursor: &mut usize) -> String { + if *cursor >= data.len() { + "".into() + } else { + let (_, right) = data.split_at(*cursor); + String::from_utf8_lossy(right).into() + } +} + +fuzz_target!(|data: &[u8]| { + if data.len() == 0 { + return; + } + let mut cursor: usize = 0; + + let foo = Foo { + some_u8: extract_u8(&data, &mut cursor), + some_u16: extract_u16(&data, &mut cursor), + some_u32: extract_u32(&data, &mut cursor), + some_u64: extract_u64(&data, &mut cursor), + some_i8: extract_i8(&data, &mut cursor), + some_i16: extract_i16(&data, &mut cursor), + some_i32: extract_i32(&data, &mut cursor), + some_i64: extract_i64(&data, &mut cursor), + some_bool: extract_bool(&data, &mut cursor), + some_str: extract_remainder_as_string(&data, &mut cursor), + }; + let bytes = serialize(&foo); + let foo2 = deserialize(&bytes); + assert_eq!(foo, foo2); +}); From 0da566fad3be8596e76f2518cae066a1ea6c3c50 Mon Sep 17 00:00:00 2001 From: Nick Lewycky Date: Mon, 2 Dec 2019 13:15:29 -0800 Subject: [PATCH 2/3] Add a readme for operation of the fuzzer. --- fuzz/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 fuzz/README.md diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 0000000..537380c --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,32 @@ +This directory contains the fuzz tests for serde-bench. To fuzz, we use the `cargo-fuzz` package. + +## Installation + +You may need to install the `cargo-fuzz` package to get the `cargo fuzz` subcommand. Use + +```sh +$ cargo install cargo-fuzz +``` + +`cargo-fuzz` is documented in the [Rust Fuzz Book](https://rust-fuzz.github.io/book/cargo-fuzz.html). + +## Running the fuzzer + +Once `cargo-fuzz` is installed, you can run the `bincode` fuzzer with +```sh +cargo fuzz run bincode +``` + +You should see output that looks something like this: + +``` +INFO: Seed: 2073236486 +INFO: Loaded 1 modules (16636 inline 8-bit counters): 16636 [0x55cf2eddac48, 0x55cf2edded44), +INFO: Loaded 1 PC tables (16636 PCs): 16636 [0x55cf2edded48,0x55cf2ee1fd08), +INFO: 26 files found in /home/nicholas/bench/fuzz/corpus/bincode +INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes +INFO: seed corpus: files: 26 min: 1b max: 86b total: 1135b rss: 127Mb +#27 INITED cov: 2901 ft: 3189 corp: 21/833b exec/s: 0 rss: 172Mb +#2048 pulse cov: 2901 ft: 3189 corp: 21/833b lim: 100 exec/s: 1024 rss: 174Mb +``` +It will continue to generate random inputs forever, until it finds a bug or is terminated. The testcases for bugs it finds go into `fuzz/artifacts/bincode` and you can rerun the fuzzer on a single input by passing it on the command line `cargo fuzz run bincode my_testcase`. From 2dbd4f185d9ea73179d943f6a8cd31a7f86fc5ca Mon Sep 17 00:00:00 2001 From: Nick Lewycky Date: Mon, 2 Dec 2019 13:31:53 -0800 Subject: [PATCH 3/3] Clean up, no functionality change. Co-authored-by: Mark McCaskey --- fuzz/fuzz_targets/bincode.rs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/fuzz/fuzz_targets/bincode.rs b/fuzz/fuzz_targets/bincode.rs index e930150..7ed8200 100644 --- a/fuzz/fuzz_targets/bincode.rs +++ b/fuzz/fuzz_targets/bincode.rs @@ -29,7 +29,9 @@ fn serialize(foo: &Foo) -> Vec { fn deserialize(bytes: &Vec) -> Foo { let bincode_foo = bincode::deserialize::(&bytes).unwrap(); + let serde_foo = serde_bench::deserialize::(&bytes).unwrap(); + assert_eq!(serde_foo, bincode_foo); serde_foo @@ -42,14 +44,12 @@ fn extract(data: &[u8], cursor: &mut usize, len: usize) -> Vec { } else { (data.len() - *cursor, len - (data.len() - *cursor)) }; - for _ in 0..left { - v.push(data[*cursor]); - *cursor += 1; - } - for _ in 0..right { - v.push(0u8); - } + v.extend(&data[*cursor..(*cursor + left)]); + *cursor += left; + v.extend(std::iter::repeat(0u8).take(right)); + assert_eq!(v.len(), len); + v } @@ -96,12 +96,8 @@ fn extract_bool(data: &[u8], cursor: &mut usize) -> bool { } fn extract_remainder_as_string(data: &[u8], cursor: &mut usize) -> String { - if *cursor >= data.len() { - "".into() - } else { - let (_, right) = data.split_at(*cursor); - String::from_utf8_lossy(right).into() - } + let (_, right) = data.split_at(*cursor); + String::from_utf8_lossy(right).into() } fuzz_target!(|data: &[u8]| { @@ -122,7 +118,8 @@ fuzz_target!(|data: &[u8]| { some_bool: extract_bool(&data, &mut cursor), some_str: extract_remainder_as_string(&data, &mut cursor), }; + let bytes = serialize(&foo); - let foo2 = deserialize(&bytes); - assert_eq!(foo, foo2); + let foo_serde = deserialize(&bytes); + assert_eq!(foo, foo_serde); });