Skip to content

Commit

Permalink
Improve debug output of fuzzers (#1344)
Browse files Browse the repository at this point in the history
* add FuzzModule, WasmSource and WatSource to wasmi_fuzz crate

* use new FuzzModule in differential fuzzing

* rename smith_module -> module

* improve debug output for execute fuzzing target

* improve debug output for translation fuzzer

* re-export WasmSource and WatSource
  • Loading branch information
Robbepop authored Jan 24, 2025
1 parent 174e14c commit 320f556
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 68 deletions.
2 changes: 2 additions & 0 deletions crates/fuzz/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod config;
mod crash_inputs;
mod error;
mod module;
#[cfg(feature = "differential")]
pub mod oracle;
mod value;
Expand All @@ -9,5 +10,6 @@ pub use self::{
config::{FuzzSmithConfig, FuzzWasmiConfig},
crash_inputs::generate_crash_inputs,
error::{FuzzError, TrapCode},
module::{FuzzModule, WasmSource, WatSource},
value::{FuzzVal, FuzzValType},
};
87 changes: 87 additions & 0 deletions crates/fuzz/src/module.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use arbitrary::Unstructured;
use std::fmt::{self, Debug};

/// A Wasm module fuzz input.
pub struct FuzzModule {
module: wasm_smith::Module,
}

impl FuzzModule {
/// Creates a new [`FuzzModule`] from the given `config` and fuzz input bytes, `u`.
pub fn new(
config: impl Into<wasm_smith::Config>,
u: &mut Unstructured,
) -> arbitrary::Result<Self> {
let config = config.into();
let module = wasm_smith::Module::new(config, u)?;
Ok(Self { module })
}

/// Ensure that all of this Wasm module’s functions will terminate when executed.
///
/// Read more about this API [here](wasm_smith::Module::ensure_termination).
pub fn ensure_termination(&mut self, default_fuel: u32) {
if let Err(err) = self.module.ensure_termination(default_fuel) {
panic!("unexpected invalid Wasm module: {err}")
}
}

/// Returns the machine readble [`WasmSource`] code.
pub fn wasm(&self) -> WasmSource {
WasmSource {
bytes: self.module.to_bytes(),
}
}
}

impl Debug for FuzzModule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let config = self.module.config();
let wat = self.wasm().to_wat();
f.debug_struct("FuzzModule")
.field("config", config)
.field("wat", &wat)
.finish()
}
}

/// A `.wasm` source code.
pub struct WasmSource {
bytes: Vec<u8>,
}

impl WasmSource {
/// Consumes `self` and returns the underlying bytes of the [`WasmSource`].
pub fn into_bytes(self) -> Box<[u8]> {
self.bytes.into()
}

/// Returns the underlying bytes of the [`WasmSource`].
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..]
}

/// Converts the [`WasmSource`] to human readable `.wat` formatted source.
///
/// The returned [`WatSource`] is convenience for debugging.
pub fn to_wat(&self) -> WatSource {
let wat = match wasmprinter::print_bytes(&self.bytes[..]) {
Ok(wat) => wat,
Err(err) => panic!("invalid Wasm: {err}"),
};
WatSource { text: wat }
}
}

/// A `.wat` source code.
///
/// Convenience type for debug printing `.wat` formatted Wasm source code.
pub struct WatSource {
text: String,
}

impl fmt::Debug for WatSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\n{}", self.text)
}
}
38 changes: 7 additions & 31 deletions fuzz/fuzz_targets/differential.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
#![no_main]

use std::fmt;

use arbitrary::{Arbitrary, Unstructured};
use libfuzzer_sys::fuzz_target;
use wasmi::Val;
use wasmi_fuzz::{
config::FuzzSmithConfig,
oracle::{ChosenOracle, DifferentialOracle, DifferentialOracleMeta, WasmiOracle},
FuzzModule,
FuzzVal,
};

