1 //! Define the interface for differential evaluation of Wasm functions. 2 3 use crate::generators::{Config, DiffValue, DiffValueType, WasmtimeConfig}; 4 use crate::oracles::{diff_wasmi::WasmiEngine, diff_wasmtime::WasmtimeEngine}; 5 use anyhow::Error; 6 use arbitrary::Unstructured; 7 use wasmtime::Trap; 8 9 /// Pick one of the engines implemented in this module that is: 10 /// - in the list of `allowed` engines 11 /// - can evaluate Wasm modules compatible with `existing_config`. 12 pub fn choose( 13 u: &mut Unstructured<'_>, 14 existing_config: &Config, 15 allowed: &[&str], 16 ) -> arbitrary::Result<Option<Box<dyn DiffEngine>>> { 17 // Filter out any engines that cannot match the `existing_config` or are not 18 // `allowed`. 19 let mut engines: Vec<Box<dyn DiffEngine>> = vec![]; 20 21 if allowed.contains(&"wasmtime") { 22 let mut new_wasmtime_config: WasmtimeConfig = u.arbitrary()?; 23 new_wasmtime_config.make_compatible_with(&existing_config.wasmtime); 24 let new_config = Config { 25 wasmtime: new_wasmtime_config, 26 module_config: existing_config.module_config.clone(), 27 }; 28 if let Result::Ok(e) = WasmtimeEngine::new(new_config) { 29 engines.push(Box::new(e)) 30 } 31 } 32 33 if allowed.contains(&"wasmi") { 34 if let Result::Ok(e) = WasmiEngine::new(&existing_config.module_config) { 35 engines.push(Box::new(e)) 36 } 37 } 38 39 #[cfg(feature = "fuzz-spec-interpreter")] 40 if allowed.contains(&"spec") { 41 if let Result::Ok(e) = 42 crate::oracles::diff_spec::SpecInterpreter::new(&existing_config.module_config) 43 { 44 engines.push(Box::new(e)) 45 } 46 } 47 48 #[cfg(not(any(windows, target_arch = "s390x")))] 49 if allowed.contains(&"v8") { 50 if let Result::Ok(e) = 51 crate::oracles::diff_v8::V8Engine::new(&existing_config.module_config) 52 { 53 engines.push(Box::new(e)) 54 } 55 } 56 57 if engines.is_empty() { 58 return Ok(None); 59 } 60 61 // Use the input of the fuzzer to pick an engine that we'll be fuzzing 62 // Wasmtime against. 63 let index: usize = u.int_in_range(0..=engines.len() - 1)?; 64 let engine = engines.swap_remove(index); 65 log::debug!("selected engine: {}", engine.name()); 66 Ok(Some(engine)) 67 } 68 69 /// Provide a way to instantiate Wasm modules. 70 pub trait DiffEngine { 71 /// Return the name of the engine. 72 fn name(&self) -> &'static str; 73 74 /// Create a new instance with the given engine. 75 fn instantiate(&mut self, wasm: &[u8]) -> anyhow::Result<Box<dyn DiffInstance>>; 76 77 /// Tests that the wasmtime-originating `trap` matches the error this engine 78 /// generated. 79 fn assert_error_match(&self, trap: &Trap, err: &Error); 80 81 /// Returns whether the error specified from this engine might be stack 82 /// overflow. 83 fn is_stack_overflow(&self, err: &Error) -> bool; 84 } 85 86 /// Provide a way to evaluate Wasm functions--a Wasm instance implemented by a 87 /// specific engine (i.e., compiler or interpreter). 88 pub trait DiffInstance { 89 /// Return the name of the engine behind this instance. 90 fn name(&self) -> &'static str; 91 92 /// Evaluate an exported function with the given values. 93 /// 94 /// Any error, such as a trap, should be returned through an `Err`. If this 95 /// engine cannot invoke the function signature then `None` should be 96 /// returned and this invocation will be skipped. 97 fn evaluate( 98 &mut self, 99 function_name: &str, 100 arguments: &[DiffValue], 101 results: &[DiffValueType], 102 ) -> anyhow::Result<Option<Vec<DiffValue>>>; 103 104 /// Attempts to return the value of the specified global, returning `None` 105 /// if this engine doesn't support retrieving globals at this time. 106 fn get_global(&mut self, name: &str, ty: DiffValueType) -> Option<DiffValue>; 107 108 /// Same as `get_global` but for memory. 109 fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>>; 110 } 111 112 /// Initialize any global state associated with runtimes that may be 113 /// differentially executed against. 114 pub fn setup_engine_runtimes() { 115 #[cfg(feature = "fuzz-spec-interpreter")] 116 crate::oracles::diff_spec::setup_ocaml_runtime(); 117 } 118 119 /// Build a list of allowed values from the given `defaults` using the 120 /// `env_list`. 121 /// 122 /// ``` 123 /// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list; 124 /// // Passing no `env_list` returns the defaults: 125 /// assert_eq!(build_allowed_env_list(None, &["a"]), vec!["a"]); 126 /// // We can build up a subset of the defaults: 127 /// assert_eq!(build_allowed_env_list(Some(vec!["b".to_string()]), &["a","b"]), vec!["b"]); 128 /// // Alternately we can subtract from the defaults: 129 /// assert_eq!(build_allowed_env_list(Some(vec!["-a".to_string()]), &["a","b"]), vec!["b"]); 130 /// ``` 131 /// ```should_panic 132 /// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list; 133 /// // We are not allowed to mix set "addition" and "subtraction"; the following 134 /// // will panic: 135 /// build_allowed_env_list(Some(vec!["-a".to_string(), "b".to_string()]), &["a", "b"]); 136 /// ``` 137 /// ```should_panic 138 /// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list; 139 /// // This will also panic if invalid values are used: 140 /// build_allowed_env_list(Some(vec!["c".to_string()]), &["a", "b"]); 141 /// ``` 142 pub fn build_allowed_env_list<'a>( 143 env_list: Option<Vec<String>>, 144 defaults: &[&'a str], 145 ) -> Vec<&'a str> { 146 if let Some(configured) = &env_list { 147 // Check that the names are either all additions or all subtractions. 148 let subtract_from_defaults = configured.iter().all(|c| c.starts_with("-")); 149 let add_from_defaults = configured.iter().all(|c| !c.starts_with("-")); 150 let start = if subtract_from_defaults { 1 } else { 0 }; 151 if !subtract_from_defaults && !add_from_defaults { 152 panic!( 153 "all configured values must either subtract or add from defaults; found mixed values: {:?}", 154 &env_list 155 ); 156 } 157 158 // Check that the configured names are valid ones. 159 for c in configured { 160 if !defaults.contains(&&c[start..]) { 161 panic!( 162 "invalid environment configuration `{}`; must be one of: {:?}", 163 c, defaults 164 ); 165 } 166 } 167 168 // Select only the allowed names. 169 let mut allowed = Vec::with_capacity(defaults.len()); 170 for &d in defaults { 171 let mentioned = configured.iter().any(|c| &c[start..] == d); 172 if (add_from_defaults && mentioned) || (subtract_from_defaults && !mentioned) { 173 allowed.push(d); 174 } 175 } 176 allowed 177 } else { 178 defaults.to_vec() 179 } 180 } 181 182 /// Retrieve a comma-delimited list of values from an environment variable. 183 pub fn parse_env_list(env_variable: &str) -> Option<Vec<String>> { 184 std::env::var(env_variable) 185 .ok() 186 .map(|l| l.split(",").map(|s| s.to_owned()).collect()) 187 } 188 189 #[cfg(test)] 190 pub fn smoke_test_engine<T>(mk_engine: impl Fn(Config) -> anyhow::Result<T>) 191 where 192 T: DiffEngine, 193 { 194 use arbitrary::Arbitrary; 195 use rand::prelude::*; 196 197 let mut rng = SmallRng::seed_from_u64(0); 198 let mut buf = vec![0; 2048]; 199 let n = 100; 200 for _ in 0..n { 201 rng.fill_bytes(&mut buf); 202 let u = Unstructured::new(&buf); 203 let mut config = match Config::arbitrary_take_rest(u) { 204 Ok(config) => config, 205 Err(_) => continue, 206 }; 207 // This will ensure that wasmtime, which uses this configuration 208 // settings, can guaranteed instantiate a module. 209 config.set_differential_config(); 210 211 // Configure settings to ensure that any filters in engine constructors 212 // try not to filter out this `Config`. 213 config.module_config.config.reference_types_enabled = false; 214 config.module_config.config.bulk_memory_enabled = false; 215 config.module_config.config.memory64_enabled = false; 216 config.module_config.config.threads_enabled = false; 217 config.module_config.config.simd_enabled = false; 218 config.module_config.config.min_funcs = 1; 219 config.module_config.config.max_funcs = 1; 220 config.module_config.config.min_tables = 0; 221 config.module_config.config.max_tables = 0; 222 223 let mut engine = match mk_engine(config) { 224 Ok(engine) => engine, 225 Err(e) => { 226 println!("skip {:?}", e); 227 continue; 228 } 229 }; 230 231 let wasm = wat::parse_str( 232 r#" 233 (module 234 (func (export "add") (param i32 i32) (result i32) 235 local.get 0 236 local.get 1 237 i32.add) 238 239 (global (export "global") i32 i32.const 1) 240 (memory (export "memory") 1) 241 ) 242 "#, 243 ) 244 .unwrap(); 245 let mut instance = engine.instantiate(&wasm).unwrap(); 246 let results = instance 247 .evaluate( 248 "add", 249 &[DiffValue::I32(1), DiffValue::I32(2)], 250 &[DiffValueType::I32], 251 ) 252 .unwrap(); 253 assert_eq!(results, Some(vec![DiffValue::I32(3)])); 254 255 if let Some(val) = instance.get_global("global", DiffValueType::I32) { 256 assert_eq!(val, DiffValue::I32(1)); 257 } 258 259 if let Some(val) = instance.get_memory("memory", false) { 260 assert_eq!(val.len(), 65536); 261 for i in val.iter() { 262 assert_eq!(*i, 0); 263 } 264 } 265 266 return; 267 } 268 269 panic!("after {n} runs nothing ever ran, something is probably wrong"); 270 } 271