Skip to content

Commit 6047d27

Browse files
khagankhanKhagan Karimov
andauthored
Add fuzzer integration for gc/mutatis (#11290)
* Add fuzzer integration for gc/mutatis * Replace bincode with postcard. Inline fuzz_mutate. Address other small comments * Update Cargo.lock after rebase * Update rng.gen to rng.fill to avoid deprecation failure * Remove table_ops from misc. Update fuzz_target. Keep Arbitrary impl it gives some dependecy error. Will be addressed later * Update Cargo.lock to match upstream/main * Regenerate Cargo.lock after rebase and dependency changes * Reset Cargo.lock to upstream/main to preserve cargo-deny compatibility * Update dependencies --------- Co-authored-by: Khagan Karimov <[email protected]>
1 parent 1155d6d commit 6047d27

File tree

6 files changed

+83
-10
lines changed

6 files changed

+83
-10
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/fuzzing/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ wasmi = { version = "0.43.1", default-features = false, features = ["std", "simd
3636
futures = { workspace = true }
3737
wasmtime-test-util = { workspace = true, features = ['wast', 'component-fuzz', 'component'] }
3838
serde_json = { workspace = true }
39+
serde = { workspace = true }
3940

4041
[dependencies.wasmtime-cli-flags]
4142
workspace = true

crates/fuzzing/src/generators/table_ops.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Generating series of `table.get` and `table.set` operations.
22
use mutatis::mutators as m;
33
use mutatis::{Candidates, Context, DefaultMutate, Generate, Mutate, Result as MutResult};
4+
use serde::{Deserialize, Serialize};
45
use smallvec::SmallVec;
56
use std::ops::RangeInclusive;
67
use wasm_encoder::{
@@ -11,12 +12,12 @@ use wasm_encoder::{
1112

1213
/// A description of a Wasm module that makes a series of `externref` table
1314
/// operations.
14-
#[derive(Debug, Default)]
15+
#[derive(Debug, Default, Serialize, Deserialize)]
1516
pub struct TableOps {
1617
pub(crate) num_params: u32,
1718
pub(crate) num_globals: u32,
1819
pub(crate) table_size: i32,
19-
ops: Vec<TableOp>,
20+
pub(crate) ops: Vec<TableOp>,
2021
}
2122

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

183184
self.ops = new_ops;
184185
}
186+
187+
/// Attempts to remove the last opcode from the sequence.
188+
///
189+
/// Returns `true` if an opcode was successfully removed, or `false` if the list was already empty.
190+
pub fn pop(&mut self) -> bool {
191+
self.ops.pop().is_some()
192+
}
185193
}
186194

187195
/// A mutator for the table ops
@@ -283,7 +291,7 @@ macro_rules! define_table_ops {
283291
$op:ident $( ( $($limit:expr => $ty:ty),* ) )? : $params:expr => $results:expr ,
284292
)*
285293
) => {
286-
#[derive(Copy, Clone, Debug)]
294+
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
287295
pub(crate) enum TableOp {
288296
$(
289297
$op ( $( $($ty),* )? ),

fuzz/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ workspace = true
1212
cargo-fuzz = true
1313

1414
[dependencies]
15+
mutatis = { workspace = true }
16+
rand = { workspace = true }
17+
postcard = { workspace = true }
1518
anyhow = { workspace = true }
1619
env_logger = { workspace = true }
1720
cranelift-assembler-x64 = { workspace = true, features = ["fuzz"] }
@@ -105,3 +108,10 @@ path = "fuzz_targets/misc.rs"
105108
test = false
106109
doc = false
107110
bench = false
111+
112+
[[bin]]
113+
name = "table_ops"
114+
path = "fuzz_targets/table_ops.rs"
115+
test = false
116+
doc = false
117+
bench = false

fuzz/fuzz_targets/misc.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ run_fuzzers! {
6464
pulley_roundtrip
6565
assembler_roundtrip
6666
memory_accesses
67-
table_ops
6867
stacks
6968
api_calls
7069
dominator_tree
@@ -87,12 +86,6 @@ fn memory_accesses(u: Unstructured<'_>) -> Result<()> {
8786
Ok(())
8887
}
8988

90-
fn table_ops(u: Unstructured<'_>) -> Result<()> {
91-
let (config, ops) = Arbitrary::arbitrary_take_rest(u)?;
92-
let _ = wasmtime_fuzzing::oracles::table_ops(config, ops);
93-
Ok(())
94-
}
95-
9689
fn stacks(u: Unstructured<'_>) -> Result<()> {
9790
wasmtime_fuzzing::oracles::check_stacks(Arbitrary::arbitrary_take_rest(u)?);
9891
Ok(())

fuzz/fuzz_targets/table_ops.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#![no_main]
2+
3+
use libfuzzer_sys::arbitrary::{Arbitrary, Unstructured};
4+
use libfuzzer_sys::{fuzz_mutator, fuzz_target, fuzzer_mutate};
5+
use mutatis::Session;
6+
use postcard::{from_bytes, to_slice};
7+
use rand::{Rng, SeedableRng};
8+
use wasmtime_fuzzing::generators::table_ops::TableOps;
9+
use wasmtime_fuzzing::oracles::table_ops;
10+
11+
fuzz_target!(|data: &[u8]| {
12+
let Ok((seed, ops)) = postcard::from_bytes::<(u64, TableOps)>(data) else {
13+
return;
14+
};
15+
16+
let mut buf = [0u8; 1024];
17+
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
18+
rng.fill(&mut buf);
19+
20+
let u = Unstructured::new(&buf);
21+
let config = wasmtime_fuzzing::generators::Config::arbitrary_take_rest(u)
22+
.expect("should be able to generate config from seed");
23+
24+
let _ = table_ops(config, ops);
25+
});
26+
27+
fuzz_mutator!(|data: &mut [u8], size: usize, max_size: usize, seed: u32| {
28+
let _ = env_logger::try_init();
29+
30+
// With probability of about 1/8, use default mutator
31+
if seed.count_ones() % 8 == 0 {
32+
return fuzzer_mutate(data, size, max_size);
33+
}
34+
35+
// Try to decode using postcard; fallback to default input on failure
36+
let mut tuple: (u64, TableOps) = from_bytes(&data[..size]).ok().unwrap_or_default();
37+
38+
let mut session = Session::new().seed(seed.into()).shrink(max_size < size);
39+
40+
if session.mutate(&mut tuple).is_ok() {
41+
loop {
42+
if let Ok(encoded) = to_slice(&tuple, data) {
43+
return encoded.len();
44+
}
45+
46+
// Attempt to shrink ops if encoding fails (e.g., buffer too small)
47+
if tuple.1.pop() {
48+
continue;
49+
}
50+
51+
break;
52+
}
53+
}
54+
55+
// Fallback to default libfuzzer mutator
56+
fuzzer_mutate(data, size, max_size)
57+
});

0 commit comments

Comments
 (0)