Skip to content

Commit

Permalink
Modernize Wasmi differential fuzzing (#1257)
Browse files Browse the repository at this point in the history
* refactor FuzzVal

* add FuzzError

* add Wasm module exports abstraction

* add DifferentialOracle trait

* add Wasmi oracle

* remove invalid doc link

* move wasmi/mod.rs -> wasmi.rs

* move From FuzzVal -> wasmi::Val impl

* add differential crate feature to wasmi_fuzz

* rename binding

* remove unused imports

* add Wasmi v0.31 oracle impl

* improve panic message

* add wasmtime differential oracle

* clean-up wasmtime oracle

* improve FuzzError and add is_non_deterministic

* add FuncType information to fuzz ModuleExports

* add [Partial]Eq for FuzzVal

* refactor DifferentialOracle traits

* enable tail-call and extended-const Wasm proposals for Wasmi (stack) oracle

* modernize differential fuzzing
  • Loading branch information
Robbepop authored Oct 26, 2024
1 parent c33ccf7 commit 93e8898
Show file tree
Hide file tree
Showing 12 changed files with 932 additions and 734 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions crates/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,11 @@ publish = false

[dependencies]
wasmi = { workspace = true, features = ["std"] }
wasmi-stack = { package = "wasmi", feature = ["std"], version = "0.31.2", optional = true }
wasmtime = { version = "26.0.0", feature = ["std"], optional = true }
wasm-smith = "0.219.1"
arbitrary = "1.3.2"

[features]
default = ["differential"]
differential = ["dep:wasmi-stack", "dep:wasmtime"]
25 changes: 25 additions & 0 deletions crates/fuzz/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#[derive(Debug, PartialEq, Eq)]
pub enum FuzzError {
Trap(TrapCode),
Other,
}

impl FuzzError {
/// Returns `true` if `self` may be of non-deterministic origin.
pub fn is_non_deterministic(&self) -> bool {
matches!(self, Self::Trap(TrapCode::StackOverflow) | Self::Other)
}
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TrapCode {
UnreachableCodeReached,
MemoryOutOfBounds,
TableOutOfBounds,
IndirectCallToNull,
IntegerDivisionByZero,
IntegerOverflow,
BadConversionToInteger,
StackOverflow,
BadSignature,
}
7 changes: 5 additions & 2 deletions crates/fuzz/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
pub mod config;
mod oracle;
mod error;
#[cfg(feature = "differential")]
pub mod oracle;
mod value;

pub use self::{
config::{FuzzSmithConfig, FuzzWasmiConfig},
value::{FuzzRefTy, FuzzVal, FuzzValType},
error::{FuzzError, TrapCode},
value::{FuzzVal, FuzzValType},
};
134 changes: 134 additions & 0 deletions crates/fuzz/src/oracle/exports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use core::slice;
use wasmi::FuncType;

/// Names of exported Wasm objects from a fuzzed Wasm module.
#[derive(Debug, Default)]
pub struct ModuleExports {
/// Names of exported functions.
funcs: StringSequence,
/// The types of exported functions.
func_types: Vec<FuncType>,
/// Names of exported global variables.
globals: StringSequence,
/// Names of exported linear memories.
memories: StringSequence,
/// Names of exported tables.
tables: StringSequence,
}

impl ModuleExports {
/// Pushes an exported function `name` to `self`.
pub(crate) fn push_func(&mut self, name: &str, ty: FuncType) {
self.funcs.push(name);
self.func_types.push(ty);
}

/// Pushes an exported global `name` to `self`.
pub(crate) fn push_global(&mut self, name: &str) {
self.globals.push(name);
}

/// Pushes an exported memory `name` to `self`.
pub(crate) fn push_memory(&mut self, name: &str) {
self.memories.push(name);
}

/// Pushes an exported table `name` to `self`.
pub(crate) fn push_table(&mut self, name: &str) {
self.tables.push(name);
}

/// Returns an iterator yielding the names of the exported Wasm functions.
pub fn funcs(&self) -> ExportedFuncsIter {
ExportedFuncsIter {
names: self.funcs.iter(),
types: self.func_types.iter(),
}
}

/// Returns an iterator yielding the names of the exported Wasm globals.
pub fn globals(&self) -> StringSequenceIter {
self.globals.iter()
}

/// Returns an iterator yielding the names of the exported Wasm memories.
pub fn memories(&self) -> StringSequenceIter {
self.memories.iter()
}

/// Returns an iterator yielding the names of the exported Wasm tables.
pub fn tables(&self) -> StringSequenceIter {
self.tables.iter()
}
}

/// Iterator yieling the exported functions of a fuzzed Wasm module.
#[derive(Debug)]
pub struct ExportedFuncsIter<'a> {
/// The names of the exported Wasm functions.
names: StringSequenceIter<'a>,
/// The types of the exported Wasm functions.
types: slice::Iter<'a, FuncType>,
}

impl<'a> Iterator for ExportedFuncsIter<'a> {
type Item = (&'a str, &'a FuncType);

#[inline]
fn next(&mut self) -> Option<Self::Item> {
let name = self.names.next()?;
let ty = self.types.next()?;
Some((name, ty))
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.names.size_hint()
}
}

/// An append-only sequence of strings.
#[derive(Debug, Default)]
pub struct StringSequence {
/// The underlying sequence of strings.
strings: Vec<Box<str>>,
}

impl StringSequence {
/// Pushes another string `s` to `self`.
pub fn push(&mut self, s: &str) {
self.strings.push(Box::from(s));
}

/// Returns an iterator over the strings in `self`.
///
/// The iterator yields the strings in order of their insertion.
pub fn iter(&self) -> StringSequenceIter {
StringSequenceIter {
iter: self.strings.iter(),
}
}
}

/// An iterator yielding the strings of a sequence of strings.
#[derive(Debug)]
pub struct StringSequenceIter<'a> {
/// The underlying iterator over strings.
iter: slice::Iter<'a, Box<str>>,
}

