1 #![no_main]
2
3 use libfuzzer_sys::arbitrary::{self, Result, Unstructured};
4 use libfuzzer_sys::fuzz_target;
5 use std::sync::atomic::AtomicUsize;
6 use std::sync::atomic::Ordering::SeqCst;
7 use std::sync::{Mutex, Once};
8 use wasmtime_fuzzing::generators::{Config, DiffValue, DiffValueType, SingleInstModule};
9 use wasmtime_fuzzing::oracles::diff_wasmtime::WasmtimeInstance;
10 use wasmtime_fuzzing::oracles::engine::{build_allowed_env_list, parse_env_list};
11 use wasmtime_fuzzing::oracles::{DiffEqResult, differential, engine, log_wasm};
12
13 // Upper limit on the number of invocations for each WebAssembly function
14 // executed by this fuzz target.
15 const NUM_INVOCATIONS: usize = 5;
16
17 // Only run once when the fuzz target loads.
18 static SETUP: Once = Once::new();
19
20 // Environment-specified configuration for controlling the kinds of engines and
21 // modules used by this fuzz target. E.g.:
22 // - ALLOWED_ENGINES=wasmi,spec cargo +nightly fuzz run ...
23 // - ALLOWED_ENGINES=-v8 cargo +nightly fuzz run ...
24 // - ALLOWED_MODULES=single-inst cargo +nightly fuzz run ...
25 static ALLOWED_ENGINES: Mutex<Vec<Option<&str>>> = Mutex::new(vec![]);
26 static ALLOWED_MODULES: Mutex<Vec<Option<&str>>> = Mutex::new(vec![]);
27
28 // Statistics about what's actually getting executed during fuzzing
29 static STATS: RuntimeStats = RuntimeStats::new();
30
31 fuzz_target!(|data: &[u8]| {
32 SETUP.call_once(|| {
33 // To avoid a uncaught `SIGSEGV` due to signal handlers; see comments on
34 // `setup_ocaml_runtime`.
35 engine::setup_engine_runtimes();
36
37 // Retrieve the configuration for this fuzz target from `ALLOWED_*`
38 // environment variables.
39 let allowed_engines = build_allowed_env_list(
40 parse_env_list("ALLOWED_ENGINES"),
41 &["wasmtime", "wasmi", "spec", "v8", "winch", "pulley"],
42 );
43 let allowed_modules = build_allowed_env_list(
44 parse_env_list("ALLOWED_MODULES"),
45 &["wasm-smith", "single-inst"],
46 );
47
48 *ALLOWED_ENGINES.lock().unwrap() = allowed_engines;
49 *ALLOWED_MODULES.lock().unwrap() = allowed_modules;
50 });
51
52 // Errors in `run` have to do with not enough input in `data`, which we
53 // ignore here since it doesn't affect how we'd like to fuzz.
54 let _ = execute_one(&data);
55 });
56
execute_one(data: &[u8]) -> Result<()>57 fn execute_one(data: &[u8]) -> Result<()> {
58 wasmtime_fuzzing::init_fuzzing();
59 STATS.bump_attempts();
60
61 let mut u = Unstructured::new(data);
62
63 // Generate a Wasmtime and module configuration and update its settings
64 // initially to be suitable for differential execution where the generated
65 // wasm will behave the same in two different engines. This will get further
66 // refined below.
67 let mut config: Config = u.arbitrary()?;
68 config.set_differential_config();
69
70 let allowed_engines = ALLOWED_ENGINES.lock().unwrap();
71 let allowed_modules = ALLOWED_MODULES.lock().unwrap();
72
73 // Choose an engine that Wasmtime will be differentially executed against.
74 // The chosen engine is then created, which might update `config`, and
75 // returned as a trait object.
76 let lhs = match *u.choose(&allowed_engines)? {
77 Some(engine) => engine,
78 None => {
79 log::debug!("test case uses a runtime-disabled engine");
80 return Ok(());
81 }
82 };
83
84 log::trace!("Building LHS engine");
85 let mut lhs = match engine::build(&mut u, lhs, &mut config)? {
86 Some(engine) => engine,
87 // The chosen engine does not have support compiled into the fuzzer,
88 // discard this test case.
89 None => return Ok(()),
90 };
91 log::debug!("lhs engine: {}", lhs.name());
92
93 // Using the now-legalized module configuration generate the Wasm module;
94 // this is specified by either the ALLOWED_MODULES environment variable or a
95 // random selection between wasm-smith and single-inst.
96 let build_wasm_smith_module = |u: &mut Unstructured, config: &Config| -> Result<_> {
97 log::debug!("build wasm-smith with {config:?}");
98 STATS.wasm_smith_modules.fetch_add(1, SeqCst);
99 let module = config.generate(u, Some(1000))?;
100 Ok(module.to_bytes())
101 };
102 let build_single_inst_module = |u: &mut Unstructured, config: &Config| -> Result<_> {
103 log::debug!("build single-inst with {config:?}");
104 STATS.single_instruction_modules.fetch_add(1, SeqCst);
105 let module = SingleInstModule::new(u, &config.module_config)?;
106 Ok(module.to_bytes())
107 };
108 if allowed_modules.is_empty() {
109 panic!("unable to generate a module to fuzz against; check `ALLOWED_MODULES`")
110 }
111 let wasm = match *u.choose(&allowed_modules)? {
112 Some("wasm-smith") => build_wasm_smith_module(&mut u, &config)?,
113 Some("single-inst") => build_single_inst_module(&mut u, &config)?,
114 None => {
115 log::debug!("test case uses a runtime-disabled module strategy");
116 return Ok(());
117 }
118 _ => unreachable!(),
119 };
120
121 log_wasm(&wasm);
122
123 // Instantiate the generated wasm file in the chosen differential engine.
124 let lhs_instance = lhs.instantiate(&wasm);
125 STATS.bump_engine(lhs.name());
126
127 // Always use Wasmtime as the second engine to instantiate within.
128 log::debug!("Building RHS Wasmtime");
129 let rhs_store = config.to_store();
130 let rhs_module = wasmtime::Module::new(rhs_store.engine(), &wasm).unwrap();
131 let rhs_instance = WasmtimeInstance::new(rhs_store, rhs_module);
132
133 let (mut lhs_instance, mut rhs_instance) =
134 match DiffEqResult::new(&*lhs, lhs_instance, rhs_instance) {
135 // Both sides successful, continue below to invoking exports.
136 DiffEqResult::Success(l, r) => (l, r),
137
138 // Both sides failed, or computation has diverged. In both cases this
139 // test case is done.
140 DiffEqResult::Poisoned | DiffEqResult::Failed => return Ok(()),
141 };
142
143 // Call each exported function with different sets of arguments.
144 'outer: for (name, signature) in rhs_instance.exported_functions() {
145 let mut invocations = 0;
146 loop {
147 let arguments = match signature
148 .params()
149 .map(|ty| {
150 let ty = ty
151 .try_into()
152 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
153 DiffValue::arbitrary_of_type(&mut u, ty)
154 })
155 .collect::<Result<Vec<_>>>()
156 {
157 Ok(args) => args,
158 // This function signature isn't compatible with differential
159 // fuzzing yet, try the next exported function in the meantime.
160 Err(_) => continue 'outer,
161 };
162
163 let result_tys = match signature
164 .results()
165 .map(|ty| {
166 DiffValueType::try_from(ty).map_err(|_| arbitrary::Error::IncorrectFormat)
167 })
168 .collect::<Result<Vec<_>>>()
169 {
170 Ok(tys) => tys,
171 // This function signature isn't compatible with differential
172 // fuzzing yet, try the next exported function in the meantime.
173 Err(_) => continue 'outer,
174 };
175
176 let ok = differential(
177 lhs_instance.as_mut(),
178 lhs.as_ref(),
179 &mut rhs_instance,
180 &name,
181 &arguments,
182 &result_tys,
183 )
184 .expect("failed to run differential evaluation");
185
186 invocations += 1;
187 STATS.total_invocations.fetch_add(1, SeqCst);
188
189 // If this differential execution has resulted in the two instances
190 // diverging in state we can't keep executing so don't execute any
191 // more functions.
192 if !ok {
193 break 'outer;
194 }
195
196 // We evaluate the same function with different arguments until we
197 // Hit a predetermined limit or we run out of unstructured data--it
198 // does not make sense to re-evaluate the same arguments over and
199 // over.
200 if invocations > NUM_INVOCATIONS || u.is_empty() {
201 break;
202 }
203 }
204 }
205
206 STATS.successes.fetch_add(1, SeqCst);
207 Ok(())
208 }
209
210 #[derive(Default)]
211 struct RuntimeStats {
212 /// Total number of fuzz inputs processed
213 attempts: AtomicUsize,
214
215 /// Number of times we've invoked engines
216 total_invocations: AtomicUsize,
217
218 /// Number of times a fuzz input finished all the way to the end without any
219 /// sort of error (including `Arbitrary` errors)
220 successes: AtomicUsize,
221
222 // Counters for which engine was chosen
223 wasmi: AtomicUsize,
224 v8: AtomicUsize,
225 spec: AtomicUsize,
226 wasmtime: AtomicUsize,
227 winch: AtomicUsize,
228 pulley: AtomicUsize,
229
230 // Counters for which style of module is chosen
231 wasm_smith_modules: AtomicUsize,
232 single_instruction_modules: AtomicUsize,
233 }
234
235 impl RuntimeStats {
new() -> RuntimeStats236 const fn new() -> RuntimeStats {
237 RuntimeStats {
238 attempts: AtomicUsize::new(0),
239 total_invocations: AtomicUsize::new(0),
240 successes: AtomicUsize::new(0),
241 wasmi: AtomicUsize::new(0),
242 v8: AtomicUsize::new(0),
243 spec: AtomicUsize::new(0),
244 wasmtime: AtomicUsize::new(0),
245 winch: AtomicUsize::new(0),
246 pulley: AtomicUsize::new(0),
247 wasm_smith_modules: AtomicUsize::new(0),
248 single_instruction_modules: AtomicUsize::new(0),
249 }
250 }
251
bump_attempts(&self)252 fn bump_attempts(&self) {
253 let attempts = self.attempts.fetch_add(1, SeqCst);
254 if attempts == 0 || attempts % 1_000 != 0 {
255 return;
256 }
257 let successes = self.successes.load(SeqCst);
258 println!(
259 "=== Execution rate ({} successes / {} attempted modules): {:.02}% ===",
260 successes,
261 attempts,
262 successes as f64 / attempts as f64 * 100f64,
263 );
264
265 let v8 = self.v8.load(SeqCst);
266 let spec = self.spec.load(SeqCst);
267 let wasmi = self.wasmi.load(SeqCst);
268 let wasmtime = self.wasmtime.load(SeqCst);
269 let winch = self.winch.load(SeqCst);
270 let pulley = self.pulley.load(SeqCst);
271 let total = v8 + spec + wasmi + wasmtime + winch + pulley;
272 println!(
273 "\twasmi: {:.02}%, spec: {:.02}%, wasmtime: {:.02}%, v8: {:.02}%, \
274 winch: {:.02}%, \
275 pulley: {:.02}%",
276 wasmi as f64 / total as f64 * 100f64,
277 spec as f64 / total as f64 * 100f64,
278 wasmtime as f64 / total as f64 * 100f64,
279 v8 as f64 / total as f64 * 100f64,
280 winch as f64 / total as f64 * 100f64,
281 pulley as f64 / total as f64 * 100f64,
282 );
283
284 let wasm_smith = self.wasm_smith_modules.load(SeqCst);
285 let single_inst = self.single_instruction_modules.load(SeqCst);
286 let total = wasm_smith + single_inst;
287 println!(
288 "\twasm-smith: {:.02}%, single-inst: {:.02}%",
289 wasm_smith as f64 / total as f64 * 100f64,
290 single_inst as f64 / total as f64 * 100f64,
291 );
292 }
293
bump_engine(&self, name: &str)294 fn bump_engine(&self, name: &str) {
295 match name {
296 "wasmi" => self.wasmi.fetch_add(1, SeqCst),
297 "wasmtime" => self.wasmtime.fetch_add(1, SeqCst),
298 "spec" => self.spec.fetch_add(1, SeqCst),
299 "v8" => self.v8.fetch_add(1, SeqCst),
300 "winch" => self.winch.fetch_add(1, SeqCst),
301 "pulley" => self.pulley.fetch_add(1, SeqCst),
302 _ => return,
303 };
304 }
305 }
306