Path: blob/main/fuzz/fuzz_targets/differential.rs
1691 views
#![no_main]12use libfuzzer_sys::arbitrary::{self, Result, Unstructured};3use libfuzzer_sys::fuzz_target;4use std::sync::atomic::AtomicUsize;5use std::sync::atomic::Ordering::SeqCst;6use std::sync::{Mutex, Once};7use wasmtime_fuzzing::generators::{Config, DiffValue, DiffValueType, SingleInstModule};8use wasmtime_fuzzing::oracles::diff_wasmtime::WasmtimeInstance;9use wasmtime_fuzzing::oracles::engine::{build_allowed_env_list, parse_env_list};10use wasmtime_fuzzing::oracles::{DiffEqResult, differential, engine, log_wasm};1112// Upper limit on the number of invocations for each WebAssembly function13// executed by this fuzz target.14const NUM_INVOCATIONS: usize = 5;1516// Only run once when the fuzz target loads.17static SETUP: Once = Once::new();1819// Environment-specified configuration for controlling the kinds of engines and20// modules used by this fuzz target. E.g.:21// - ALLOWED_ENGINES=wasmi,spec cargo +nightly fuzz run ...22// - ALLOWED_ENGINES=-v8 cargo +nightly fuzz run ...23// - ALLOWED_MODULES=single-inst cargo +nightly fuzz run ...24static ALLOWED_ENGINES: Mutex<Vec<Option<&str>>> = Mutex::new(vec![]);25static ALLOWED_MODULES: Mutex<Vec<Option<&str>>> = Mutex::new(vec![]);2627// Statistics about what's actually getting executed during fuzzing28static STATS: RuntimeStats = RuntimeStats::new();2930fuzz_target!(|data: &[u8]| {31SETUP.call_once(|| {32// To avoid a uncaught `SIGSEGV` due to signal handlers; see comments on33// `setup_ocaml_runtime`.34engine::setup_engine_runtimes();3536// Retrieve the configuration for this fuzz target from `ALLOWED_*`37// environment variables.38let allowed_engines = build_allowed_env_list(39parse_env_list("ALLOWED_ENGINES"),40&["wasmtime", "wasmi", "spec", "v8", "winch", "pulley"],41);42let allowed_modules = build_allowed_env_list(43parse_env_list("ALLOWED_MODULES"),44&["wasm-smith", "single-inst"],45);4647*ALLOWED_ENGINES.lock().unwrap() = allowed_engines;48*ALLOWED_MODULES.lock().unwrap() = allowed_modules;49});5051// Errors in `run` have to do with not enough input in `data`, which we52// ignore here since it doesn't affect how we'd like to fuzz.53let _ = execute_one(&data);54});5556fn execute_one(data: &[u8]) -> Result<()> {57wasmtime_fuzzing::init_fuzzing();58STATS.bump_attempts();5960let mut u = Unstructured::new(data);6162// Generate a Wasmtime and module configuration and update its settings63// initially to be suitable for differential execution where the generated64// wasm will behave the same in two different engines. This will get further65// refined below.66let mut config: Config = u.arbitrary()?;67config.set_differential_config();6869let allowed_engines = ALLOWED_ENGINES.lock().unwrap();70let allowed_modules = ALLOWED_MODULES.lock().unwrap();7172// Choose an engine that Wasmtime will be differentially executed against.73// The chosen engine is then created, which might update `config`, and74// returned as a trait object.75let lhs = match *u.choose(&allowed_engines)? {76Some(engine) => engine,77None => {78log::debug!("test case uses a runtime-disabled engine");79return Ok(());80}81};8283log::trace!("Building LHS engine");84let mut lhs = match engine::build(&mut u, lhs, &mut config)? {85Some(engine) => engine,86// The chosen engine does not have support compiled into the fuzzer,87// discard this test case.88None => return Ok(()),89};90log::debug!("lhs engine: {}", lhs.name());9192// Using the now-legalized module configuration generate the Wasm module;93// this is specified by either the ALLOWED_MODULES environment variable or a94// random selection between wasm-smith and single-inst.95let build_wasm_smith_module = |u: &mut Unstructured, config: &Config| -> Result<_> {96log::debug!("build wasm-smith with {config:?}");97STATS.wasm_smith_modules.fetch_add(1, SeqCst);98let module = config.generate(u, Some(1000))?;99Ok(module.to_bytes())100};101let build_single_inst_module = |u: &mut Unstructured, config: &Config| -> Result<_> {102log::debug!("build single-inst with {config:?}");103STATS.single_instruction_modules.fetch_add(1, SeqCst);104let module = SingleInstModule::new(u, &config.module_config)?;105Ok(module.to_bytes())106};107if allowed_modules.is_empty() {108panic!("unable to generate a module to fuzz against; check `ALLOWED_MODULES`")109}110let wasm = match *u.choose(&allowed_modules)? {111Some("wasm-smith") => build_wasm_smith_module(&mut u, &config)?,112Some("single-inst") => build_single_inst_module(&mut u, &config)?,113None => {114log::debug!("test case uses a runtime-disabled module strategy");115return Ok(());116}117_ => unreachable!(),118};119120log_wasm(&wasm);121122// Instantiate the generated wasm file in the chosen differential engine.123let lhs_instance = lhs.instantiate(&wasm);124STATS.bump_engine(lhs.name());125126// Always use Wasmtime as the second engine to instantiate within.127log::debug!("Building RHS Wasmtime");128let rhs_store = config.to_store();129let rhs_module = wasmtime::Module::new(rhs_store.engine(), &wasm).unwrap();130let rhs_instance = WasmtimeInstance::new(rhs_store, rhs_module);131132let (mut lhs_instance, mut rhs_instance) =133match DiffEqResult::new(&*lhs, lhs_instance, rhs_instance) {134// Both sides successful, continue below to invoking exports.135DiffEqResult::Success(l, r) => (l, r),136137// Both sides failed, or computation has diverged. In both cases this138// test case is done.139DiffEqResult::Poisoned | DiffEqResult::Failed => return Ok(()),140};141142// Call each exported function with different sets of arguments.143'outer: for (name, signature) in rhs_instance.exported_functions() {144let mut invocations = 0;145loop {146let arguments = match signature147.params()148.map(|ty| {149let ty = ty150.try_into()151.map_err(|_| arbitrary::Error::IncorrectFormat)?;152DiffValue::arbitrary_of_type(&mut u, ty)153})154.collect::<Result<Vec<_>>>()155{156Ok(args) => args,157// This function signature isn't compatible with differential158// fuzzing yet, try the next exported function in the meantime.159Err(_) => continue 'outer,160};161162let result_tys = match signature163.results()164.map(|ty| {165DiffValueType::try_from(ty).map_err(|_| arbitrary::Error::IncorrectFormat)166})167.collect::<Result<Vec<_>>>()168{169Ok(tys) => tys,170// This function signature isn't compatible with differential171// fuzzing yet, try the next exported function in the meantime.172Err(_) => continue 'outer,173};174175let ok = differential(176lhs_instance.as_mut(),177lhs.as_ref(),178&mut rhs_instance,179&name,180&arguments,181&result_tys,182)183.expect("failed to run differential evaluation");184185invocations += 1;186STATS.total_invocations.fetch_add(1, SeqCst);187188// If this differential execution has resulted in the two instances189// diverging in state we can't keep executing so don't execute any190// more functions.191if !ok {192break 'outer;193}194195// We evaluate the same function with different arguments until we196// Hit a predetermined limit or we run out of unstructured data--it197// does not make sense to re-evaluate the same arguments over and198// over.199if invocations > NUM_INVOCATIONS || u.is_empty() {200break;201}202}203}204205STATS.successes.fetch_add(1, SeqCst);206Ok(())207}208209#[derive(Default)]210struct RuntimeStats {211/// Total number of fuzz inputs processed212attempts: AtomicUsize,213214/// Number of times we've invoked engines215total_invocations: AtomicUsize,216217/// Number of times a fuzz input finished all the way to the end without any218/// sort of error (including `Arbitrary` errors)219successes: AtomicUsize,220221// Counters for which engine was chosen222wasmi: AtomicUsize,223v8: AtomicUsize,224spec: AtomicUsize,225wasmtime: AtomicUsize,226winch: AtomicUsize,227pulley: AtomicUsize,228229// Counters for which style of module is chosen230wasm_smith_modules: AtomicUsize,231single_instruction_modules: AtomicUsize,232}233234impl RuntimeStats {235const fn new() -> RuntimeStats {236RuntimeStats {237attempts: AtomicUsize::new(0),238total_invocations: AtomicUsize::new(0),239successes: AtomicUsize::new(0),240wasmi: AtomicUsize::new(0),241v8: AtomicUsize::new(0),242spec: AtomicUsize::new(0),243wasmtime: AtomicUsize::new(0),244winch: AtomicUsize::new(0),245pulley: AtomicUsize::new(0),246wasm_smith_modules: AtomicUsize::new(0),247single_instruction_modules: AtomicUsize::new(0),248}249}250251fn bump_attempts(&self) {252let attempts = self.attempts.fetch_add(1, SeqCst);253if attempts == 0 || attempts % 1_000 != 0 {254return;255}256let successes = self.successes.load(SeqCst);257println!(258"=== Execution rate ({} successes / {} attempted modules): {:.02}% ===",259successes,260attempts,261successes as f64 / attempts as f64 * 100f64,262);263264let v8 = self.v8.load(SeqCst);265let spec = self.spec.load(SeqCst);266let wasmi = self.wasmi.load(SeqCst);267let wasmtime = self.wasmtime.load(SeqCst);268let winch = self.winch.load(SeqCst);269let pulley = self.pulley.load(SeqCst);270let total = v8 + spec + wasmi + wasmtime + winch + pulley;271println!(272"\twasmi: {:.02}%, spec: {:.02}%, wasmtime: {:.02}%, v8: {:.02}%, \273winch: {:.02}, \274pulley: {:.02}%",275wasmi as f64 / total as f64 * 100f64,276spec as f64 / total as f64 * 100f64,277wasmtime as f64 / total as f64 * 100f64,278v8 as f64 / total as f64 * 100f64,279winch as f64 / total as f64 * 100f64,280pulley as f64 / total as f64 * 100f64,281);282283let wasm_smith = self.wasm_smith_modules.load(SeqCst);284let single_inst = self.single_instruction_modules.load(SeqCst);285let total = wasm_smith + single_inst;286println!(287"\twasm-smith: {:.02}%, single-inst: {:.02}%",288wasm_smith as f64 / total as f64 * 100f64,289single_inst as f64 / total as f64 * 100f64,290);291}292293fn bump_engine(&self, name: &str) {294match name {295"wasmi" => self.wasmi.fetch_add(1, SeqCst),296"wasmtime" => self.wasmtime.fetch_add(1, SeqCst),297"spec" => self.spec.fetch_add(1, SeqCst),298"v8" => self.v8.fetch_add(1, SeqCst),299"winch" => self.winch.fetch_add(1, SeqCst),300"pulley" => self.pulley.fetch_add(1, SeqCst),301_ => return,302};303}304}305306307