impl<'a> Iterator for StringSequenceIter<'a> {
type Item = &'a str;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|s| &**s)
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}

impl ExactSizeIterator for StringSequenceIter<'_> {}
76 changes: 76 additions & 0 deletions crates/fuzz/src/oracle/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,77 @@
pub use self::{
exports::{ModuleExports, StringSequenceIter},
wasmi::WasmiOracle,
wasmi_stack::WasmiStackOracle,
wasmtime::WasmtimeOracle,
};
use crate::{FuzzError, FuzzSmithConfig, FuzzVal};
use arbitrary::{Arbitrary, Unstructured};

mod exports;
mod wasmi;
mod wasmi_stack;
mod wasmtime;

/// Trait implemented by differential fuzzing oracles.
pub trait DifferentialOracle {
/// Returns the name of the differential fuzzing oracle.
fn name(&self) -> &'static str;

/// Calls the exported function with `name` and `params` and returns the result.
fn call(&mut self, name: &str, params: &[FuzzVal]) -> Result<Box<[FuzzVal]>, FuzzError>;

/// Returns the value of the global named `name` if any.
fn get_global(&mut self, name: &str) -> Option<FuzzVal>;

/// Returns the bytes of the memory named `name` if any.
fn get_memory(&mut self, name: &str) -> Option<&[u8]>;
}

/// Trait implemented by differential fuzzing oracles.
pub trait DifferentialOracleMeta: Sized {
/// Tells `config` about the minimum viable configuration possible for this oracle.
fn configure(config: &mut FuzzSmithConfig);

/// Sets up the Wasm fuzzing oracle for the given `wasm` binary if possible.
fn setup(wasm: &[u8]) -> Option<Self>;
}

/// A chosen differnential fuzzing oracle.
#[derive(Debug, Default, Copy, Clone)]
pub enum ChosenOracle {
/// The legacy Wasmi v0.31 oracle.
#[default]
WasmiStack,
/// The Wasmtime oracle.
Wasmtime,
}

impl Arbitrary<'_> for ChosenOracle {
fn arbitrary(u: &mut Unstructured) -> arbitrary::Result<Self> {
let index = u8::arbitrary(u).unwrap_or_default();
let chosen = match index {
0 => Self::Wasmtime,
_ => Self::WasmiStack,
};
Ok(chosen)
}
}

impl ChosenOracle {
/// Configures `fuzz_config` for the chosen differential fuzzing oracle.
pub fn configure(&self, fuzz_config: &mut FuzzSmithConfig) {
match self {
ChosenOracle::WasmiStack => WasmiStackOracle::configure(fuzz_config),
ChosenOracle::Wasmtime => WasmtimeOracle::configure(fuzz_config),
}
}

/// Sets up the chosen differential fuzzing oracle.
pub fn setup(&self, wasm: &[u8]) -> Option<Box<dyn DifferentialOracle>> {
let oracle: Box<dyn DifferentialOracle> = match self {
ChosenOracle::WasmiStack => Box::new(WasmiStackOracle::setup(wasm)?),
ChosenOracle::Wasmtime => Box::new(WasmtimeOracle::setup(wasm)?),
};
Some(oracle)
}
}
Loading

0 comments on commit 93e8898

Please sign in to comment.