From a1732b2906d79033ff9266ac391a9783ac6daa27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Tue, 25 Apr 2023 05:56:24 +0200 Subject: [PATCH] winch(fuzz): Initial support for differential fuzzing (#6281) * winch(fuzz): Initial support for differential fuzzing This commit introduces initial support for differential fuzzing for Winch. In order to fuzz winch, this change introduces the `winch` cargo feature. When the `winch` cargo feature is enabled the differential fuzz target uses `wasmi` as the differential engine and `wasm-smith` and `single-inst` as the module sources. The intention behind this change is to have a *local* approach for fuzzing and verifying programs generated by Winch and to have an initial implementation that will allow us to eventually enable this change by default. Currently it's not worth it to enable this change by default given all the filtering that needs to happen to ensure that the generated modules are supported by Winch. It's worth noting that the Wasm filtering code will be temporary, until Winch reaches feature parity in terms of Wasm operators. * Check build targets with the `winch` feature flag * Rename fuzz target feature to `fuzz-winch` --- .github/workflows/main.yml | 2 + Cargo.lock | 1 + crates/fuzzing/Cargo.toml | 1 + crates/fuzzing/src/generators.rs | 2 + crates/fuzzing/src/generators/config.rs | 26 +++++++ fuzz/Cargo.toml | 2 + fuzz/fuzz_targets/differential.rs | 93 ++++++++++++++++++++++--- 7 files changed, 119 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0cdc7065a7..7108f63daf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -351,6 +351,8 @@ jobs: - run: cargo fuzz build --dev -s none # Check that the ISLE fuzz targets build too. - run: cargo fuzz build --dev -s none --fuzz-dir ./cranelift/isle/fuzz + # Check that winch fuzzing builds too. + - run: cargo fuzz build --dev -s none --features fuzz-winch # common logic to cancel the entire run if this job fails - run: gh run cancel ${{ github.run_id }} diff --git a/Cargo.lock b/Cargo.lock index 9811cb8cb1..3c3adcb714 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3984,6 +3984,7 @@ dependencies = [ "rand 0.8.5", "smallvec", "target-lexicon", + "wasmparser", "wasmtime", "wasmtime-fuzzing", ] diff --git a/crates/fuzzing/Cargo.toml b/crates/fuzzing/Cargo.toml index 053bd79ddf..5ead20aca9 100644 --- a/crates/fuzzing/Cargo.toml +++ b/crates/fuzzing/Cargo.toml @@ -46,3 +46,4 @@ wasm-spec-interpreter = { path = "./wasm-spec-interpreter", optional = true, fea [features] fuzz-spec-interpreter = ['wasm-spec-interpreter'] +winch = ["wasmtime/winch"] diff --git a/crates/fuzzing/src/generators.rs b/crates/fuzzing/src/generators.rs index 3da2e24040..8bc11b1a64 100644 --- a/crates/fuzzing/src/generators.rs +++ b/crates/fuzzing/src/generators.rs @@ -23,6 +23,8 @@ pub mod table_ops; mod value; pub use codegen_settings::CodegenSettings; +#[cfg(feature = "winch")] +pub use config::CompilerStrategy; pub use config::{Config, WasmtimeConfig}; pub use instance_allocation_strategy::InstanceAllocationStrategy; pub use memory::{MemoryConfig, NormalMemoryConfig, UnalignedMemory, UnalignedMemoryCreator}; diff --git a/crates/fuzzing/src/generators/config.rs b/crates/fuzzing/src/generators/config.rs index d689158326..d564cabbd0 100644 --- a/crates/fuzzing/src/generators/config.rs +++ b/crates/fuzzing/src/generators/config.rs @@ -168,6 +168,9 @@ impl Config { .allocation_strategy(self.wasmtime.strategy.to_wasmtime()) .generate_address_map(self.wasmtime.generate_address_map); + #[cfg(feature = "winch")] + cfg.strategy(self.wasmtime.compiler_strategy.to_wasmtime()); + self.wasmtime.codegen.configure(&mut cfg); // If the wasm-smith-generated module use nan canonicalization then we @@ -392,6 +395,9 @@ pub struct WasmtimeConfig { padding_between_functions: Option, generate_address_map: bool, native_unwind_info: bool, + #[cfg(feature = "winch")] + /// Configuration for the compiler to use. + pub compiler_strategy: CompilerStrategy, } impl WasmtimeConfig { @@ -435,3 +441,23 @@ impl OptLevel { } } } + +#[cfg(feature = "winch")] +#[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] +/// Compiler to use. +pub enum CompilerStrategy { + /// Cranelift compiler. + Cranelift, + /// Winch compiler. + Winch, +} + +#[cfg(feature = "winch")] +impl CompilerStrategy { + fn to_wasmtime(&self) -> wasmtime::Strategy { + match self { + CompilerStrategy::Cranelift => wasmtime::Strategy::Cranelift, + CompilerStrategy::Winch => wasmtime::Strategy::Winch, + } + } +} diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 53575417f2..17cb3b2900 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,6 +21,7 @@ cranelift-control = { workspace = true } libfuzzer-sys = { version = "0.4.0", features = ["arbitrary-derive"] } target-lexicon = { workspace = true } smallvec = { workspace = true } +wasmparser = { workspace = true, optional = true } wasmtime = { workspace = true } wasmtime-fuzzing = { workspace = true } component-test-util = { workspace = true } @@ -35,6 +36,7 @@ quote = "1.0" component-fuzz-util = { workspace = true } [features] +fuzz-winch = ["wasmtime/winch", "wasmtime-fuzzing/winch", "dep:wasmparser"] default = ['fuzz-spec-interpreter'] fuzz-spec-interpreter = ['wasmtime-fuzzing/fuzz-spec-interpreter'] chaos = ["cranelift-control/chaos"] diff --git a/fuzz/fuzz_targets/differential.rs b/fuzz/fuzz_targets/differential.rs index e6a1f50127..cf71d34232 100644 --- a/fuzz/fuzz_targets/differential.rs +++ b/fuzz/fuzz_targets/differential.rs @@ -6,10 +6,14 @@ use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering::SeqCst; use std::sync::Once; use wasmtime::Trap; +#[cfg(feature = "fuzz-winch")] +use wasmtime_fuzzing::generators::CompilerStrategy; use wasmtime_fuzzing::generators::{Config, DiffValue, DiffValueType, SingleInstModule}; use wasmtime_fuzzing::oracles::diff_wasmtime::WasmtimeInstance; use wasmtime_fuzzing::oracles::engine::{build_allowed_env_list, parse_env_list}; use wasmtime_fuzzing::oracles::{differential, engine, log_wasm}; +#[cfg(feature = "fuzz-winch")] +use wasmtime_fuzzing::wasm_smith::{InstructionKind, InstructionKinds}; // Upper limit on the number of invocations for each WebAssembly function // executed by this fuzz target. @@ -35,16 +39,22 @@ fuzz_target!(|data: &[u8]| { // `setup_ocaml_runtime`. engine::setup_engine_runtimes(); + let (default_engines, default_modules) = if cfg!(feature = "fuzz-winch") { + (vec!["wasmi"], vec!["wasm-smith", "single-inst"]) + } else { + ( + vec!["wasmtime", "wasmi", "spec", "v8"], + vec!["wasm-smith", "single-inst"], + ) + }; + // Retrieve the configuration for this fuzz target from `ALLOWED_*` // environment variables. - let allowed_engines = build_allowed_env_list( - parse_env_list("ALLOWED_ENGINES"), - &["wasmtime", "wasmi", "spec", "v8"], - ); - let allowed_modules = build_allowed_env_list( - parse_env_list("ALLOWED_MODULES"), - &["wasm-smith", "single-inst"], - ); + let allowed_engines = + build_allowed_env_list(parse_env_list("ALLOWED_ENGINES"), &default_engines); + let allowed_modules = + build_allowed_env_list(parse_env_list("ALLOWED_MODULES"), &default_modules); + unsafe { ALLOWED_ENGINES = allowed_engines; ALLOWED_MODULES = allowed_modules; @@ -68,6 +78,16 @@ fn execute_one(data: &[u8]) -> Result<()> { let mut config: Config = u.arbitrary()?; config.set_differential_config(); + #[cfg(feature = "fuzz-winch")] + { + // When fuzzing Winch: + // 1. Explicitly override the compiler strategy. + // 2. Explicitly set the allowed instructions for `wasm-smith`. + config.wasmtime.compiler_strategy = CompilerStrategy::Winch; + config.module_config.config.allowed_instructions = + InstructionKinds::new(&[InstructionKind::Numeric, InstructionKind::Variable]); + } + // Choose an engine that Wasmtime will be differentially executed against. // The chosen engine is then created, which might update `config`, and // returned as a trait object. @@ -100,6 +120,12 @@ fn execute_one(data: &[u8]) -> Result<()> { "single-inst" => build_single_inst_module(&mut u, &config)?, _ => unreachable!(), }; + + #[cfg(feature = "fuzz-winch")] + if !winch_supports_module(&wasm) { + return Ok(()); + } + log_wasm(&wasm); // Instantiate the generated wasm file in the chosen differential engine. @@ -263,3 +289,54 @@ impl RuntimeStats { }; } } + +#[cfg(feature = "fuzz-winch")] +// Returns true if the module only contains operators supported by +// Winch. Winch's x86_64 target has broader support for Wasm operators +// than the aarch64 target. This list assumes fuzzing on the x86_64 +// target. +fn winch_supports_module(module: &[u8]) -> bool { + use wasmparser::{Operator::*, Parser, Payload}; + + let mut supported = true; + let mut parser = Parser::new(0).parse_all(module); + + 'main: while let Some(payload) = parser.next() { + match payload.unwrap() { + Payload::CodeSectionEntry(body) => { + let op_reader = body.get_operators_reader().unwrap(); + for op in op_reader { + match op.unwrap() { + I32Const { .. } + | I64Const { .. } + | I32Add { .. } + | I64Add { .. } + | I32Sub { .. } + | I32Mul { .. } + | I32DivS { .. } + | I32DivU { .. } + | I64DivS { .. } + | I64DivU { .. } + | I64RemU { .. } + | I64RemS { .. } + | I32RemU { .. } + | I32RemS { .. } + | I64Mul { .. } + | I64Sub { .. } + | LocalGet { .. } + | LocalSet { .. } + | Call { .. } + | End { .. } => {} + _ => { + supported = false; + break 'main; + } + } + } + } + _ => {} + } + } + + supported +}