Skip to content

Commit

Permalink
Merge all fuzz targets into one (#1193)
Browse files Browse the repository at this point in the history
* Merge all fuzz targets into one

This commit merges all fuzz targets in this repository into one fuzz
target. The goal here is to have better control over how these fuzz
targets run on oss-fuzz. I've discovered recently that of the 22 fuzz
targets that the Wasmtime project has less than 10 are running each day
meaning that the coverage can be spotty throughout the time that fuzzers
are running. This appears to be due to the fact that the time slicing
unit is for individual fuzzers and presumably Wasmtime reached its quota
for the day running those fuzzers.

By merging all fuzzers into one binary the hope is that we're able to
have more fine-grained control over what's fuzzed when rather than being
at the mercy of oss-fuzz. This commit replaces 9 fuzzers with 1 single
fuzzer.

The new fuzzer is structured generally as taking the first byte as a
discriminant of which sub-fuzzer to run. For string-taking fuzzers I
figured it'd be convenient if the input was literally a string still so
the string-based fuzzers will all run on valid utf-8 inputs.

* Run rustfmt

* Fix wasmtime build
  • Loading branch information
alexcrichton authored Sep 9, 2023
1 parent e2af293 commit e29e4ca
Show file tree
Hide file tree
Showing 15 changed files with 187 additions and 209 deletions.
54 changes: 2 additions & 52 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,57 +31,7 @@ test = false
doctest = false

[[bin]]
name = "text-parser"
path = "fuzz_targets/text-parser.rs"
name = "run"
path = "fuzz_targets/run.rs"
test = false
bench = false

[[bin]]
name = "validate"
path = "fuzz_targets/validate.rs"
test = false
bench = false

[[bin]]
name = "print"
path = "fuzz_targets/print.rs"
test = false
bench = false

[[bin]]
name = "roundtrip"
path = "fuzz_targets/roundtrip.rs"
test = false
bench = false

[[bin]]
name = "incremental-parse"
path = "fuzz_targets/incremental-parse.rs"
test = false
bench = false

[[bin]]
name = "validate-valid-module"
path = "fuzz_targets/validate-valid-module.rs"
test = false
doc = false
bench = false

[[bin]]
name = "mutate"
path = "fuzz_targets/mutate.rs"
test = false
doc = false
bench = false

[[bin]]
name = "no-traps"
path = "fuzz_targets/no-traps.rs"
test = false
doc = false

[[bin]]
name = "roundtrip-wit"
path = "fuzz_targets/roundtrip-wit.rs"
test = false
doc = false
7 changes: 0 additions & 7 deletions fuzz/fuzz_targets/print.rs

This file was deleted.

82 changes: 82 additions & 0 deletions fuzz/fuzz_targets/run.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#![no_main]

use arbitrary::Unstructured;
use libfuzzer_sys::fuzz_target;
use std::sync::OnceLock;

// Helper macro which takes a static list of fuzzers as input which are then
// delegated to internally based on the fuzz target selected.
//
// In general this fuzz target will execute a number of fuzzers all with the
// same input. The `FUZZER` environment variable can be used to forcibly disable
// all but one.
macro_rules! run_fuzzers {
($($fuzzer:ident: $kind:ident,)*) => {
static ENABLED: OnceLock<u32> = OnceLock::new();

fuzz_target!(|bytes: &[u8]| {
// Lazily initialize this fuzzer in terms of logging as well as
// enabled fuzzers via the `FUZZER` env var.
let enabled = *ENABLED.get_or_init(|| {
env_logger::init();
let configured = std::env::var("FUZZER").ok();
let configured = configured.as_deref();
let mut enabled = 0;
let mut index = 0;

$(
if configured.is_none() || configured == Some(stringify!($fuzzer)) {
enabled |= 1 << index;
}
index += 1;
)*
let _ = index;

enabled
});

let mut index = 0;
$(
if enabled & (1 << index) != 0 {
run_fuzzers!(@run $fuzzer $kind bytes index);
}
index += 1;
)*
let _ = index;
});
};

(@run $fuzzer:ident unstructured $bytes:ident $index:ident) => {
// Use the first byte of input as a discriminant of which fuzzer to
// select.
//
// Afterwards run the specific fuzzer that the fuzz input is
// targeted for so long as it's enabled.
if let Some((which_fuzzer, bytes)) = $bytes.split_first() {
if *which_fuzzer == $index {
let mut u = Unstructured::new(bytes);
let _ = wasm_tools_fuzz::$fuzzer::run(&mut u);
}
}
};

(@run $fuzzer:ident string $bytes:ident $index:ident) => {
// For string-based fuzzers run all fuzzers enabled for all
// string-looking inputs.
if let Ok(s) = std::str::from_utf8($bytes) {
wasm_tools_fuzz::$fuzzer::run(s);
}
};
}

run_fuzzers! {
mutate: unstructured,
validate_valid_module: unstructured,
roundtrip_wit: unstructured,
no_traps: unstructured,
validate: unstructured,
incremental_parse: unstructured,
print: unstructured,
roundtrip: string,
text_parser: string,
}
16 changes: 0 additions & 16 deletions fuzz/fuzz_targets/text-parser.rs

This file was deleted.

58 changes: 0 additions & 58 deletions fuzz/fuzz_targets/validate.rs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
#![no_main]

use libfuzzer_sys::*;
use arbitrary::{Result, Unstructured};
use wasmparser::*;
use Payload::*;

Expand All @@ -9,8 +7,8 @@ use Payload::*;
//
// The assertion here is that parsing everything in one go should always produce
// the exact same results as an incremental parse.
fuzz_target!(|data: Vec<Vec<u8>>| {
drop(env_logger::try_init());
pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
let data: Vec<Vec<u8>> = u.arbitrary()?;

// Concatenate everything together, create our expected iterator of
// payloads, and then write out `input.wasm` if debugging is enabled.
Expand Down Expand Up @@ -213,4 +211,5 @@ fuzz_target!(|data: Vec<Vec<u8>>| {
}
}
}
});
Ok(())
}
10 changes: 10 additions & 0 deletions fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ use libfuzzer_sys::arbitrary::{Result, Unstructured};
use std::fmt::Debug;
use wasm_smith::{Component, Module, SwarmConfig};

