differential fuzzer

This commit is contained in:
Chris Fallin 2021-12-24 17:03:13 -08:00
parent bace89cd6b
commit 3f8ead1485
2 changed files with 67 additions and 38 deletions

View file

@ -14,8 +14,8 @@ libfuzzer-sys = "0.4"
wasm-smith = "0.8" wasm-smith = "0.8"
env_logger = "0.9" env_logger = "0.9"
log = "0.4" log = "0.4"
wasmi = "0.10"
wasmparser = "0.81" wasmparser = "0.81"
wasmtime = "0.32"
[dependencies.waffle] [dependencies.waffle]
path = ".." path = ".."

View file

@ -3,8 +3,9 @@ use libfuzzer_sys::{arbitrary, fuzz_target};
use waffle::Module; use waffle::Module;
fn has_loop(bytes: &[u8]) -> bool { fn has_loop_or_no_start(bytes: &[u8]) -> bool {
let parser = wasmparser::Parser::new(0); let parser = wasmparser::Parser::new(0);
let mut has_start = false;
for payload in parser.parse_all(bytes) { for payload in parser.parse_all(bytes) {
match payload.unwrap() { match payload.unwrap() {
wasmparser::Payload::CodeSectionEntry(body) => { wasmparser::Payload::CodeSectionEntry(body) => {
@ -12,16 +13,25 @@ fn has_loop(bytes: &[u8]) -> bool {
let op = op.unwrap(); let op = op.unwrap();
match op { match op {
wasmparser::Operator::Loop { .. } => { wasmparser::Operator::Loop { .. } => {
// Disallow direct loops.
return true;
}
wasmparser::Operator::Call { .. }
| wasmparser::Operator::CallIndirect { .. } => {
// Disallow recursion.
return true; return true;
} }
_ => {} _ => {}
} }
} }
} }
wasmparser::Payload::StartSection { .. } => {
has_start = true;
}
_ => {} _ => {}
} }
} }
false !has_start
} }
#[derive(Debug)] #[derive(Debug)]
@ -76,6 +86,9 @@ impl wasm_smith::Config for Config {
fn canonicalize_nans(&self) -> bool { fn canonicalize_nans(&self) -> bool {
true true
} }
fn max_memory_pages(&self, _is_64: bool) -> u64 {
1
}
} }
fuzz_target!(|module: wasm_smith::ConfiguredModule<Config>| { fuzz_target!(|module: wasm_smith::ConfiguredModule<Config>| {
@ -84,17 +97,19 @@ fuzz_target!(|module: wasm_smith::ConfiguredModule<Config>| {
let orig_bytes = module.module.to_bytes(); let orig_bytes = module.module.to_bytes();
if has_loop(&orig_bytes[..]) { if has_loop_or_no_start(&orig_bytes[..]) {
log::debug!("has a loop; discarding fuzz run"); log::debug!(
"has a loop or no start; discarding fuzz run. Body:\n{:?}",
module
);
return; return;
} }
let orig_wasmi_module = let engine = wasmtime::Engine::default();
wasmi::Module::from_buffer(&orig_bytes[..]).expect("failed to parse original wasm"); let mut store = wasmtime::Store::new(&engine, ());
let orig_instance = let orig_module =
wasmi::ModuleInstance::new(&orig_wasmi_module, &wasmi::ImportsBuilder::default()) wasmtime::Module::new(&engine, &orig_bytes[..]).expect("failed to parse original wasm");
.expect("cannot instantiate original wasm") let orig_instance = wasmtime::Instance::new(&mut store, &orig_module, &[]);
.run_start(&mut wasmi::NopExternals);
let orig_instance = match orig_instance { let orig_instance = match orig_instance {
Ok(orig_instance) => orig_instance, Ok(orig_instance) => orig_instance,
Err(e) => { Err(e) => {
@ -106,34 +121,48 @@ fuzz_target!(|module: wasm_smith::ConfiguredModule<Config>| {
let parsed_module = Module::from_wasm_bytes(&orig_bytes[..]).unwrap(); let parsed_module = Module::from_wasm_bytes(&orig_bytes[..]).unwrap();
let roundtrip_bytes = parsed_module.to_wasm_bytes(); let roundtrip_bytes = parsed_module.to_wasm_bytes();
let roundtrip_wasmi_module = let roundtrip_module = wasmtime::Module::new(&engine, &roundtrip_bytes[..])
wasmi::Module::from_buffer(&roundtrip_bytes).expect("failed to parse roundtripped wasm"); .expect("failed to parse roundtripped wasm");
let roundtrip_instance = let roundtrip_instance = wasmtime::Instance::new(&mut store, &roundtrip_module, &[])
wasmi::ModuleInstance::new(&roundtrip_wasmi_module, &wasmi::ImportsBuilder::default()) .expect("cannot instantiate roundtripped wasm");
.expect("cannot instantiate roundtripped wasm")
.run_start(&mut wasmi::NopExternals)
.expect("cannot run start on original wasm");
// Ensure globals are equal. // Ensure exports are equal.
assert_eq!(
orig_instance.globals().len(), let a_globals: Vec<_> = orig_instance
roundtrip_instance.globals().len() .exports(&mut store)
); .filter_map(|e| e.into_global())
for (a, b) in orig_instance .collect();
.globals() let a_globals: Vec<wasmtime::Val> = a_globals.into_iter().map(|g| g.get(&mut store)).collect();
.iter() let a_mems: Vec<wasmtime::Memory> = orig_instance
.zip(roundtrip_instance.globals().iter()) .exports(&mut store)
{ .filter_map(|e| e.into_memory())
match (a.get(), b.get()) { .collect();
(wasmi::RuntimeValue::I32(a), wasmi::RuntimeValue::I32(b)) => assert_eq!(a, b),
(wasmi::RuntimeValue::I64(a), wasmi::RuntimeValue::I64(b)) => assert_eq!(a, b), let b_globals: Vec<_> = roundtrip_instance
(wasmi::RuntimeValue::F32(a), wasmi::RuntimeValue::F32(b)) => { .exports(&mut store)
assert_eq!(a.to_bits(), b.to_bits()) .filter_map(|e| e.into_global())
} .collect();
(wasmi::RuntimeValue::F64(a), wasmi::RuntimeValue::F64(b)) => { let b_globals: Vec<wasmtime::Val> = b_globals.into_iter().map(|g| g.get(&mut store)).collect();
assert_eq!(a.to_bits(), b.to_bits()) let b_mems: Vec<wasmtime::Memory> = roundtrip_instance
} .exports(&mut store)
_ => panic!("mismatched types"), .filter_map(|e| e.into_memory())
.collect();
assert_eq!(a_globals.len(), b_globals.len());
for (a, b) in a_globals.into_iter().zip(b_globals.into_iter()) {
match (a, b) {
(wasmtime::Val::I32(a), wasmtime::Val::I32(b)) => assert_eq!(a, b),
(wasmtime::Val::I64(a), wasmtime::Val::I64(b)) => assert_eq!(a, b),
(wasmtime::Val::F32(a), wasmtime::Val::F32(b)) => assert_eq!(a, b),
(wasmtime::Val::F64(a), wasmtime::Val::F64(b)) => assert_eq!(a, b),
_ => panic!("mismatching types"),
} }
} }
assert_eq!(a_mems.len(), b_mems.len());
for (a, b) in a_mems.into_iter().zip(b_mems.into_iter()) {
let a_data = a.data(&store);
let b_data = b.data(&store);
assert_eq!(a_data, b_data);
}
}); });