//! A simple fuzzer for FACT //! //! This is an intentionally small fuzzer which is intended to only really be //! used during the development of FACT itself when generating adapter modules. //! This creates arbitrary adapter signatures and then generates the required //! trampoline for that adapter ensuring that the final output wasm module is a //! valid wasm module. This doesn't actually validate anything about the //! correctness of the trampoline, only that it's valid wasm. #![no_main] use arbitrary::Arbitrary; use component_fuzz_util::TestCase; use libfuzzer_sys::fuzz_target; use wasmparser::{Parser, Payload, Validator, WasmFeatures}; use wasmtime_environ::component::*; use wasmtime_environ::fact::Module; #[derive(Arbitrary, Debug)] struct GenAdapterModule { debug: bool, adapters: Vec, } #[derive(Arbitrary, Debug)] struct GenAdapter { post_return: bool, lift_memory64: bool, lower_memory64: bool, test: TestCase, } fuzz_target!(|module: GenAdapterModule| { target(module); }); fn target(module: GenAdapterModule) { drop(env_logger::try_init()); let mut types = ComponentTypesBuilder::default(); // Manufactures a unique `CoreDef` so all function imports get unique // function imports. let mut next_def = 0; let mut dummy_def = || { next_def += 1; dfg::CoreDef::Adapter(dfg::AdapterId::from_u32(next_def)) }; // Manufactures a `CoreExport` for a memory with the shape specified. Note // that we can't import as many memories as functions so these are // intentionally limited. Once a handful of memories are generated of each // type then they start getting reused. let mut next_memory = 0; let mut memories32 = Vec::new(); let mut memories64 = Vec::new(); let mut dummy_memory = |memory64: bool| { let dst = if memory64 { &mut memories64 } else { &mut memories32 }; let idx = if dst.len() < 5 { next_memory += 1; dst.push(next_memory - 1); next_memory - 1 } else { dst[0] }; dfg::CoreExport { instance: dfg::InstanceId::from_u32(idx), item: ExportItem::Name(String::new()), } }; let mut adapters = Vec::new(); for adapter in module.adapters.iter() { let wat_decls = adapter.test.declarations(); let wat = format!( "(component {types} (type (func {params} {results})) )", types = wat_decls.types, params = wat_decls.params, results = wat_decls.results, ); let wasm = wat::parse_str(&wat).unwrap(); let mut validator = Validator::new_with_features(WasmFeatures { component_model: true, ..Default::default() }); types.push_type_scope(); for payload in Parser::new(0).parse_all(&wasm) { let payload = payload.unwrap(); validator.payload(&payload).unwrap(); let section = match payload { Payload::ComponentTypeSection(s) => s, _ => continue, }; for ty in section { let ty = types.intern_component_type(&ty.unwrap()).unwrap(); types.push_component_typedef(ty); let ty = match ty { TypeDef::ComponentFunc(ty) => ty, _ => continue, }; adapters.push(Adapter { lift_ty: ty, lower_ty: ty, lower_options: AdapterOptions { instance: RuntimeComponentInstanceIndex::from_u32(0), string_encoding: convert_encoding(adapter.test.encoding1), memory64: adapter.lower_memory64, // Pessimistically assume that memory/realloc are going to be // required for this trampoline and provide it. Avoids doing // calculations to figure out whether they're necessary and // simplifies the fuzzer here without reducing coverage within FACT // itself. memory: Some(dummy_memory(adapter.lower_memory64)), realloc: Some(dummy_def()), // Lowering never allows `post-return` post_return: None, }, lift_options: AdapterOptions { instance: RuntimeComponentInstanceIndex::from_u32(1), string_encoding: convert_encoding(adapter.test.encoding2), memory64: adapter.lift_memory64, memory: Some(dummy_memory(adapter.lift_memory64)), realloc: Some(dummy_def()), post_return: if adapter.post_return { Some(dummy_def()) } else { None }, }, func: dummy_def(), }); } } types.pop_type_scope(); } let mut fact_module = Module::new(&types, module.debug); for (i, adapter) in adapters.iter().enumerate() { fact_module.adapt(&format!("adapter{i}"), adapter); } let wasm = fact_module.encode(); let result = Validator::new_with_features(WasmFeatures { multi_memory: true, memory64: true, ..WasmFeatures::default() }) .validate_all(&wasm); let err = match result { Ok(_) => return, Err(e) => e, }; eprintln!("invalid wasm module: {err:?}"); for adapter in module.adapters.iter() { eprintln!("adapter: {adapter:?}"); } std::fs::write("invalid.wasm", &wasm).unwrap(); match wasmprinter::print_bytes(&wasm) { Ok(s) => std::fs::write("invalid.wat", &s).unwrap(), Err(_) => drop(std::fs::remove_file("invalid.wat")), } panic!() } fn convert_encoding(encoding: component_fuzz_util::StringEncoding) -> StringEncoding { match encoding { component_fuzz_util::StringEncoding::Utf8 => StringEncoding::Utf8, component_fuzz_util::StringEncoding::Utf16 => StringEncoding::Utf16, component_fuzz_util::StringEncoding::Latin1OrUtf16 => StringEncoding::CompactUtf16, } }