/// Fuzzing input for differential fuzzing.
#[derive(Debug)]
pub struct FuzzInput {
/// The chosen Wasm runtime oracle to compare against Wasmi.
chosen_oracle: ChosenOracle,
/// The fuzzed Wasm module and its configuration.
smith_module: wasm_smith::Module,
module: FuzzModule,
}

impl<'a> Arbitrary<'a> for FuzzInput {
Expand All @@ -28,43 +28,19 @@ impl<'a> Arbitrary<'a> for FuzzInput {
WasmiOracle::configure(&mut fuzz_config);
chosen_oracle.configure(&mut fuzz_config);
let smith_config: wasm_smith::Config = fuzz_config.into();
let mut smith_module = wasm_smith::Module::new(smith_config, u)?;
smith_module.ensure_termination(1_000 /* fuel */)
.expect("`ensure_termination` can only fail for modules that have not been created by `wasm_smith`");
let mut smith_module = FuzzModule::new(smith_config, u)?;
smith_module.ensure_termination(1_000 /* fuel */);
Ok(Self {
chosen_oracle,
smith_module,
module: smith_module,
})
}
}

impl fmt::Debug for FuzzInput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// Prints the internal `String` as `Display` on `Debug` output.
pub struct Text(String);
impl fmt::Debug for Text {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\n{}", self.0.as_str())
}
}

let wasm = self.smith_module.to_bytes();
let wat = match wasmprinter::print_bytes(wasm) {
Ok(wat) => wat,
Err(err) => format!("invalid Wasm: {err}"),
};
f.debug_struct("FuzzInput")
.field("chosen_oracle", &self.chosen_oracle)
.field("module", &self.smith_module)
.field("wasm", &Text(wat))
.finish()
}
}

impl FuzzInput {
/// Returns the fuzzed Wasm input bytes.
pub fn wasm(&self) -> Box<[u8]> {
self.smith_module.to_bytes().into()
self.module.wasm().into_bytes()
}
}

Expand Down
69 changes: 49 additions & 20 deletions fuzz/fuzz_targets/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,61 @@ use wasmi::{
StoreLimitsBuilder,
Val,
};
use wasmi_fuzz::{config::ValidationMode, FuzzVal, FuzzValType, FuzzWasmiConfig};
use wasmi_fuzz::{
config::ValidationMode,
FuzzModule,
FuzzSmithConfig,
FuzzVal,
FuzzValType,
FuzzWasmiConfig,
};

fuzz_target!(|seed: &[u8]| {
let mut u = Unstructured::new(seed);
let Ok(wasmi_config) = FuzzWasmiConfig::arbitrary(&mut u) else {
return;
};
let Ok(mut fuzz_config) = wasmi_fuzz::FuzzSmithConfig::arbitrary(&mut u) else {
return;
};
fuzz_config.export_everything();
let Ok(smith_module) = wasm_smith::Module::new(fuzz_config.into(), &mut u) else {
return;
};
let wasm_bytes = smith_module.to_bytes();
let wasm = wasm_bytes.as_slice();
#[derive(Debug)]
pub struct FuzzInput<'a> {
config: FuzzWasmiConfig,
module: FuzzModule,
u: Unstructured<'a>,
}

impl<'a> Arbitrary<'a> for FuzzInput<'a> {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let config = FuzzWasmiConfig::arbitrary(u)?;
let mut fuzz_config = FuzzSmithConfig::arbitrary(u)?;
fuzz_config.export_everything();
let module = FuzzModule::new(fuzz_config, u)?;
Ok(Self {
config,
module,
u: Unstructured::new(&[]),
})
}

fn arbitrary_take_rest(mut u: Unstructured<'a>) -> arbitrary::Result<Self> {
Self::arbitrary(&mut u).map(|mut input| {
input.u = u;
input
})
}
}

