Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/fuzzing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ wasmi = { version = "0.43.1", default-features = false, features = ["std", "simd
futures = { workspace = true }
wasmtime-test-util = { workspace = true, features = ['wast', 'component-fuzz', 'component'] }
serde_json = { workspace = true }
serde = { workspace = true }

[dependencies.wasmtime-cli-flags]
workspace = true
Expand Down
14 changes: 11 additions & 3 deletions crates/fuzzing/src/generators/table_ops.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Generating series of `table.get` and `table.set` operations.
use mutatis::mutators as m;
use mutatis::{Candidates, Context, DefaultMutate, Generate, Mutate, Result as MutResult};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::ops::RangeInclusive;
use wasm_encoder::{
Expand All @@ -11,12 +12,12 @@ use wasm_encoder::{

/// A description of a Wasm module that makes a series of `externref` table
/// operations.
#[derive(Debug, Default)]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct TableOps {
pub(crate) num_params: u32,
pub(crate) num_globals: u32,
pub(crate) table_size: i32,
ops: Vec<TableOp>,
pub(crate) ops: Vec<TableOp>,
}

const NUM_PARAMS_RANGE: RangeInclusive<u32> = 0..=10;
Expand Down Expand Up @@ -182,6 +183,13 @@ impl TableOps {

self.ops = new_ops;
}

/// Attempts to remove the last opcode from the sequence.
///
/// Returns `true` if an opcode was successfully removed, or `false` if the list was already empty.
pub fn pop(&mut self) -> bool {
self.ops.pop().is_some()
}
}

/// A mutator for the table ops
Expand Down Expand Up @@ -283,7 +291,7 @@ macro_rules! define_table_ops {
$op:ident $( ( $($limit:expr => $ty:ty),* ) )? : $params:expr => $results:expr ,
)*
) => {
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub(crate) enum TableOp {
$(
$op ( $( $($ty),* )? ),
Expand Down
10 changes: 10 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ workspace = true
cargo-fuzz = true

[dependencies]
mutatis = { workspace = true }
rand = { workspace = true }
postcard = { workspace = true }
anyhow = { workspace = true }
env_logger = { workspace = true }
cranelift-assembler-x64 = { workspace = true, features = ["fuzz"] }
Expand Down Expand Up @@ -105,3 +108,10 @@ path = "fuzz_targets/misc.rs"
test = false
doc = false
bench = false

[[bin]]
name = "table_ops"
path = "fuzz_targets/table_ops.rs"
test = false
doc = false
bench = false
7 changes: 0 additions & 7 deletions fuzz/fuzz_targets/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ run_fuzzers! {
pulley_roundtrip
assembler_roundtrip
memory_accesses
table_ops
stacks
api_calls
dominator_tree
Expand All @@ -87,12 +86,6 @@ fn memory_accesses(u: Unstructured<'_>) -> Result<()> {
Ok(())
}

fn table_ops(u: Unstructured<'_>) -> Result<()> {
let (config, ops) = Arbitrary::arbitrary_take_rest(u)?;
let _ = wasmtime_fuzzing::oracles::table_ops(config, ops);
Ok(())
}

fn stacks(u: Unstructured<'_>) -> Result<()> {
wasmtime_fuzzing::oracles::check_stacks(Arbitrary::arbitrary_take_rest(u)?);
Ok(())
Expand Down
57 changes: 57 additions & 0 deletions fuzz/fuzz_targets/table_ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#![no_main]

use libfuzzer_sys::arbitrary::{Arbitrary, Unstructured};
use libfuzzer_sys::{fuzz_mutator, fuzz_target, fuzzer_mutate};
use mutatis::Session;
use postcard::{from_bytes, to_slice};
use rand::{Rng, SeedableRng};
use wasmtime_fuzzing::generators::table_ops::TableOps;
use wasmtime_fuzzing::oracles::table_ops;

fuzz_target!(|data: &[u8]| {
let Ok((seed, ops)) = postcard::from_bytes::<(u64, TableOps)>(data) else {
return;
};

let mut buf = [0u8; 1024];
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
rng.fill(&mut buf);

let u = Unstructured::new(&buf);
let config = wasmtime_fuzzing::generators::Config::arbitrary_take_rest(u)
.expect("should be able to generate config from seed");

let _ = table_ops(config, ops);
});

fuzz_mutator!(|data: &mut [u8], size: usize, max_size: usize, seed: u32| {
let _ = env_logger::try_init();

// With probability of about 1/8, use default mutator
if seed.count_ones() % 8 == 0 {
return fuzzer_mutate(data, size, max_size);
}

// Try to decode using postcard; fallback to default input on failure
let mut tuple: (u64, TableOps) = from_bytes(&data[..size]).ok().unwrap_or_default();

let mut session = Session::new().seed(seed.into()).shrink(max_size < size);

if session.mutate(&mut tuple).is_ok() {
loop {
if let Ok(encoded) = to_slice(&tuple, data) {
return encoded.len();
}

// Attempt to shrink ops if encoding fails (e.g., buffer too small)
if tuple.1.pop() {
continue;
}

break;
}
}

// Fallback to default libfuzzer mutator
fuzzer_mutate(data, size, max_size)
});