* [fuzz] Add `Module` enum, refactor `ModuleConfig` This change adds a way to create either a single-instruction module or a regular (big) `wasm-smith` module. It has some slight refactorings in preparation for the use of this new code. * [fuzz] Add `DiffValue` for differential evaluation In order to evaluate functions with randomly-generated values, we needed a common way to generate these values. Using the Wasmtime `Val` type is not great because we would like to be able to implement various traits on the new value type, e.g., to convert `Into` and `From` boxed values of other engines we differentially fuzz against. This new type, `DiffValue`, gives us a common ground for all the conversions and comparisons between the other engine types. * [fuzz] Add interface for differential engines In order to randomly choose an engine to fuzz against, we expect all of the engines to meet a common interface. The traits in this commit allow us to instantiate a module from its binary form, evaluate exported functions, and (possibly) hash the exported items of the instance. This change has some missing pieces, though: - the `wasm-spec-interpreter` needs some work to be able to create instances, evaluate a function by name, and expose exported items - the `v8` engine is not implemented yet due to the complexity of its Rust lifetimes * [fuzz] Use `ModuleFeatures` instead of existing configuration When attempting to use both wasm-smith and single-instruction modules, there is a mismatch in how we communicate what an engine must be able to support. In the first case, we could use the `ModuleConfig`, a wrapper for wasm-smith's `SwarmConfig`, but single-instruction modules do not have a `SwarmConfig`--the many options simply don't apply. Here, we instead add `ModuleFeatures` and adapt a `ModuleConfig` to that. `ModuleFeatures` then becomes the way to communicate what features an engine must support to evaluate functions in a module. * [fuzz] Add a new fuzz target using the meta-differential oracle This change adds the `differential_meta` target to the list of fuzz targets. I expect that sometime soon this could replace the other `differential*` targets, as it almost checks all the things those check. The major missing piece is that currently it only chooses single-instruction modules instead of also generating arbitrary modules using `wasm-smith`. Also, this change adds the concept of an ignorable error: some differential engines will choke with certain inputs (e.g., `wasmi` might have an old opcode mapping) which we do not want to flag as fuzz bugs. Here we wrap those errors in `DiffIgnoreError` and then use a new helper trait, `DiffIgnorable`, to downcast and inspect the `anyhow` error to only panic on non-ignorable errors; the ignorable errors are converted to one of the `arbitrary::Error` variants, which we already ignore. * [fuzz] Compare `DiffValue` NaNs more leniently Because arithmetic NaNs can contain arbitrary payload bits, checking that two differential executions should produce the same result should relax the comparison of the `F32` and `F64` types (and eventually `V128` as well... TODO). This change adds several considerations, however, so that in the future we make the comparison a bit stricter, e.g., re: canonical NaNs. This change, however, just matches the current logic used by other fuzz targets. * review: allow hashing mutate the instance state @alexcrichton requested that the interface be adapted to accommodate Wasmtime's API, in which even reading from an instance could trigger mutation of the store. * review: refactor where configurations are made compatible See @alexcrichton's [suggestion](https://github.com/bytecodealliance/wasmtime/pull/4515#discussion_r928974376). * review: convert `DiffValueType` using `TryFrom` See @alexcrichton's [comment](https://github.com/bytecodealliance/wasmtime/pull/4515#discussion_r928962394). * review: adapt target implementation to Wasmtime-specific RHS This change is joint work with @alexcrichton to adapt the structure of the fuzz target to his comments [here](https://github.com/bytecodealliance/wasmtime/pull/4515#pullrequestreview-1073247791). This change: - removes `ModuleFeatures` and the `Module` enum (for big and small modules) - upgrades `SingleInstModule` to filter out cases that are not valid for a given `ModuleConfig` - adds `DiffEngine::name()` - constructs each `DiffEngine` using a `ModuleConfig`, eliminating `DiffIgnoreError` completely - prints an execution rate to the `differential_meta` target Still TODO: - `get_exported_function_signatures` could be re-written in terms of the Wasmtime API instead `wasmparser` - the fuzzer crashes eventually, we think due to the signal handler interference between OCaml and Wasmtime - the spec interpreter has several cases that we skip for now but could be fuzzed with further work Co-authored-by: Alex Crichton <alex@alexcrichton.com> * fix: avoid SIGSEGV by explicitly initializing OCaml runtime first * review: use Wasmtime's API to retrieve exported functions Co-authored-by: Alex Crichton <alex@alexcrichton.com>
110 lines
4.1 KiB
Rust
110 lines
4.1 KiB
Rust
#![no_main]
|
|
|
|
use libfuzzer_sys::arbitrary::{Result, Unstructured};
|
|
use libfuzzer_sys::fuzz_target;
|
|
use std::sync::atomic::AtomicUsize;
|
|
use std::sync::atomic::Ordering::SeqCst;
|
|
use std::sync::Once;
|
|
use wasmtime_fuzzing::generators::{Config, DiffValue, SingleInstModule};
|
|
use wasmtime_fuzzing::oracles::diff_spec;
|
|
use wasmtime_fuzzing::oracles::diff_wasmtime::WasmtimeInstance;
|
|
use wasmtime_fuzzing::oracles::{differential, engine, log_wasm};
|
|
|
|
// Upper limit on the number of invocations for each WebAssembly function
|
|
// executed by this fuzz target.
|
|
const NUM_INVOCATIONS: usize = 5;
|
|
|
|
// Keep track of how many WebAssembly modules we actually executed (i.e. ran to
|
|
// completion) versus how many were tried.
|
|
static TOTAL_INVOCATIONS: AtomicUsize = AtomicUsize::new(0);
|
|
static TOTAL_SUCCESSES: AtomicUsize = AtomicUsize::new(0);
|
|
static TOTAL_ATTEMPTED: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
// The spec interpreter requires special one-time setup.
|
|
static SETUP: Once = Once::new();
|
|
|
|
fuzz_target!(|data: &[u8]| {
|
|
// To avoid a uncaught `SIGSEGV` due to signal handlers; see comments on
|
|
// `setup_ocaml_runtime`.
|
|
SETUP.call_once(|| diff_spec::setup_ocaml_runtime());
|
|
|
|
// Errors in `run` have to do with not enough input in `data`, which we
|
|
// ignore here since it doesn't affect how we'd like to fuzz.
|
|
drop(run(&data));
|
|
});
|
|
|
|
fn run(data: &[u8]) -> Result<()> {
|
|
let successes = TOTAL_SUCCESSES.load(SeqCst);
|
|
let attempts = TOTAL_ATTEMPTED.fetch_add(1, SeqCst);
|
|
if attempts > 1 && attempts % 1_000 == 0 {
|
|
println!("=== Execution rate ({} successes / {} attempted modules): {}% (total invocations: {}) ===",
|
|
successes,
|
|
attempts,
|
|
successes as f64 / attempts as f64 * 100f64,
|
|
TOTAL_INVOCATIONS.load(SeqCst)
|
|
);
|
|
}
|
|
|
|
let mut u = Unstructured::new(data);
|
|
let mut config: Config = u.arbitrary()?;
|
|
config.set_differential_config();
|
|
|
|
// Generate the Wasm module.
|
|
let wasm = if u.arbitrary()? {
|
|
// TODO figure out if this always eats up the rest of the unstructured;
|
|
// can we limit the number of instructions/functions.
|
|
let module = config.generate(&mut u, Some(1000))?;
|
|
module.to_bytes()
|
|
} else {
|
|
let module = SingleInstModule::new(&mut u, &mut config.module_config)?;
|
|
module.to_bytes()
|
|
};
|
|
log_wasm(&wasm);
|
|
|
|
// Choose a left-hand side Wasm engine.
|
|
let lhs = engine::choose(&mut u, &config)?;
|
|
let lhs_instance = lhs.instantiate(&wasm);
|
|
|
|
// Choose a right-hand side Wasm engine--this will always be Wasmtime.
|
|
let rhs_store = config.to_store();
|
|
let rhs_module = wasmtime::Module::new(rhs_store.engine(), &wasm).unwrap();
|
|
let rhs_instance = WasmtimeInstance::new(rhs_store, rhs_module);
|
|
|
|
// If we fail to instantiate, check that both sides do.
|
|
let (mut lhs_instance, mut rhs_instance) = match (lhs_instance, rhs_instance) {
|
|
(Ok(l), Ok(r)) => (l, r),
|
|
(Err(_), Err(_)) => return Ok(()), // TODO match the error messages.
|
|
(l, r) => panic!(
|
|
"failed to instantiate only one side: {:?} != {:?}",
|
|
l.err(),
|
|
r.err()
|
|
),
|
|
};
|
|
|
|
// Call each exported function with different sets of arguments.
|
|
for (name, signature) in rhs_instance.exported_functions() {
|
|
let mut invocations = 0;
|
|
loop {
|
|
let arguments = signature
|
|
.params()
|
|
.map(|t| DiffValue::arbitrary_of_type(&mut u, t.try_into().unwrap()))
|
|
.collect::<Result<Vec<_>>>()?;
|
|
differential(lhs_instance.as_mut(), &mut rhs_instance, &name, &arguments)
|
|
.expect("failed to run differential evaluation");
|
|
|
|
// We evaluate the same function with different arguments until we
|
|
// hit a predetermined limit or we run out of unstructured data--it
|
|
// does not make sense to re-evaluate the same arguments over and
|
|
// over.
|
|
invocations += 1;
|
|
TOTAL_INVOCATIONS.fetch_add(1, SeqCst);
|
|
if invocations > NUM_INVOCATIONS || u.is_empty() {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TOTAL_SUCCESSES.fetch_add(1, SeqCst);
|
|
Ok(())
|
|
}
|