fuzz_target!(|input: FuzzInput| {
let FuzzInput {
config,
module,
mut u,
} = input;
let wasm_bytes = module.wasm().into_bytes();
let wasm = &wasm_bytes[..];

let config = {
let mut config = Config::from(wasmi_config);
let engine_config = {
let mut config = Config::from(config);
// We use Wasmi's built-in fuel metering since it is way faster
// than `wasm_smith`'s fuel metering and thus allows the fuzzer
// to expand its test coverage faster.
config.consume_fuel(true);
config
};
let engine = Engine::new(&config);
let engine = Engine::new(&engine_config);
let linker = Linker::new(&engine);
let limiter = StoreLimitsBuilder::new()
.memory_size(1000 * 0x10000)
Expand All @@ -48,15 +77,15 @@ fuzz_target!(|seed: &[u8]| {
let Ok(_) = store.set_fuel(1000) else {
return;
};
if matches!(wasmi_config.validation_mode, ValidationMode::Unchecked) {
if matches!(config.validation_mode, ValidationMode::Unchecked) {
// We validate the Wasm module before handing it over to Wasmi
// despite `wasm_smith` stating to only produce valid Wasm.
// Translating an invalid Wasm module is undefined behavior.
if Module::validate(&engine, wasm).is_err() {
return;
}
}
let status = match wasmi_config.validation_mode {
let status = match config.validation_mode {
ValidationMode::Checked => Module::new(&engine, wasm),
ValidationMode::Unchecked => {
// Safety: we have just checked Wasm validity above.
Expand Down
41 changes: 24 additions & 17 deletions fuzz/fuzz_targets/translate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,40 @@ use libfuzzer_sys::fuzz_target;
use wasmi::{Config, Engine, Module};
use wasmi_fuzz::{
config::{ParsingMode, ValidationMode},
FuzzModule,
FuzzWasmiConfig,
};

fuzz_target!(|seed: &[u8]| {
let mut u = Unstructured::new(seed);
let Ok(wasmi_config) = FuzzWasmiConfig::arbitrary(&mut u) else {
return;
};
let Ok(fuzz_config) = wasmi_fuzz::FuzzSmithConfig::arbitrary(&mut u) else {
return;
};
let Ok(smith_module) = wasm_smith::Module::new(fuzz_config.into(), &mut u) else {
return;
};
let wasm_bytes = smith_module.to_bytes();
let wasm = wasm_bytes.as_slice();
let config = Config::from(wasmi_config);
let engine = Engine::new(&config);
if matches!(wasmi_config.validation_mode, ValidationMode::Unchecked) {
#[derive(Debug)]
pub struct FuzzInput {
config: FuzzWasmiConfig,
module: FuzzModule,
}

impl<'a> Arbitrary<'a> for FuzzInput {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let config = FuzzWasmiConfig::arbitrary(u)?;
let fuzz_config = wasmi_fuzz::FuzzSmithConfig::arbitrary(u)?;
let module = wasmi_fuzz::FuzzModule::new(fuzz_config, u)?;
Ok(Self { config, module })
}
}

fuzz_target!(|input: FuzzInput| {
let FuzzInput { config, module } = input;
let wasm_source = module.wasm();
let wasm = wasm_source.as_bytes();
let engine_config = Config::from(config);
let engine = Engine::new(&engine_config);
if matches!(config.validation_mode, ValidationMode::Unchecked) {
// We validate the Wasm module before handing it over to Wasmi
// despite `wasm_smith` stating to only produce valid Wasm.
// Translating an invalid Wasm module is undefined behavior.
if Module::validate(&engine, wasm).is_err() {
return;
}
}
let status = match (wasmi_config.parsing_mode, wasmi_config.validation_mode) {
let status = match (config.parsing_mode, config.validation_mode) {
(ParsingMode::Streaming, ValidationMode::Checked) => Module::new_streaming(&engine, wasm),
(ParsingMode::Buffered, ValidationMode::Checked) => Module::new(&engine, wasm),
(ParsingMode::Streaming, ValidationMode::Unchecked) => {
Expand Down

0 comments on commit 320f556

Please sign in to comment.