/src/wasmi/fuzz/fuzz_targets/differential.rs
Line | Count | Source |
1 | | #![no_main] |
2 | | |
3 | | use arbitrary::{Arbitrary, Unstructured}; |
4 | | use libfuzzer_sys::fuzz_target; |
5 | | use wasmi::ValType; |
6 | | use wasmi_fuzz::{ |
7 | | FuzzError, |
8 | | FuzzModule, |
9 | | FuzzVal, |
10 | | FuzzValType, |
11 | | config::FuzzSmithConfig, |
12 | | oracle::{ |
13 | | ChosenOracle, |
14 | | DifferentialOracle, |
15 | | DifferentialOracleMeta, |
16 | | ModuleExports, |
17 | | WasmiOracle, |
18 | | }, |
19 | | }; |
20 | | |
21 | | /// Fuzzing input for differential fuzzing. |
22 | | #[derive(Debug)] |
23 | | pub struct FuzzInput<'a> { |
24 | | /// The chosen Wasm runtime oracle to compare against Wasmi. |
25 | | chosen_oracle: ChosenOracle, |
26 | | /// The fuzzed Wasm module and its configuration. |
27 | | module: FuzzModule, |
28 | | /// Additional unstructured input data used to initialize call parameter etc. |
29 | | u: Unstructured<'a>, |
30 | | } |
31 | | |
32 | | impl<'a> Arbitrary<'a> for FuzzInput<'a> { |
33 | 13.7k | fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { |
34 | 13.7k | let mut fuzz_config = FuzzSmithConfig::arbitrary(u)?; |
35 | 13.7k | fuzz_config.allow_execution(); |
36 | 13.7k | fuzz_config.enable_nan_canonicalization(); |
37 | 13.7k | fuzz_config.export_everything(); |
38 | 13.7k | let chosen_oracle = ChosenOracle::arbitrary(u).unwrap_or_default(); |
39 | 13.7k | WasmiOracle::configure(&mut fuzz_config); |
40 | 13.7k | chosen_oracle.configure(&mut fuzz_config); |
41 | 13.7k | let smith_config: wasm_smith::Config = fuzz_config.into(); |
42 | 13.7k | let mut smith_module = FuzzModule::new(smith_config, u)?; |
43 | 13.6k | smith_module.ensure_termination(1_000 /* fuel */); |
44 | 13.6k | Ok(Self { |
45 | 13.6k | chosen_oracle, |
46 | 13.6k | module: smith_module, |
47 | 13.6k | u: Unstructured::new(&[]), |
48 | 13.6k | }) |
49 | 13.7k | } |
50 | | |
51 | 13.7k | fn arbitrary_take_rest(mut u: Unstructured<'a>) -> arbitrary::Result<Self> { |
52 | 13.7k | Self::arbitrary(&mut u).map(|mut input| { |
53 | 13.6k | input.u = u; |
54 | 13.6k | input |
55 | 13.6k | }) |
56 | 13.7k | } |
57 | | } |
58 | | |
59 | | fuzz_target!(|input: FuzzInput| { |
60 | | let Some(mut state) = FuzzState::setup(input) else { |
61 | | return; |
62 | | }; |
63 | | state.run() |
64 | | }); |
65 | | |
66 | | /// The state required to drive the differential fuzzer for a single run. |
67 | | pub struct FuzzState<'a> { |
68 | | wasmi_oracle: WasmiOracle, |
69 | | chosen_oracle: Box<dyn DifferentialOracle>, |
70 | | wasm: Box<[u8]>, |
71 | | u: Unstructured<'a>, |
72 | | } |
73 | | |
74 | | impl<'a> FuzzState<'a> { |
75 | | /// Sets up the oracles for the differential fuzzing if possible. |
76 | 13.6k | pub fn setup(input: FuzzInput<'a>) -> Option<Self> { |
77 | 13.6k | let wasm = input.module.wasm().into_bytes(); |
78 | 13.6k | let wasmi_oracle = WasmiOracle::setup(&wasm[..])?; |
79 | 11.9k | let chosen_oracle = input.chosen_oracle.setup(&wasm[..])?; |
80 | 11.9k | Some(Self { |
81 | 11.9k | wasm, |
82 | 11.9k | wasmi_oracle, |
83 | 11.9k | chosen_oracle, |
84 | 11.9k | u: input.u, |
85 | 11.9k | }) |
86 | 13.6k | } |
87 | | |
88 | | /// Performs the differential fuzzing. |
89 | 11.9k | pub fn run(&mut self) { |
90 | 11.9k | let exports = self.wasmi_oracle.exports(); |
91 | 11.9k | let mut params = Vec::new(); |
92 | 256k | for (name, func_type) in exports.funcs() { |
93 | 256k | self.init_params(&mut params, func_type.params()); |
94 | 256k | let params = ¶ms[..]; |
95 | 256k | let result_wasmi = self.wasmi_oracle.call(name, params); |
96 | 256k | let result_oracle = self.chosen_oracle.call(name, params); |
97 | | // Note: If either of the oracles returns a non-deterministic error we skip the |
98 | | // entire fuzz run since following function executions could be affected by |
99 | | // this non-determinism due to shared global state, such as global variables. |
100 | 256k | if let Err(wasmi_err) = &result_wasmi |
101 | 150k | && wasmi_err.is_non_deterministic() |
102 | | { |
103 | 0 | return; |
104 | 256k | } |
105 | 256k | if let Err(oracle_err) = &result_oracle |
106 | 150k | && oracle_err.is_non_deterministic() |
107 | | { |
108 | 53 | return; |
109 | 256k | } |
110 | 256k | match (result_wasmi, result_oracle) { |
111 | 106k | (Ok(wasmi_results), Ok(oracle_results)) => { |
112 | 106k | self.assert_results_match(name, params, &wasmi_results, &oracle_results); |
113 | 106k | self.assert_globals_match(&exports); |
114 | 106k | self.assert_memories_match(&exports); |
115 | 106k | } |
116 | 150k | (Err(wasmi_err), Err(oracle_err)) => { |
117 | 150k | self.assert_errors_match(name, params, wasmi_err, oracle_err); |
118 | 150k | self.assert_globals_match(&exports); |
119 | 150k | self.assert_memories_match(&exports); |
120 | 150k | } |
121 | 0 | (wasmi_results, oracle_results) => { |
122 | 0 | self.report_divergent_behavior(name, params, &wasmi_results, &oracle_results) |
123 | | } |
124 | | } |
125 | | } |
126 | 11.9k | } |
127 | | |
128 | | /// Fill [`FuzzVal`]s of type `src` into `dst` using `u` for initialization. |
129 | | /// |
130 | | /// Clears `dst` before the operation. |
131 | 256k | fn init_params(&mut self, dst: &mut Vec<FuzzVal>, src: &[ValType]) { |
132 | 256k | dst.clear(); |
133 | 256k | dst.extend( |
134 | 256k | src.iter() |
135 | 256k | .copied() |
136 | 256k | .map(FuzzValType::from) |
137 | 866k | .map(|ty| FuzzVal::with_type(ty, &mut self.u)), |
138 | | ); |
139 | 256k | } |
140 | | |
141 | | /// Asserts that the call results is equal for both oracles. |
142 | 106k | fn assert_results_match( |
143 | 106k | &self, |
144 | 106k | func_name: &str, |
145 | 106k | params: &[FuzzVal], |
146 | 106k | wasmi_results: &[FuzzVal], |
147 | 106k | oracle_results: &[FuzzVal], |
148 | 106k | ) { |
149 | 106k | if wasmi_results == oracle_results { |
150 | 106k | return; |
151 | 0 | } |
152 | 0 | let wasmi_name = self.wasmi_oracle.name(); |
153 | 0 | let oracle_name = self.chosen_oracle.name(); |
154 | 0 | let crash_input = self.generate_crash_inputs(); |
155 | 0 | panic!( |
156 | 0 | "\ |
157 | 0 | function call returned different values:\n\ |
158 | 0 | \tfunc: {func_name}\n\ |
159 | 0 | \tparams: {params:?}\n\ |
160 | 0 | \t{wasmi_name}: {wasmi_results:?}\n\ |
161 | 0 | \t{oracle_name}: {oracle_results:?}\n\ |
162 | 0 | \tcrash-report: 0x{crash_input}\n\ |
163 | 0 | " |
164 | | ) |
165 | 106k | } |
166 | | |
167 | | /// Asserts that the call results is equal for both oracles. |
168 | 150k | fn assert_errors_match( |
169 | 150k | &self, |
170 | 150k | func_name: &str, |
171 | 150k | params: &[FuzzVal], |
172 | 150k | wasmi_err: FuzzError, |
173 | 150k | oracle_err: FuzzError, |
174 | 150k | ) { |
175 | 150k | if wasmi_err == oracle_err { |
176 | 150k | return; |
177 | 0 | } |
178 | 0 | let wasmi_name = self.wasmi_oracle.name(); |
179 | 0 | let oracle_name = self.chosen_oracle.name(); |
180 | 0 | let crash_input = self.generate_crash_inputs(); |
181 | 0 | panic!( |
182 | 0 | "\ |
183 | 0 | function call returned different errors:\n\ |
184 | 0 | \tfunc: {func_name}\n\ |
185 | 0 | \tparams: {params:?}\n\ |
186 | 0 | \t{wasmi_name}: {wasmi_err:?}\n\ |
187 | 0 | \t{oracle_name}: {oracle_err:?}\n\ |
188 | 0 | \tcrash-report: 0x{crash_input}\n\ |
189 | 0 | " |
190 | | ) |
191 | 150k | } |
192 | | |
193 | | /// Asserts that the global variable state is equal in both oracles. |
194 | 256k | fn assert_globals_match(&mut self, exports: &ModuleExports) { |
195 | 954k | for name in exports.globals() { |
196 | 954k | let wasmi_val = self.wasmi_oracle.get_global(name); |
197 | 954k | let oracle_val = self.chosen_oracle.get_global(name); |
198 | 954k | if wasmi_val == oracle_val { |
199 | 954k | continue; |
200 | 0 | } |
201 | 0 | let wasmi_name = self.wasmi_oracle.name(); |
202 | 0 | let oracle_name = self.chosen_oracle.name(); |
203 | 0 | let crash_input = self.generate_crash_inputs(); |
204 | 0 | panic!( |
205 | 0 | "\ |
206 | 0 | encountered unequal globals:\n\ |
207 | 0 | \tglobal: {name}\n\ |
208 | 0 | \t{wasmi_name}: {wasmi_val:?}\n\ |
209 | 0 | \t{oracle_name}: {oracle_val:?}\n\ |
210 | 0 | \tcrash-report: 0x{crash_input}\n\ |
211 | 0 | " |
212 | | ) |
213 | | } |
214 | 256k | } |
215 | | |
216 | | /// Asserts that the linear memory state is equal in both oracles. |
217 | 256k | fn assert_memories_match(&mut self, exports: &ModuleExports) { |
218 | 447k | for name in exports.memories() { |
219 | 447k | let Some(wasmi_mem) = self.wasmi_oracle.get_memory(name) else { |
220 | 0 | continue; |
221 | | }; |
222 | 447k | let Some(oracle_mem) = self.chosen_oracle.get_memory(name) else { |
223 | 0 | continue; |
224 | | }; |
225 | 447k | if wasmi_mem == oracle_mem { |
226 | 447k | continue; |
227 | 0 | } |
228 | 0 | let mut first_nonmatching = 0; |
229 | 0 | let mut byte_wasmi = 0; |
230 | 0 | let mut byte_oracle = 0; |
231 | 0 | for (n, (mem0, mem1)) in wasmi_mem.iter().zip(oracle_mem).enumerate() { |
232 | 0 | if mem0 != mem1 { |
233 | 0 | first_nonmatching = n; |
234 | 0 | byte_wasmi = *mem0; |
235 | 0 | byte_oracle = *mem1; |
236 | 0 | break; |
237 | 0 | } |
238 | | } |
239 | 0 | let wasmi_name = self.wasmi_oracle.name(); |
240 | 0 | let oracle_name = self.chosen_oracle.name(); |
241 | 0 | let crash_input = self.generate_crash_inputs(); |
242 | 0 | panic!( |
243 | 0 | "\ |
244 | 0 | encountered unequal memories:\n\ |
245 | 0 | \tmemory: {name}\n\ |
246 | 0 | \tindex first non-matching: {first_nonmatching}\n\ |
247 | 0 | \t{wasmi_name}: {byte_wasmi:?}\n\ |
248 | 0 | \t{oracle_name}: {byte_oracle:?}\n\ |
249 | 0 | \tcrash-report: 0x{crash_input}\n\ |
250 | 0 | " |
251 | | ) |
252 | | } |
253 | 256k | } |
254 | | |
255 | | /// Reports divergent behavior between Wasmi and the chosen oracle. |
256 | 0 | fn report_divergent_behavior( |
257 | 0 | &self, |
258 | 0 | func_name: &str, |
259 | 0 | params: &[FuzzVal], |
260 | 0 | wasmi_result: &Result<Box<[FuzzVal]>, FuzzError>, |
261 | 0 | oracle_result: &Result<Box<[FuzzVal]>, FuzzError>, |
262 | 0 | ) { |
263 | 0 | assert!(matches!( |
264 | 0 | (&wasmi_result, &oracle_result), |
265 | | (Ok(_), Err(_)) | (Err(_), Ok(_)) |
266 | | )); |
267 | 0 | let wasmi_name = self.wasmi_oracle.name(); |
268 | 0 | let oracle_name = self.chosen_oracle.name(); |
269 | 0 | let wasmi_state = match wasmi_result { |
270 | 0 | Ok(_) => "returns result", |
271 | 0 | Err(_) => "traps", |
272 | | }; |
273 | 0 | let oracle_state = match oracle_result { |
274 | 0 | Ok(_) => "returns result", |
275 | 0 | Err(_) => "traps", |
276 | | }; |
277 | 0 | let crash_input = self.generate_crash_inputs(); |
278 | 0 | panic!( |
279 | 0 | "\ |
280 | 0 | function call {wasmi_state} for {wasmi_name} and {oracle_state} for {oracle_name}:\n\ |
281 | 0 | \tfunc: {func_name}\n\ |
282 | 0 | \tparams: {params:?}\n\ |
283 | 0 | \t{wasmi_name}: {wasmi_result:?}\n\ |
284 | 0 | \t{oracle_name}: {oracle_result:?}\n\ |
285 | 0 | \tcrash-report: 0x{crash_input}\n\ |
286 | 0 | " |
287 | | ) |
288 | | } |
289 | | |
290 | | /// Generate crash input reports for `differential` fuzzing.` |
291 | | #[track_caller] |
292 | 0 | fn generate_crash_inputs(&self) -> String { |
293 | 0 | wasmi_fuzz::generate_crash_inputs("differential", &self.wasm).unwrap() |
294 | 0 | } |
295 | | } |