diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index b5e8748..4da48f2 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -11,11 +11,11 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4" -wasm-smith = "0.8" +wasm-smith = "0.202.0" env_logger = "0.9" log = "0.4" -wasmparser = "0.95" -wasmtime = "7.0" +wasmparser = "0.202.0" +wasmtime = "19.0" [dependencies.waffle] path = ".." diff --git a/fuzz/fuzz_targets/differential.rs b/fuzz/fuzz_targets/differential.rs index b72af67..4f86ec3 100644 --- a/fuzz/fuzz_targets/differential.rs +++ b/fuzz/fuzz_targets/differential.rs @@ -4,114 +4,110 @@ use std::sync::atomic::{AtomicU64, Ordering}; use waffle::{FrontendOptions, Module}; -fuzz_target!( - |module: wasm_smith::ConfiguredModule| { - let _ = env_logger::try_init(); - log::debug!("original module: {:?}", module.module); +fuzz_target!(|module: waffle::fuzzing::ArbitraryModule| { + let module = module.0; + let _ = env_logger::try_init(); + log::debug!("original module: {:?}", module); - let orig_bytes = module.module.to_bytes(); + let orig_bytes = module.to_bytes(); - if waffle::fuzzing::reject(&orig_bytes[..]) { - log::debug!("Discarding fuzz run. Body:\n{:?}", module); - return; - } else { - log::info!("body: {:?}", module); - } - - let mut config = wasmtime::Config::default(); - config.consume_fuel(true); - let engine = wasmtime::Engine::new(&config).unwrap(); - let orig_module = - wasmtime::Module::new(&engine, &orig_bytes[..]).expect("failed to parse original wasm"); - let mut orig_store = wasmtime::Store::new(&engine, ()); - orig_store.out_of_fuel_trap(); - orig_store.add_fuel(10000).unwrap(); - let orig_instance = wasmtime::Instance::new(&mut orig_store, &orig_module, &[]); - let orig_instance = match orig_instance { - Ok(orig_instance) => orig_instance, - Err(e) => { - log::info!("cannot run start on orig intsance ({:?}); discarding", e); - return; - } - }; - - let mut parsed_module = - Module::from_wasm_bytes(&orig_bytes[..], &FrontendOptions::default()).unwrap(); - parsed_module.expand_all_funcs().unwrap(); - parsed_module.per_func_body(|body| body.optimize()); - let roundtrip_bytes = parsed_module.to_wasm_bytes().unwrap(); - - if let Ok(filename) = std::env::var("FUZZ_DUMP_WASM") { - std::fs::write(format!("{}_orig.wasm", filename), &orig_bytes[..]).unwrap(); - std::fs::write(format!("{}_roundtrip.wasm", filename), &roundtrip_bytes[..]).unwrap(); - } - - let total = TOTAL.fetch_add(1, Ordering::Relaxed); - - let roundtrip_module = wasmtime::Module::new(&engine, &roundtrip_bytes[..]) - .expect("failed to parse roundtripped wasm"); - let mut roundtrip_store = wasmtime::Store::new(&engine, ()); - roundtrip_store.out_of_fuel_trap(); - // After roundtrip, fuel consumption rate may differ. That's fine; - // what matters is that it terminated above without a trap (hence - // halts in a reasonable time). - roundtrip_store.add_fuel(u64::MAX).unwrap(); - let roundtrip_instance = - wasmtime::Instance::new(&mut roundtrip_store, &roundtrip_module, &[]) - .expect("cannot instantiate roundtripped wasm"); - - // Ensure exports are equal. - - let a_globals: Vec<_> = orig_instance - .exports(&mut orig_store) - .filter_map(|e| e.into_global()) - .collect(); - let a_globals: Vec = a_globals - .into_iter() - .map(|g| g.get(&mut orig_store)) - .collect(); - let a_mems: Vec = orig_instance - .exports(&mut orig_store) - .filter_map(|e| e.into_memory()) - .collect(); - - let b_globals: Vec<_> = roundtrip_instance - .exports(&mut roundtrip_store) - .filter_map(|e| e.into_global()) - .collect(); - let b_globals: Vec = b_globals - .into_iter() - .map(|g| g.get(&mut roundtrip_store)) - .collect(); - let b_mems: Vec = roundtrip_instance - .exports(&mut roundtrip_store) - .filter_map(|e| e.into_memory()) - .collect(); - - log::info!("a_globals = {:?}", a_globals); - log::info!("b_globals = {:?}", b_globals); - - 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(&orig_store); - let b_data = b.data(&roundtrip_store); - assert_eq!(a_data, b_data); - } - - success(total); + if waffle::fuzzing::reject(&orig_bytes[..]) { + log::debug!("Discarding fuzz run. Body:\n{:?}", module); + return; + } else { + log::info!("body: {:?}", module); } -); + + let mut config = wasmtime::Config::default(); + config.consume_fuel(true); + let engine = wasmtime::Engine::new(&config).unwrap(); + let orig_module = + wasmtime::Module::new(&engine, &orig_bytes[..]).expect("failed to parse original wasm"); + let mut orig_store = wasmtime::Store::new(&engine, ()); + orig_store.set_fuel(10000).unwrap(); + let orig_instance = wasmtime::Instance::new(&mut orig_store, &orig_module, &[]); + let orig_instance = match orig_instance { + Ok(orig_instance) => orig_instance, + Err(e) => { + log::info!("cannot run start on orig intsance ({:?}); discarding", e); + return; + } + }; + + let mut parsed_module = + Module::from_wasm_bytes(&orig_bytes[..], &FrontendOptions::default()).unwrap(); + parsed_module.expand_all_funcs().unwrap(); + parsed_module.per_func_body(|body| body.optimize()); + let roundtrip_bytes = parsed_module.to_wasm_bytes().unwrap(); + + if let Ok(filename) = std::env::var("FUZZ_DUMP_WASM") { + std::fs::write(format!("{}_orig.wasm", filename), &orig_bytes[..]).unwrap(); + std::fs::write(format!("{}_roundtrip.wasm", filename), &roundtrip_bytes[..]).unwrap(); + } + + let total = TOTAL.fetch_add(1, Ordering::Relaxed); + + let roundtrip_module = wasmtime::Module::new(&engine, &roundtrip_bytes[..]) + .expect("failed to parse roundtripped wasm"); + let mut roundtrip_store = wasmtime::Store::new(&engine, ()); + // After roundtrip, fuel consumption rate may differ. That's fine; + // what matters is that it terminated above without a trap (hence + // halts in a reasonable time). + roundtrip_store.set_fuel(u64::MAX).unwrap(); + let roundtrip_instance = wasmtime::Instance::new(&mut roundtrip_store, &roundtrip_module, &[]) + .expect("cannot instantiate roundtripped wasm"); + + // Ensure exports are equal. + + let a_globals: Vec<_> = orig_instance + .exports(&mut orig_store) + .filter_map(|e| e.into_global()) + .collect(); + let a_globals: Vec = a_globals + .into_iter() + .map(|g| g.get(&mut orig_store)) + .collect(); + let a_mems: Vec = orig_instance + .exports(&mut orig_store) + .filter_map(|e| e.into_memory()) + .collect(); + + let b_globals: Vec<_> = roundtrip_instance + .exports(&mut roundtrip_store) + .filter_map(|e| e.into_global()) + .collect(); + let b_globals: Vec = b_globals + .into_iter() + .map(|g| g.get(&mut roundtrip_store)) + .collect(); + let b_mems: Vec = roundtrip_instance + .exports(&mut roundtrip_store) + .filter_map(|e| e.into_memory()) + .collect(); + + log::info!("a_globals = {:?}", a_globals); + log::info!("b_globals = {:?}", b_globals); + + 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(&orig_store); + let b_data = b.data(&roundtrip_store); + assert_eq!(a_data, b_data); + } + + success(total); +}); static TOTAL: AtomicU64 = AtomicU64::new(0); static SUCCESS: AtomicU64 = AtomicU64::new(0); diff --git a/fuzz/fuzz_targets/opt_diff.rs b/fuzz/fuzz_targets/opt_diff.rs index cee3e11..524bbc8 100644 --- a/fuzz/fuzz_targets/opt_diff.rs +++ b/fuzz/fuzz_targets/opt_diff.rs @@ -3,66 +3,65 @@ use libfuzzer_sys::fuzz_target; use waffle::{FrontendOptions, InterpContext, InterpResult, Module}; -fuzz_target!( - |module: wasm_smith::ConfiguredModule| { - let _ = env_logger::try_init(); - log::debug!("original module: {:?}", module.module); +fuzz_target!(|module: waffle::fuzzing::ArbitraryModule| { + let module = module.0; + let _ = env_logger::try_init(); + log::debug!("original module: {:?}", module); - let orig_bytes = module.module.to_bytes(); + let orig_bytes = module.to_bytes(); - if waffle::fuzzing::reject(&orig_bytes[..]) { - log::debug!("Discarding fuzz run. Body:\n{:?}", module); - return; - } else { - log::info!("body: {:?}", module); - } - - let mut parsed_module = - Module::from_wasm_bytes(&orig_bytes[..], &FrontendOptions::default()).unwrap(); - parsed_module.expand_all_funcs().unwrap(); - - let start = parsed_module.start_func.unwrap(); - - let mut orig_ctx = match InterpContext::new(&parsed_module) { - Ok(ctx) => ctx, - Err(e) => { - log::trace!("Rejecting due to instantiation error: {:?}", e); - return; - } - }; - orig_ctx.fuel = 10000; - - match orig_ctx.call(&parsed_module, start, &[]) { - InterpResult::OutOfFuel => { - // Silently reject. - log::trace!("Rejecting due to timeout in orig"); - return; - } - InterpResult::Trap(..) => { - // Silently reject. - log::trace!("Rejecting due to trap in orig"); - return; - } - InterpResult::Ok(_) => {} - ret => panic!("Bad result: {:?}", ret), - } - - let mut opt_module = parsed_module.clone(); - opt_module.per_func_body(|body| body.optimize()); - opt_module.per_func_body(|body| body.convert_to_max_ssa(None)); - - let mut opt_ctx = InterpContext::new(&opt_module).unwrap(); - // Allow a little leeway for opts to not actually optimize. - opt_ctx.fuel = 20000; - opt_ctx.call(&opt_module, start, &[]).ok().unwrap(); - - log::trace!( - "Orig ran in {} fuel; opt ran in {} fuel", - 10000 - orig_ctx.fuel, - 20000 - opt_ctx.fuel - ); - - assert_eq!(orig_ctx.memories, opt_ctx.memories); - assert_eq!(orig_ctx.globals, opt_ctx.globals); + if waffle::fuzzing::reject(&orig_bytes[..]) { + log::debug!("Discarding fuzz run. Body:\n{:?}", module); + return; + } else { + log::info!("body: {:?}", module); } -); + + let mut parsed_module = + Module::from_wasm_bytes(&orig_bytes[..], &FrontendOptions::default()).unwrap(); + parsed_module.expand_all_funcs().unwrap(); + + let start = parsed_module.start_func.unwrap(); + + let mut orig_ctx = match InterpContext::new(&parsed_module) { + Ok(ctx) => ctx, + Err(e) => { + log::trace!("Rejecting due to instantiation error: {:?}", e); + return; + } + }; + orig_ctx.fuel = 10000; + + match orig_ctx.call(&parsed_module, start, &[]) { + InterpResult::OutOfFuel => { + // Silently reject. + log::trace!("Rejecting due to timeout in orig"); + return; + } + InterpResult::Trap(..) => { + // Silently reject. + log::trace!("Rejecting due to trap in orig"); + return; + } + InterpResult::Ok(_) => {} + ret => panic!("Bad result: {:?}", ret), + } + + let mut opt_module = parsed_module.clone(); + opt_module.per_func_body(|body| body.optimize()); + opt_module.per_func_body(|body| body.convert_to_max_ssa(None)); + + let mut opt_ctx = InterpContext::new(&opt_module).unwrap(); + // Allow a little leeway for opts to not actually optimize. + opt_ctx.fuel = 20000; + opt_ctx.call(&opt_module, start, &[]).ok().unwrap(); + + log::trace!( + "Orig ran in {} fuel; opt ran in {} fuel", + 10000 - orig_ctx.fuel, + 20000 - opt_ctx.fuel + ); + + assert_eq!(orig_ctx.memories, opt_ctx.memories); + assert_eq!(orig_ctx.globals, opt_ctx.globals); +}); diff --git a/src/fuzzing.rs b/src/fuzzing.rs index 3b8e486..4b8a8b9 100644 --- a/src/fuzzing.rs +++ b/src/fuzzing.rs @@ -23,10 +23,10 @@ pub fn reject(bytes: &[u8]) -> bool { wasmparser::Payload::StartSection { .. } => { has_start = true; } - wasmparser::Payload::ExportSection(mut reader) => { - for _ in 0..reader.get_count() { - let e = reader.read().unwrap(); - match &e.kind { + wasmparser::Payload::ExportSection(reader) => { + for export in reader { + let export = export.unwrap(); + match &export.kind { &wasmparser::ExternalKind::Global => { num_globals += 1; } @@ -34,10 +34,10 @@ pub fn reject(bytes: &[u8]) -> bool { } } } - wasmparser::Payload::MemorySection(mut reader) => { - for _ in 0..reader.get_count() { - let m = reader.read().unwrap(); - if m.maximum.is_none() || m.maximum.unwrap() > 100 { + wasmparser::Payload::MemorySection(reader) => { + for mem in reader { + let mem = mem.unwrap(); + if mem.maximum.is_none() || mem.maximum.unwrap() > 100 { return true; } } @@ -53,59 +53,32 @@ pub fn reject(bytes: &[u8]) -> bool { false } +pub fn fuzzing_config() -> wasm_smith::Config { + wasm_smith::Config { + min_funcs: 1, + max_funcs: 1, + min_memories: 1, + max_memories: 1, + min_globals: 10, + max_globals: 10, + min_tables: 0, + max_tables: 0, + min_imports: 0, + max_imports: 0, + min_exports: 12, + max_exports: 12, + allow_start_export: true, + canonicalize_nans: true, + max_memory32_pages: 1, + ..Default::default() + } +} + #[derive(Debug)] -pub struct Config; +pub struct ArbitraryModule(pub wasm_smith::Module); -impl<'a> arbitrary::Arbitrary<'a> for Config { - fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - Ok(Config) - } -} - -impl wasm_smith::Config for Config { - fn min_funcs(&self) -> usize { - 1 - } - fn max_funcs(&self) -> usize { - 1 - } - fn min_memories(&self) -> u32 { - 1 - } - fn max_memories(&self) -> usize { - 1 - } - fn min_globals(&self) -> usize { - 10 - } - fn max_globals(&self) -> usize { - 10 - } - fn min_tables(&self) -> u32 { - 0 - } - fn max_tables(&self) -> usize { - 0 - } - fn min_imports(&self) -> usize { - 0 - } - fn max_imports(&self) -> usize { - 0 - } - fn min_exports(&self) -> usize { - 12 - } - fn max_exports(&self) -> usize { - 12 - } - fn allow_start_export(&self) -> bool { - true - } - fn canonicalize_nans(&self) -> bool { - true - } - fn max_memory_pages(&self, _is_64: bool) -> u64 { - 1 +impl<'a> arbitrary::Arbitrary<'a> for ArbitraryModule { + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + Ok(Self(wasm_smith::Module::new(fuzzing_config(), u)?)) } }