Replace binaryen -ttf based fuzzing with wasm-smith (#2336)

This commit removes the binaryen support for fuzzing from wasmtime,
instead switching over to `wasm-smith`. In general it's great to have
what fuzzing we can, but our binaryen support suffers from a few issues:

* The Rust crate, binaryen-sys, seems largely unmaintained at this
  point. While we could likely take ownership and/or send PRs to update
  the crate it seems like the maintenance is largely on us at this point.

* Currently the binaryen-sys crate doesn't support fuzzing anything
  beyond MVP wasm, but we're interested at least in features like bulk
  memory and reference types. Additionally we'll also be interested in
  features like module-linking. New features would require either
  implementation work in binaryen or the binaryen-sys crate to support.

* We have 4-5 fuzz-bugs right now related to timeouts simply in
  generating a module for wasmtime to fuzz. One investigation along
  these lines in the past revealed a bug in binaryen itself, and in any
  case these bugs would otherwise need to get investigated, reported,
  and possibly fixed ourselves in upstream binaryen.

Overall I'm not sure at this point if maintaining binaryen fuzzing is
worth it with the advent of `wasm-smith` which has similar goals for
wasm module generation, but is much more readily maintainable on our
end.

Additonally in this commit I've added a fuzzer for wasm-smith's
`SwarmConfig`-based fuzzer which should expand the coverage of tested
modules.

Closes #2163
This commit is contained in:
Alex Crichton
2020-10-29 10:02:59 -05:00
committed by GitHub
parent 3cd9d52d32
commit b73b831892
10 changed files with 41 additions and 104 deletions

View File

@@ -146,7 +146,7 @@ jobs:
- run: cargo install cargo-fuzz --vers "^0.8" - run: cargo install cargo-fuzz --vers "^0.8"
- run: cargo fetch - run: cargo fetch
working-directory: ./fuzz working-directory: ./fuzz
- run: cargo fuzz build --dev --features binaryen - run: cargo fuzz build --dev
rebuild_peephole_optimizers: rebuild_peephole_optimizers:
name: Rebuild Peephole Optimizers name: Rebuild Peephole Optimizers

27
Cargo.lock generated
View File

@@ -115,27 +115,6 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
[[package]]
name = "binaryen"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a51ad23b3c7ab468d9daa948201921879ef0052e561c250fd0b326e6f000f2dd"
dependencies = [
"binaryen-sys",
]
[[package]]
name = "binaryen-sys"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df5829a7c89f7827e58866704e4dfdf48a635d73c6e5449c1a8a0ba5a319d28a"
dependencies = [
"cc",
"cmake",
"heck",
"regex",
]
[[package]] [[package]]
name = "bincode" name = "bincode"
version = "1.3.1" version = "1.3.1"
@@ -2294,9 +2273,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-smith" name = "wasm-smith"
version = "0.1.5" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ff896bbe4adf62d6a909708c34db3ad94ce2103daa9673f64fe15e60ba70dad" checksum = "f372c777fcc75bbad237aa0b14e380cfddb3680f42c0584f1f0681542f8559b7"
dependencies = [ dependencies = [
"arbitrary", "arbitrary",
"leb128", "leb128",
@@ -2497,10 +2476,10 @@ version = "0.19.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arbitrary", "arbitrary",
"binaryen",
"env_logger", "env_logger",
"log", "log",
"rayon", "rayon",
"wasm-smith",
"wasmparser 0.63.0", "wasmparser 0.63.0",
"wasmprinter", "wasmprinter",
"wasmtime", "wasmtime",

View File

@@ -9,7 +9,6 @@ version = "0.19.0"
[dependencies] [dependencies]
anyhow = "1.0.22" anyhow = "1.0.22"
arbitrary = { version = "0.4.1", features = ["derive"] } arbitrary = { version = "0.4.1", features = ["derive"] }
binaryen = { version = "0.10.0", optional = true }
env_logger = "0.7.1" env_logger = "0.7.1"
log = "0.4.8" log = "0.4.8"
rayon = "1.2.1" rayon = "1.2.1"
@@ -17,6 +16,7 @@ wasmparser = "0.63.0"
wasmprinter = "0.2.10" wasmprinter = "0.2.10"
wasmtime = { path = "../wasmtime" } wasmtime = { path = "../wasmtime" }
wasmtime-wast = { path = "../wast" } wasmtime-wast = { path = "../wast" }
wasm-smith = "0.1.9"
[dev-dependencies] [dev-dependencies]
wat = "1.0.23" wat = "1.0.23"

View File

@@ -8,55 +8,12 @@
//! wrapper over an external tool, such that the wrapper implements the //! wrapper over an external tool, such that the wrapper implements the
//! `Arbitrary` trait for the wrapped external tool. //! `Arbitrary` trait for the wrapped external tool.
#[cfg(feature = "binaryen")]
pub mod api; pub mod api;
pub mod table_ops; pub mod table_ops;
use arbitrary::{Arbitrary, Unstructured}; use arbitrary::{Arbitrary, Unstructured};
/// A Wasm test case generator that is powered by Binaryen's `wasm-opt -ttf`.
#[derive(Clone)]
#[cfg(feature = "binaryen")]
pub struct WasmOptTtf {
/// The raw, encoded Wasm bytes.
pub wasm: Vec<u8>,
}
#[cfg(feature = "binaryen")]
impl std::fmt::Debug for WasmOptTtf {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"WasmOptTtf {{ wasm: wat::parse_str(r###\"\n{}\n\"###).unwrap() }}",
wasmprinter::print_bytes(&self.wasm).expect("valid wasm should always disassemble")
)
}
}
#[cfg(feature = "binaryen")]
impl Arbitrary for WasmOptTtf {
fn arbitrary(input: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
crate::init_fuzzing();
let seed: Vec<u8> = Arbitrary::arbitrary(input)?;
let module = binaryen::tools::translate_to_fuzz_mvp(&seed);
let wasm = module.write();
Ok(WasmOptTtf { wasm })
}
fn arbitrary_take_rest(input: arbitrary::Unstructured) -> arbitrary::Result<Self> {
crate::init_fuzzing();
let seed: Vec<u8> = Arbitrary::arbitrary_take_rest(input)?;
let module = binaryen::tools::translate_to_fuzz_mvp(&seed);
let wasm = module.write();
Ok(WasmOptTtf { wasm })
}
fn size_hint(depth: usize) -> (usize, Option<usize>) {
<Vec<u8> as Arbitrary>::size_hint(depth)
}
}
/// A description of configuration options that we should do differential /// A description of configuration options that we should do differential
/// testing between. /// testing between.
#[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)]

View File

@@ -17,6 +17,7 @@
use arbitrary::{Arbitrary, Unstructured}; use arbitrary::{Arbitrary, Unstructured};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::mem; use std::mem;
use wasm_smith::Module;
use wasmparser::*; use wasmparser::*;
#[derive(Arbitrary, Debug)] #[derive(Arbitrary, Debug)]
@@ -31,7 +32,7 @@ struct Swarm {
} }
/// A call to one of Wasmtime's public APIs. /// A call to one of Wasmtime's public APIs.
#[derive(Arbitrary, Clone, Debug)] #[derive(Arbitrary, Debug)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum ApiCall { pub enum ApiCall {
ConfigNew, ConfigNew,
@@ -39,7 +40,7 @@ pub enum ApiCall {
ConfigInterruptable(bool), ConfigInterruptable(bool),
EngineNew, EngineNew,
StoreNew, StoreNew,
ModuleNew { id: usize, wasm: super::WasmOptTtf }, ModuleNew { id: usize, wasm: Module },
ModuleDrop { id: usize }, ModuleDrop { id: usize },
InstanceNew { id: usize, module: usize }, InstanceNew { id: usize, module: usize },
InstanceDrop { id: usize }, InstanceDrop { id: usize },
@@ -106,8 +107,9 @@ impl Arbitrary for ApiCalls {
if swarm.module_new { if swarm.module_new {
choices.push(|input, scope| { choices.push(|input, scope| {
let id = scope.next_id(); let id = scope.next_id();
let wasm = super::WasmOptTtf::arbitrary(input)?; let mut wasm = Module::arbitrary(input)?;
let predicted_rss = predict_rss(&wasm.wasm).unwrap_or(0); wasm.ensure_termination(1000);
let predicted_rss = predict_rss(&wasm.to_bytes()).unwrap_or(0);
scope.modules.insert(id, predicted_rss); scope.modules.insert(id, predicted_rss);
Ok(ModuleNew { id, wasm }) Ok(ModuleNew { id, wasm })
}); });
@@ -173,7 +175,7 @@ impl Arbitrary for ApiCalls {
// We can generate arbitrary `WasmOptTtf` instances, which have // We can generate arbitrary `WasmOptTtf` instances, which have
// no upper bound on the number of bytes they consume. This sets // no upper bound on the number of bytes they consume. This sets
// the upper bound to `None`. // the upper bound to `None`.
<super::WasmOptTtf as Arbitrary>::size_hint(depth), <Module as Arbitrary>::size_hint(depth),
) )
}) })
} }

