1 #![no_main] 2 3 use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured}; 4 use libfuzzer_sys::fuzz_target; 5 use std::sync::OnceLock; 6 7 // Helper macro which takes a static list of fuzzers as input which are then 8 // delegated to internally based on the fuzz target selected. 9 // 10 // In general this fuzz target will execute a number of fuzzers all with the 11 // same input. The `FUZZER` environment variable can be used to forcibly disable 12 // all but one. 13 macro_rules! run_fuzzers { 14 ($($fuzzer:ident)*) => { 15 static ENABLED: OnceLock<u32> = OnceLock::new(); 16 17 fuzz_target!( 18 init: wasmtime_fuzzing::misc_init(), 19 |bytes: &[u8]| { 20 // Use the first byte of input as a discriminant of which fuzzer to 21 // select. 22 let Some((which_fuzzer, bytes)) = bytes.split_first() else { 23 return; 24 }; 25 26 // Lazily initialize this fuzzer in terms of logging as well as 27 // enabled fuzzers via the `FUZZER` env var. This creates a bitmask 28 // inside of `ENABLED` of enabled fuzzers, returned here as 29 // `enabled`. 30 let enabled = *ENABLED.get_or_init(|| { 31 let configured = std::env::var("FUZZER").ok(); 32 let configured = configured.as_deref(); 33 let mut enabled = 0; 34 let mut index = 0; 35 36 $( 37 if configured.is_none() || configured == Some(stringify!($fuzzer)) { 38 enabled |= 1 << index; 39 } 40 index += 1; 41 )* 42 let _ = index; 43 44 enabled 45 }); 46 47 // Generate a linear check for each fuzzer. Only run each fuzzer if 48 // the fuzzer is enabled, and also only if the `which_fuzzer` 49 // discriminant matches the fuzzer being run. 50 // 51 // Note that it's a bit wonky here due to rust macros. 52 let mut index = 0; 53 $( 54 if enabled & (1 << index) != 0 && *which_fuzzer == index { 55 let _: Result<()> = $fuzzer(Unstructured::new(bytes)); 56 } 57 index += 1; 58 )* 59 let _ = index; 60 }); 61 }; 62 } 63 64 run_fuzzers! { 65 pulley_roundtrip 66 assembler_roundtrip 67 memory_accesses 68 stacks 69 api_calls 70 dominator_tree 71 component_async 72 } 73 74 fn pulley_roundtrip(u: Unstructured<'_>) -> Result<()> { 75 pulley_interpreter_fuzz::roundtrip(Arbitrary::arbitrary_take_rest(u)?); 76 Ok(()) 77 } 78 79 fn assembler_roundtrip(u: Unstructured<'_>) -> Result<()> { 80 use cranelift_assembler_x64::{Inst, fuzz}; 81 let inst: Inst<fuzz::FuzzRegs> = Arbitrary::arbitrary_take_rest(u)?; 82 fuzz::roundtrip(&inst); 83 Ok(()) 84 } 85 86 fn memory_accesses(u: Unstructured<'_>) -> Result<()> { 87 wasmtime_fuzzing::oracles::memory::check_memory_accesses(Arbitrary::arbitrary_take_rest(u)?); 88 Ok(()) 89 } 90 91 fn stacks(u: Unstructured<'_>) -> Result<()> { 92 wasmtime_fuzzing::oracles::check_stacks(Arbitrary::arbitrary_take_rest(u)?); 93 Ok(()) 94 } 95 96 fn api_calls(u: Unstructured<'_>) -> Result<()> { 97 wasmtime_fuzzing::oracles::make_api_calls(Arbitrary::arbitrary_take_rest(u)?); 98 Ok(()) 99 } 100 101 fn dominator_tree(mut data: Unstructured<'_>) -> Result<()> { 102 use cranelift_codegen::cursor::{Cursor, FuncCursor}; 103 use cranelift_codegen::dominator_tree::{DominatorTree, SimpleDominatorTree}; 104 use cranelift_codegen::flowgraph::ControlFlowGraph; 105 use cranelift_codegen::ir::{ 106 Block, BlockCall, Function, InstBuilder, JumpTableData, Value, types::I32, 107 }; 108 use std::collections::HashMap; 109 110 const MAX_BLOCKS: u16 = 1 << 12; 111 112 let mut func = Function::new(); 113 114 let mut num_to_block = Vec::new(); 115 116 let mut cfg = HashMap::<Block, Vec<Block>>::new(); 117 118 for edge in data.arbitrary_iter::<(u16, u16)>()? { 119 let (a, b) = edge?; 120 121 let a = a % MAX_BLOCKS; 122 let b = b % MAX_BLOCKS; 123 124 while a >= num_to_block.len() as u16 { 125 num_to_block.push(func.dfg.make_block()); 126 } 127 128 let a = num_to_block[a as usize]; 129 130 while b >= num_to_block.len() as u16 { 131 num_to_block.push(func.dfg.make_block()); 132 } 133 134 let b = num_to_block[b as usize]; 135 136 cfg.entry(a).or_default().push(b); 137 } 138 139 let mut cursor = FuncCursor::new(&mut func); 140 141 let mut v0: Option<Value> = None; 142 143 for block in num_to_block { 144 cursor.insert_block(block); 145 146 if v0.is_none() { 147 v0 = Some(cursor.ins().iconst(I32, 0)); 148 } 149 150 if let Some(children) = cfg.get(&block) { 151 if children.len() == 1 { 152 cursor.ins().jump(children[0], &[]); 153 } else { 154 let block_calls = children 155 .iter() 156 .map(|&block| { 157 BlockCall::new(block, core::iter::empty(), &mut cursor.func.dfg.value_lists) 158 }) 159 .collect::<Vec<_>>(); 160 161 let data = JumpTableData::new(block_calls[0], &block_calls[1..]); 162 let jt = cursor.func.create_jump_table(data); 163 cursor.ins().br_table(v0.unwrap(), jt); 164 } 165 } else { 166 cursor.ins().return_(&[]); 167 } 168 } 169 170 let cfg = ControlFlowGraph::with_function(&func); 171 let domtree = DominatorTree::with_function(&func, &cfg); 172 let expected_domtree = SimpleDominatorTree::with_function(&func, &cfg); 173 174 for block in func.layout.blocks() { 175 let expected = expected_domtree.idom(block); 176 let got = domtree.idom(block); 177 if expected != got { 178 panic!("Expected dominator for {block} is {expected:?}, got {got:?}"); 179 } 180 } 181 182 Ok(()) 183 } 184 185 fn component_async(u: Unstructured<'_>) -> Result<()> { 186 wasmtime_fuzzing::oracles::component_async::run(Arbitrary::arbitrary_take_rest(u)?); 187 Ok(()) 188 } 189