diff --git a/crates/fuzzing/src/generators.rs b/crates/fuzzing/src/generators.rs index 10eaa86844..295ea29a41 100644 --- a/crates/fuzzing/src/generators.rs +++ b/crates/fuzzing/src/generators.rs @@ -12,29 +12,10 @@ pub mod api; pub mod table_ops; +use anyhow::Result; use arbitrary::{Arbitrary, Unstructured}; - -/// A description of configuration options that we should do differential -/// testing between. -#[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] -pub struct DifferentialConfig { - opt_level: OptLevel, - force_jump_veneers: bool, -} - -impl DifferentialConfig { - /// Convert this differential fuzzing config into a `wasmtime::Config`. - pub fn to_wasmtime_config(&self) -> anyhow::Result { - let mut config = crate::fuzz_default_config(wasmtime::Strategy::Cranelift)?; - config.cranelift_opt_level(self.opt_level.to_wasmtime()); - if self.force_jump_veneers { - unsafe { - config.cranelift_flag_set("wasmtime_linkopt_force_jump_veneer", "true")?; - } - } - Ok(config) - } -} +use std::sync::Arc; +use wasmtime::{LinearMemory, MemoryCreator, MemoryType}; #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] enum OptLevel { @@ -54,7 +35,7 @@ impl OptLevel { } /// Implementation of generating a `wasmtime::Config` arbitrarily -#[derive(Arbitrary, Debug)] +#[derive(Arbitrary, Debug, Eq, Hash, PartialEq)] pub struct Config { opt_level: OptLevel, debug_info: bool, @@ -62,13 +43,29 @@ pub struct Config { interruptable: bool, #[allow(missing_docs)] pub consume_fuel: bool, + memory_config: MemoryConfig, + force_jump_veneers: bool, +} - // Note that we use 32-bit values here to avoid blowing the 64-bit address - // space by requesting ungodly-large sizes/guards. - static_memory_maximum_size: Option, - static_memory_guard_size: Option, - dynamic_memory_guard_size: Option, - guard_before_linear_memory: bool, +#[derive(Arbitrary, Debug, Eq, Hash, PartialEq)] +enum MemoryConfig { + /// Configuration for linear memories which correspond to normal + /// configuration settings in `wasmtime` itself. This will tweak various + /// parameters about static/dynamic memories. + /// + /// Note that we use 32-bit values here to avoid blowing the 64-bit address + /// space by requesting ungodly-large sizes/guards. + Normal { + static_memory_maximum_size: Option, + static_memory_guard_size: Option, + dynamic_memory_guard_size: Option, + guard_before_linear_memory: bool, + }, + + /// Configuration to force use of a linear memory that's unaligned at its + /// base address to force all wasm addresses to be unaligned at the hardware + /// level, even if the wasm itself correctly aligns everything internally. + CustomUnaligned, } impl Config { @@ -76,18 +73,102 @@ impl Config { pub fn to_wasmtime(&self) -> wasmtime::Config { let mut cfg = crate::fuzz_default_config(wasmtime::Strategy::Auto).unwrap(); cfg.debug_info(self.debug_info) - .static_memory_maximum_size(self.static_memory_maximum_size.unwrap_or(0).into()) - .static_memory_guard_size(self.static_memory_guard_size.unwrap_or(0).into()) - .dynamic_memory_guard_size(self.dynamic_memory_guard_size.unwrap_or(0).into()) - .guard_before_linear_memory(self.guard_before_linear_memory) .cranelift_nan_canonicalization(self.canonicalize_nans) .cranelift_opt_level(self.opt_level.to_wasmtime()) .interruptable(self.interruptable) .consume_fuel(self.consume_fuel); + + if self.force_jump_veneers { + unsafe { + cfg.cranelift_flag_set("wasmtime_linkopt_force_jump_veneer", "true") + .unwrap(); + } + } + + match &self.memory_config { + MemoryConfig::Normal { + static_memory_maximum_size, + static_memory_guard_size, + dynamic_memory_guard_size, + guard_before_linear_memory, + } => { + cfg.static_memory_maximum_size(static_memory_maximum_size.unwrap_or(0).into()) + .static_memory_guard_size(static_memory_guard_size.unwrap_or(0).into()) + .dynamic_memory_guard_size(dynamic_memory_guard_size.unwrap_or(0).into()) + .guard_before_linear_memory(*guard_before_linear_memory); + } + MemoryConfig::CustomUnaligned => { + cfg.with_host_memory(Arc::new(UnalignedMemoryCreator)) + .static_memory_maximum_size(0) + .dynamic_memory_guard_size(0) + .static_memory_guard_size(0) + .guard_before_linear_memory(false); + } + } return cfg; } } +struct UnalignedMemoryCreator; + +unsafe impl MemoryCreator for UnalignedMemoryCreator { + fn new_memory( + &self, + _ty: MemoryType, + minimum: usize, + maximum: Option, + reserved_size_in_bytes: Option, + guard_size_in_bytes: usize, + ) -> Result, String> { + assert_eq!(guard_size_in_bytes, 0); + assert!(reserved_size_in_bytes.is_none() || reserved_size_in_bytes == Some(0)); + Ok(Box::new(UnalignedMemory { + src: vec![0; minimum + 1], + maximum, + })) + } +} + +/// A custom "linear memory allocator" for wasm which only works with the +/// "dynamic" mode of configuration where wasm always does explicit bounds +/// checks. +/// +/// This memory attempts to always use unaligned host addresses for the base +/// address of linear memory with wasm. This means that all jit loads/stores +/// should be unaligned, which is a "big hammer way" of testing that all our JIT +/// code works with unaligned addresses since alignment is not required for +/// correctness in wasm itself. +struct UnalignedMemory { + /// This memory is always one byte larger than the actual size of linear + /// memory. + src: Vec, + maximum: Option, +} + +unsafe impl LinearMemory for UnalignedMemory { + fn byte_size(&self) -> usize { + // Chop off the extra byte reserved for the true byte size of this + // linear memory. + self.src.len() - 1 + } + + fn maximum_byte_size(&self) -> Option { + self.maximum + } + + fn grow_to(&mut self, new_size: usize) -> Result<()> { + // Make sure to allocate an extra byte for our "unalignment" + self.src.resize(new_size + 1, 0); + Ok(()) + } + + fn as_ptr(&self) -> *mut u8 { + // Return our allocated memory, offset by one, so that the base address + // of memory is always unaligned. + self.src[1..].as_ptr() as *mut _ + } +} + include!(concat!(env!("OUT_DIR"), "/spectests.rs")); /// A spec test from the upstream wast testsuite, arbitrarily chosen from the diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs index 5483c9876b..8d2fd334c7 100644 --- a/crates/fuzzing/src/oracles.rs +++ b/crates/fuzzing/src/oracles.rs @@ -238,7 +238,7 @@ pub fn compile(wasm: &[u8], strategy: Strategy) { /// we call the exported functions for all of our different configs. pub fn differential_execution( module: &crate::generators::GeneratedModule, - configs: &[crate::generators::DifferentialConfig], + configs: &[crate::generators::Config], ) { use std::collections::{HashMap, HashSet}; @@ -252,18 +252,13 @@ pub fn differential_execution( return; } - let configs: Vec<_> = match configs.iter().map(|c| c.to_wasmtime_config()).collect() { - Ok(cs) => cs, - // If the config is trying to use something that was turned off at - // compile time just continue to the next fuzz input. - Err(_) => return, - }; - + let configs: Vec<_> = configs.iter().map(|c| (c.to_wasmtime(), c)).collect(); let mut export_func_results: HashMap, Trap>> = Default::default(); let wasm = module.module.to_bytes(); log_wasm(&wasm); - for mut config in configs { + for (mut config, fuzz_config) in configs { + log::debug!("fuzz config: {:?}", fuzz_config); // Disable module linking since it isn't enabled by default for // `GeneratedModule` but is enabled by default for our fuzz config. // Since module linking is currently a breaking change this is required @@ -272,6 +267,9 @@ pub fn differential_execution( let engine = Engine::new(&config).unwrap(); let mut store = create_store(&engine); + if fuzz_config.consume_fuel { + store.add_fuel(u64::max_value()).unwrap(); + } let module = Module::new(&engine, &wasm).unwrap(); @@ -293,10 +291,7 @@ pub fn differential_execution( }) .collect::>(); for (name, f) in exports { - // Always call the hang limit initializer first, so that we don't - // infinite loop when calling another export. - init_hang_limit(&mut store, instance); - + log::debug!("invoke export {:?}", name); let ty = f.ty(&store); let params = dummy::dummy_values(ty.params()); let mut results = vec![Val::I32(0); ty.results().len()]; @@ -312,17 +307,6 @@ pub fn differential_execution( } } - fn init_hang_limit(store: &mut Store, instance: Instance) { - match instance.get_export(&mut *store, "hangLimitInitializer") { - None => return, - Some(Extern::Func(f)) => { - f.call(store, &[], &mut []) - .expect("initializing the hang limit should not fail"); - } - Some(_) => panic!("unexpected hangLimitInitializer export"), - } - } - fn assert_same_export_func_result( lhs: &Result, Trap>, rhs: &Result, Trap>, @@ -337,7 +321,11 @@ pub fn differential_execution( }; match (lhs, rhs) { - (Err(_), Err(_)) => {} + (Err(a), Err(b)) => { + if a.trap_code() != b.trap_code() { + fail(); + } + } (Ok(lhs), Ok(rhs)) => { if lhs.len() != rhs.len() { fail(); diff --git a/fuzz/fuzz_targets/differential.rs b/fuzz/fuzz_targets/differential.rs index fd52cbc1f3..ea6092af8f 100644 --- a/fuzz/fuzz_targets/differential.rs +++ b/fuzz/fuzz_targets/differential.rs @@ -4,8 +4,8 @@ use libfuzzer_sys::fuzz_target; use wasmtime_fuzzing::{generators, oracles}; fuzz_target!(|data: ( - generators::DifferentialConfig, - generators::DifferentialConfig, + generators::Config, + generators::Config, generators::GeneratedModule, )| { let (lhs, rhs, mut wasm) = data;