View File

@@ -119,9 +119,8 @@ pub fn compile(wasm: &[u8], strategy: Strategy) {
/// exports. Modulo OOM, non-canonical NaNs, and usage of Wasm features that are /// exports. Modulo OOM, non-canonical NaNs, and usage of Wasm features that are
/// or aren't enabled for different configs, we should get the same results when /// or aren't enabled for different configs, we should get the same results when
/// we call the exported functions for all of our different configs. /// we call the exported functions for all of our different configs.
#[cfg(feature = "binaryen")]
pub fn differential_execution( pub fn differential_execution(
ttf: &crate::generators::WasmOptTtf, module: &wasm_smith::Module,
configs: &[crate::generators::DifferentialConfig], configs: &[crate::generators::DifferentialConfig],
) { ) {
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@@ -144,13 +143,14 @@ pub fn differential_execution(
}; };
let mut export_func_results: HashMap<String, Result<Box<[Val]>, Trap>> = Default::default(); let mut export_func_results: HashMap<String, Result<Box<[Val]>, Trap>> = Default::default();
log_wasm(&ttf.wasm); let wasm = module.to_bytes();
log_wasm(&wasm);
for config in &configs { for config in &configs {
let engine = Engine::new(config); let engine = Engine::new(config);
let store = Store::new(&engine); let store = Store::new(&engine);
let module = match Module::new(&engine, &ttf.wasm) { let module = match Module::new(&engine, &wasm) {
Ok(module) => module, Ok(module) => module,
// The module might rely on some feature that our config didn't // The module might rely on some feature that our config didn't
// enable or something like that. // enable or something like that.
@@ -278,7 +278,6 @@ pub fn differential_execution(
} }
/// Invoke the given API calls. /// Invoke the given API calls.
#[cfg(feature = "binaryen")]
pub fn make_api_calls(api: crate::generators::api::ApiCalls) { pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
use crate::generators::api::ApiCall; use crate::generators::api::ApiCall;
use std::collections::HashMap; use std::collections::HashMap;
@@ -323,8 +322,9 @@ pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
ApiCall::ModuleNew { id, wasm } => { ApiCall::ModuleNew { id, wasm } => {
log::debug!("creating module: {}", id); log::debug!("creating module: {}", id);
log_wasm(&wasm.wasm); let wasm = wasm.to_bytes();
let module = match Module::new(engine.as_ref().unwrap(), &wasm.wasm) { log_wasm(&wasm);
let module = match Module::new(engine.as_ref().unwrap(), &wasm) {
Ok(m) => m, Ok(m) => m,
Err(_) => continue, Err(_) => continue,
}; };

View File

@@ -31,26 +31,17 @@ path = "fuzz_targets/instantiate.rs"
test = false test = false
doc = false doc = false
[[bin]]
name = "instantiate_translated"
path = "fuzz_targets/instantiate_translated.rs"
test = false
doc = false
required-features = ["binaryen"]
[[bin]] [[bin]]
name = "api_calls" name = "api_calls"
path = "fuzz_targets/api_calls.rs" path = "fuzz_targets/api_calls.rs"
test = false test = false
doc = false doc = false
required-features = ["binaryen"]
[[bin]] [[bin]]
name = "differential" name = "differential"
path = "fuzz_targets/differential.rs" path = "fuzz_targets/differential.rs"
test = false test = false
doc = false doc = false
required-features = ["binaryen"]
[[bin]] [[bin]]
name = "spectests" name = "spectests"
@@ -99,15 +90,18 @@ test = false
doc = false doc = false
required-features = ["peepmatic-fuzzing"] required-features = ["peepmatic-fuzzing"]
[features]
binaryen = ["wasmtime-fuzzing/binaryen"]
[[bin]] [[bin]]
name = "instantiate-wasm-smith" name = "instantiate-wasm-smith"
path = "fuzz_targets/instantiate-wasm-smith.rs" path = "fuzz_targets/instantiate-wasm-smith.rs"
test = false test = false
doc = false doc = false
[[bin]]
name = "instantiate-swarm"
path = "fuzz_targets/instantiate-swarm.rs"
test = false
doc = false
[[bin]] [[bin]]
name = "instantiate-maybe-invalid" name = "instantiate-maybe-invalid"
path = "fuzz_targets/instantiate-maybe-invalid.rs" path = "fuzz_targets/instantiate-maybe-invalid.rs"

View File

@@ -6,8 +6,9 @@ use wasmtime_fuzzing::{generators, oracles};
fuzz_target!(|data: ( fuzz_target!(|data: (
generators::DifferentialConfig, generators::DifferentialConfig,
generators::DifferentialConfig, generators::DifferentialConfig,
generators::WasmOptTtf wasm_smith::Module,
)| { )| {
let (lhs, rhs, wasm) = data; let (lhs, rhs, mut wasm) = data;
wasm.ensure_termination(1000);
oracles::differential_execution(&wasm, &[lhs, rhs]); oracles::differential_execution(&wasm, &[lhs, rhs]);
}); });

View File

@@ -0,0 +1,13 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use std::time::Duration;
use wasm_smith::{ConfiguredModule, SwarmConfig};
use wasmtime::Strategy;
use wasmtime_fuzzing::oracles;
fuzz_target!(|module: ConfiguredModule<SwarmConfig>| {
let mut cfg = wasmtime_fuzzing::fuzz_default_config(Strategy::Auto).unwrap();
cfg.wasm_multi_memory(true);
oracles::instantiate_with_config(&module.to_bytes(), cfg, Some(Duration::from_secs(20)));
});

View File

@@ -1,9 +0,0 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use wasmtime::Strategy;
use wasmtime_fuzzing::{generators, oracles};
fuzz_target!(|data: generators::WasmOptTtf| {
oracles::instantiate(&data.wasm, Strategy::Auto);
});