Skip to content

Commit

Permalink
Migrate to using cargo fuzz in CI
Browse files Browse the repository at this point in the history
This commit migrates CI to using the `cargo fuzz` binary with its
settings for compiling Rust code to avoid mismatches like the codegen
unit issue found in rust-fuzz#89
  • Loading branch information
alexcrichton committed Mar 3, 2022
1 parent 2ed340d commit 8d0d439
Show file tree
Hide file tree
Showing 17 changed files with 156 additions and 60 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ jobs:
rustup component add rustfmt --toolchain stable
cargo +stable fmt --all -- --check
- run: cargo install cargo-fuzz

- name: Run tests
run: ./ci/script.sh
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target
Cargo.lock
corpus
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ arbitrary-derive = ["arbitrary/derive"]

[workspace]
members = [
"./example",
"./example_arbitrary",
"./example_mutator",
"./example/fuzz",
"./example_arbitrary/fuzz",
"./example_mutator/fuzz",
]

[dev-dependencies]
Expand Down
52 changes: 11 additions & 41 deletions ci/script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,26 @@ export CARGO_TARGET_DIR=$(pwd)/target
cargo test --doc

pushd ./example
cargo rustc \
--release \
-- \
-Ccodegen-units=1 \
-Cpasses=sancov-module \
-Cllvm-args=-sanitizer-coverage-level=3 \
-Cllvm-args=-sanitizer-coverage-trace-compares \
-Cllvm-args=-sanitizer-coverage-inline-8bit-counters \
-Cllvm-args=-sanitizer-coverage-stack-depth \
-Cllvm-args=-sanitizer-coverage-trace-geps \
-Cllvm-args=-sanitizer-coverage-prune-blocks=0 \
-Zsanitizer=address
(! $CARGO_TARGET_DIR/release/example -runs=100000)
cargo fuzz build
cargo fuzz build --dev
(! cargo fuzz run bananas -- -runs=100000)
popd

pushd ./example_arbitrary
cargo rustc \
--release \
-- \
-Ccodegen-units=1 \
-Cpasses=sancov-module \
-Cllvm-args=-sanitizer-coverage-level=3 \
-Cllvm-args=-sanitizer-coverage-trace-compares \
-Cllvm-args=-sanitizer-coverage-inline-8bit-counters \
-Cllvm-args=-sanitizer-coverage-stack-depth \
-Cllvm-args=-sanitizer-coverage-trace-geps \
-Cllvm-args=-sanitizer-coverage-prune-blocks=0 \
-Zsanitizer=address
(! $CARGO_TARGET_DIR/release/example_arbitrary -runs=10000000)
cargo fuzz build
cargo fuzz build --dev
(! cargo fuzz run rgb -- -runs=10000000)
RUST_LIBFUZZER_DEBUG_PATH=$(pwd)/debug_output \
$CARGO_TARGET_DIR/release/example_arbitrary \
$(ls ./crash-* | head -n 1)
cargo fuzz run rgb \
$(ls ./fuzz/artifacts/rgb/crash-* | head -n 1)
cat $(pwd)/debug_output
grep -q Rgb $(pwd)/debug_output
popd

pushd ./example_mutator
cargo rustc \
--release \
-- \
-Ccodegen-units=1 \
-Cpasses=sancov-module \
-Cllvm-args=-sanitizer-coverage-level=3 \
-Cllvm-args=-sanitizer-coverage-trace-compares \
-Cllvm-args=-sanitizer-coverage-inline-8bit-counters \
-Cllvm-args=-sanitizer-coverage-stack-depth \
-Cllvm-args=-sanitizer-coverage-trace-geps \
-Cllvm-args=-sanitizer-coverage-prune-blocks=0 \
-Zsanitizer=address
(! $CARGO_TARGET_DIR/release/example_mutator -runs=10000000)
cargo fuzz build
cargo fuzz build --dev
(! cargo fuzz run boom -- -runs=10000000)
popd

echo "All good!"
4 changes: 1 addition & 3 deletions example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
[package]
name = "example"
version = "0.1.0"
authors = ["Simonas Kazlauskas <[email protected]>"]
edition = "2018"
edition = "2021"

[dependencies]
libfuzzer-sys = { path = ".." }
16 changes: 16 additions & 0 deletions example/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "example-fuzz"
version = "0.1.0"
authors = ["Simonas Kazlauskas <[email protected]>"]
edition = "2018"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = { path = "../.." }
example = { path = ".." }

[[bin]]
name = "bananas"
path = "fuzz_targets/bananas.rs"
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ fuzz_target!(|data: &[u8]| {
if data == "banana!".as_bytes() {
panic!("success!");
}
example::bananas(data);
});
5 changes: 5 additions & 0 deletions example/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub fn bananas(data: &[u8]) {
if data == &b"banana!"[..] {
panic!("success!");
}
}
4 changes: 2 additions & 2 deletions example_arbitrary/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ version = "0.1.0"
authors = ["Simonas Kazlauskas <[email protected]>"]
edition = "2018"

