-
Notifications
You must be signed in to change notification settings - Fork 330
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(fuzzing): Improve Wasm compilation fuzzer (#3380)
This PR tries to improve the coverage of arbitrary Wasms generated by the wasm fuzzers. 1. Compilation fuzzer: - Added checks for non determinism in compilation errors. - The arbitrary Wasm module provided to the fuzzer has the current probability distribution ``` // 33% - Random bytes // 33% - Wasm with arbitrary wasm-smith config + maybe invalid functions // 33% - IC compliant wasm + maybe invalid functions ``` 2. `ICWasmModule` will generate both wasm32 and wasm64 modules. EmbeddersConfig and System API imports are handled accordingly. 3. Unify EmbeddersConfig in `ic_wasm.rs`
- Loading branch information
1 parent
c16efb0
commit ad9ac37
Showing
10 changed files
with
270 additions
and
152 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
#![no_main] | ||
use libfuzzer_sys::fuzz_target; | ||
use wasm_fuzzers::compile::run_fuzzer; | ||
use wasm_fuzzers::compile::MaybeInvalidModule; | ||
|
||
fuzz_target!(|module: MaybeInvalidModule| { | ||
fuzz_target!(|module: &[u8]| { | ||
run_fuzzer(module); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,134 @@ | ||
use crate::ic_wasm::{generate_exports, ic_embedders_config, ic_wasm_config}; | ||
use arbitrary::{Arbitrary, Result, Unstructured}; | ||
use ic_config::embedders::Config as EmbeddersConfig; | ||
use ic_config::flag_status::FlagStatus; | ||
use ic_embedders::{wasm_utils::compile, WasmtimeEmbedder}; | ||
use ic_logger::replica_logger::no_op_logger; | ||
use ic_wasm_types::BinaryEncodedWasm; | ||
use wasm_smith::{Config, Module}; | ||
use std::time::Duration; | ||
use tokio::runtime::Runtime; | ||
use wasm_smith::{Config, MemoryOffsetChoices, Module}; | ||
|
||
#[derive(Debug)] | ||
pub struct MaybeInvalidModule(pub Module); | ||
pub struct MaybeInvalidModule { | ||
pub module: Module, | ||
pub memory64_enabled: bool, | ||
} | ||
|
||
const MAX_PARALLEL_EXECUTIONS: usize = 4; | ||
|
||
impl<'a> Arbitrary<'a> for MaybeInvalidModule { | ||
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { | ||
let mut config = Config::arbitrary(u)?; | ||
let mut config = if u.ratio(1, 2)? { | ||
let memory64_enabled = u.ratio(2, 3)?; | ||
let mut config = ic_wasm_config(ic_embedders_config(memory64_enabled)); | ||
config.exports = generate_exports(ic_embedders_config(memory64_enabled), u)?; | ||
config.min_data_segments = 2; | ||
config.max_data_segments = 10; | ||
config | ||
} else { | ||
Config::arbitrary(u)? | ||
}; | ||
config.allow_invalid_funcs = true; | ||
Ok(MaybeInvalidModule(Module::new(config, u)?)) | ||
config.memory_offset_choices = MemoryOffsetChoices(40, 20, 40); | ||
Ok(MaybeInvalidModule { | ||
module: Module::new(config.clone(), u)?, | ||
memory64_enabled: config.memory64_enabled, | ||
}) | ||
} | ||
} | ||
|
||
#[inline(always)] | ||
pub fn run_fuzzer(module: MaybeInvalidModule) { | ||
let mut config = EmbeddersConfig::default(); | ||
config.feature_flags.wasm64 = FlagStatus::Enabled; | ||
let wasm = module.0.to_bytes(); | ||
let binary_wasm = BinaryEncodedWasm::new(wasm); | ||
let embedder = WasmtimeEmbedder::new(config, no_op_logger()); | ||
|
||
let (_, _) = compile(&embedder, &binary_wasm); | ||
pub fn run_fuzzer(bytes: &[u8]) { | ||
let config; | ||
let mut u = Unstructured::new(bytes); | ||
|
||
// Arbitrary Wasm module generation probabilities | ||
// 33% - Random bytes | ||
// 33% - Wasm with arbitrary wasm-smith config + maybe invalid functions | ||
// 33% - IC compliant wasm + maybe invalid functions | ||
|
||
// Only used w/ random bytes | ||
let memory64_enabled = u.ratio(1, 2).unwrap_or(false); | ||
|
||
let wasm = if u.ratio(1, 3).unwrap_or(false) | ||
|| bytes.len() < <MaybeInvalidModule as Arbitrary>::size_hint(0).0 | ||
{ | ||
config = ic_embedders_config(memory64_enabled); | ||
raw_wasm_bytes(bytes) | ||
} else { | ||
let data = <MaybeInvalidModule as Arbitrary>::arbitrary_take_rest(u); | ||
|
||
match data { | ||
Ok(data) => { | ||
config = ic_embedders_config(data.memory64_enabled); | ||
data.module.to_bytes() | ||
} | ||
Err(_) => { | ||
config = ic_embedders_config(memory64_enabled); | ||
raw_wasm_bytes(bytes) | ||
} | ||
} | ||
}; | ||
|
||
let rt: Runtime = tokio::runtime::Builder::new_multi_thread() | ||
.worker_threads(6) | ||
.max_blocking_threads(2) | ||
.enable_all() | ||
.build() | ||
.unwrap_or_else(|err| panic!("Could not create tokio runtime: {}", err)); | ||
|
||
let futs = (0..MAX_PARALLEL_EXECUTIONS) | ||
.map(|_| { | ||
rt.spawn({ | ||
let wasm = wasm.clone(); | ||
let binary_wasm = BinaryEncodedWasm::new(wasm); | ||
let embedder = WasmtimeEmbedder::new(config.clone(), no_op_logger()); | ||
|
||
async move { compile(&embedder, &binary_wasm) } | ||
}) | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
rt.block_on(async move { | ||
// The omitted field is EmbedderCache(Result<InstancePre<StoreData>, HypervisorError>) | ||
// 1. InstancePre<StoreData> doesn't implement PartialEq | ||
// 2. HypervisorError is the same in compilation_result which is checked for equality | ||
|
||
let result = futures::future::join_all(futs) | ||
.await | ||
.into_iter() | ||
.map(|r| r.expect("Failed to join tasks")) | ||
.map(|(_, compilation_result)| { | ||
if let Ok(mut r) = compilation_result { | ||
r.0.compilation_time = Duration::from_millis(1); | ||
Ok(r) | ||
} else { | ||
compilation_result | ||
} | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
let first = result.first(); | ||
|
||
if let Some(first) = first { | ||
assert!(result.iter().all(|r| r == first)); | ||
} | ||
}); | ||
} | ||
|
||
#[inline(always)] | ||
fn raw_wasm_bytes(data: &[u8]) -> Vec<u8> { | ||
let mut wasm: Vec<u8> = b"\x00asm".to_vec(); | ||
wasm.extend_from_slice(data); | ||
wasm | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use arbitrary::{Arbitrary, Unstructured}; | ||
|
||
#[test] | ||
fn test_compile_wasm_using_embedder_single_run() { | ||
let arbitrary_str: &str = "this is a test string"; | ||
let unstrucutred = Unstructured::new(arbitrary_str.as_bytes()); | ||
let module = <MaybeInvalidModule as Arbitrary>::arbitrary_take_rest(unstrucutred) | ||
.expect("Unable to extract wasm from Unstructured data"); | ||
run_fuzzer(module); | ||
run_fuzzer(arbitrary_str.as_bytes()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.