pub mod incremental_parse;
pub mod mutate;
pub mod no_traps;
pub mod print;
pub mod roundtrip;
pub mod roundtrip_wit;
pub mod text_parser;
pub mod validate;
pub mod validate_valid_module;

pub fn generate_valid_module(
u: &mut Unstructured,
configure: impl FnOnce(&mut SwarmConfig, &mut Unstructured<'_>) -> Result<()>,
Expand Down
23 changes: 8 additions & 15 deletions fuzz/fuzz_targets/mutate.rs → fuzz/src/mutate.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
#![no_main]

use arbitrary::Unstructured;
use libfuzzer_sys::fuzz_target;
use arbitrary::{Result, Unstructured};
use std::sync::atomic::{AtomicU64, Ordering};
use wasmparser::WasmFeatures;

static NUM_RUNS: AtomicU64 = AtomicU64::new(0);
static NUM_SUCCESSFUL_MUTATIONS: AtomicU64 = AtomicU64::new(0);

fuzz_target!(|bytes: &[u8]| {
let _ = env_logger::try_init();

pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
// Generate a random Wasm module with `wasm-smith` as well as a RNG seed for
// use with `wasm-mutate`.

let mut seed = 0;
let mut preserve_semantics = false;
let mut u = Unstructured::new(bytes);
let (wasm, config) = match wasm_tools_fuzz::generate_valid_module(&mut u, |config, u| {
let (wasm, config) = crate::generate_valid_module(u, |config, u| {
config.exceptions_enabled = false;
seed = u.arbitrary()?;
preserve_semantics = u.arbitrary()?;
Ok(())
}) {
Ok(m) => m,
Err(_) => return,
};
})?;
log::debug!("seed = {}", seed);

// Keep track of how many runs we've done thus far and how many of those
Expand Down Expand Up @@ -59,7 +50,7 @@ fuzz_target!(|bytes: &[u8]| {
Ok(iterator) => iterator,
Err(e) => {
log::warn!("Failed to mutate the Wasm: {}", e);
return;
return Ok(());
}
};

Expand Down Expand Up @@ -112,7 +103,9 @@ fuzz_target!(|bytes: &[u8]| {
eval::assert_same_evaluation(&wasm, &mutated_wasm);
}
}
});

Ok(())
}

#[cfg(feature = "wasmtime")]
#[path = "../../crates/fuzz-stats/src/lib.rs"]
Expand Down
22 changes: 8 additions & 14 deletions fuzz/fuzz_targets/no-traps.rs → fuzz/src/no_traps.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
#![no_main]

use arbitrary::Unstructured;
use libfuzzer_sys::fuzz_target;
use arbitrary::{Result, Unstructured};
use wasm_smith::SwarmConfig;
#[cfg(feature = "wasmtime")]
use wasmtime::*;
Expand All @@ -12,25 +9,21 @@ pub mod fuzz_stats;

// Define a fuzz target that accepts arbitrary
// `Module`s as input.
fuzz_target!(|data: &[u8]| {
pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
// Use data to generate a random wasm module
let mut u = Unstructured::new(data);
let (wasm_bytes, config) = match wasm_tools_fuzz::generate_valid_module(&mut u, |config, _| {
let (wasm_bytes, config) = crate::generate_valid_module(u, |config, _| {
config.disallow_traps = true;
config.threads_enabled = false;
config.exceptions_enabled = false;
config.max_memory_pages = config.max_memory_pages.min(100);
Ok(())
}) {
Ok(m) => m,
Err(_) => return,
};
})?;
validate_module(config.clone(), &wasm_bytes);

// Tail calls aren't implemented in wasmtime, so don't try to run them
// there.
if config.tail_call_enabled {
return;
return Ok(());
}

#[cfg(feature = "wasmtime")]
Expand Down Expand Up @@ -62,7 +55,7 @@ fuzz_target!(|data: &[u8]| {
.and_then(|imports| Instance::new(&mut store, &module, &imports));
let instance = match inst_result {
Ok(r) => r,
Err(err) => return check_err(err),
Err(err) => return Ok(check_err(err)),
};

let args = fuzz_stats::dummy::dummy_values(func_ty.params());
Expand Down Expand Up @@ -102,7 +95,8 @@ fuzz_target!(|data: &[u8]| {
panic!("generated wasm trapped in non-trapping mode: {}", err)
}
}
});
Ok(())
}

fn validate_module(config: SwarmConfig, wasm_bytes: &Vec<u8>) {
// Validate the module or component and assert that it passes validation.
Expand Down
7 changes: 7 additions & 0 deletions fuzz/src/print.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use arbitrary::{Result, Unstructured};

pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
let data = u.bytes(u.len())?;
drop(wasmprinter::print_bytes(data));
Ok(())
}
Loading

0 comments on commit e29e4ca

Please sign in to comment.