[dependencies]
libfuzzer-sys = { path = "..", features = ["arbitrary-derive"] }
[target.'cfg(fuzzing)'.dependencies]
arbitrary = "1"
16 changes: 16 additions & 0 deletions example_arbitrary/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "example_arbitrary_fuzz"
version = "0.1.0"
authors = ["Simonas Kazlauskas <[email protected]>"]
edition = "2018"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = { path = "../..", features = ["arbitrary-derive"] }
example_arbitrary = { path = ".." }

[[bin]]
name = "rgb"
path = "fuzz_targets/rgb.rs"
7 changes: 7 additions & 0 deletions example_arbitrary/fuzz/fuzz_targets/rgb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]

use libfuzzer_sys::fuzz_target;

fuzz_target!(|rgb: example_arbitrary::Rgb| {
example_arbitrary::test(rgb);
});
13 changes: 5 additions & 8 deletions example_arbitrary/src/main.rs → example_arbitrary/src/lib.rs
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
#![no_main]

use libfuzzer_sys::{arbitrary, fuzz_target};

#[derive(arbitrary::Arbitrary, Debug)]
struct Rgb {
#[derive(Debug)]
#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
pub struct Rgb {
r: u8,
g: u8,
b: u8,
}

fuzz_target!(|rgb: Rgb| {
pub fn test(rgb: Rgb) {
if rgb.r < rgb.g {
if rgb.g < rgb.b {
panic!("success: r < g < b!");
}
}
});
}
3 changes: 0 additions & 3 deletions example_mutator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,5 @@ version = "0.1.0"
authors = ["Nick Fitzgerald <[email protected]>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
flate2 = "1.0.20"
libfuzzer-sys = { path = ".." }
16 changes: 16 additions & 0 deletions example_mutator/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "example_mutator_fuzz"
version = "0.1.0"
authors = ["Nick Fitzgerald <[email protected]>"]
edition = "2018"

[package.metadata]
cargo-fuzz = true

[dependencies]
flate2 = "1.0.20"
libfuzzer-sys = { path = "../.." }

[[bin]]
name = "boom"
path = "fuzz_targets/boom.rs"
File renamed without changes.
50 changes: 50 additions & 0 deletions example_mutator/fuzz/fuzz_targets/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#![no_main]

use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use libfuzzer_sys::{fuzz_mutator, fuzz_target};
use std::io::{Read, Write};

fuzz_target!(|data: &[u8]| {
example_mutator::test(data);
});

fuzz_mutator!(
|data: &mut [u8], size: usize, max_size: usize, _seed: u32| {
// Decompress the input data. If that fails, use a dummy value.
let mut decompressed = decompress(&data[..size]).unwrap_or_else(|| b"hi".to_vec());

// Mutate the decompressed data with `libFuzzer`'s default mutator. Make
// the `decompressed` vec's extra capacity available for insertion
// mutations via `resize`.
let len = decompressed.len();
let cap = decompressed.capacity();
decompressed.resize(cap, 0);
let new_decompressed_size = libfuzzer_sys::fuzzer_mutate(&mut decompressed, len, cap);

// Recompress the mutated data.
let compressed = compress(&decompressed[..new_decompressed_size]);

// Copy the recompressed mutated data into `data` and return the new size.
let new_size = std::cmp::min(max_size, compressed.len());
data[..new_size].copy_from_slice(&compressed[..new_size]);
new_size
}
);

fn decompress(data: &[u8]) -> Option<Vec<u8>> {
let mut decoder = GzDecoder::new(data);
let mut decompressed = Vec::new();
if decoder.read_to_end(&mut decompressed).is_ok() {
Some(decompressed)
} else {
None
}
}

fn compress(data: &[u8]) -> Vec<u8> {
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder
.write_all(data)
.expect("writing into a vec is infallible");
encoder.finish().expect("writing into a vec is infallible")
}
20 changes: 20 additions & 0 deletions example_mutator/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use flate2::read::GzDecoder;
use std::io::Read;

pub fn test(data: &[u8]) {
if let Some(data) = decompress(data) {
if data.starts_with(b"boom") {
panic!();
}
}
}

fn decompress(data: &[u8]) -> Option<Vec<u8>> {
let mut decoder = GzDecoder::new(data);
let mut decompressed = Vec::new();
if decoder.read_to_end(&mut decompressed).is_ok() {
Some(decompressed)
} else {
None
}
}

0 comments on commit 8d0d439

Please sign in to comment.