Refactor fuzzing configuration and sometimes disable debug verifier. (#3664)

* fuzz: Refactor Wasmtime's fuzz targets

A recent fuzz bug found is related to timing out when compiling a
module. This timeout, however, is predominately because Cranelift's
debug verifier is enabled and taking up over half the compilation time.
I wanted to fix this by disabling the verifier when input modules might
have a lot of functions, but this was pretty difficult to implement.

Over time we've grown a number of various fuzzers. Most are
`wasm-smith`-based at this point but there's various entry points for
configuring the wasm-smith module, the wasmtime configuration, etc. I've
historically gotten quite lost in trying to change defaults and feeling
like I have to touch a lot of different places. This is the motivation
for this commit, simplifying fuzzer default configuration.

This commit removes the ability to create a default `Config` for
fuzzing, instead only supporting generating a configuration via
`Arbitrary`. This then involved refactoring all targets and fuzzers to
ensure that configuration is generated through `Arbitrary`. This should
actually expand the coverage of some existing fuzz targets since
`Arbitrary for Config` will tweak options that don't affect runtime,
such as memory configuration or jump veneers.

All existing fuzz targets are refactored to use this new method of
configuration. Some fuzz targets were also shuffled around or
reimplemented:

* `compile` - this now directly calls `Module::new` to skip all the
  fuzzing infrastructure. This is mostly done because this fuzz target
  isn't too interesting and is largely just seeing what happens when
  things are thrown at the wall for Wasmtime.

* `instantiate-maybe-invalid` - this fuzz target now skips instantiation
  and instead simply goes into `Module::new` like the `compile` target.
  The rationale behind this is that most modules won't instantiate
  anyway and this fuzz target is primarily fuzzing the compiler. This
  skips having to generate arbitrary configuration since
  wasm-smith-generated-modules (or valid ones at least) aren't used
  here.

* `instantiate` - this fuzz target was removed. In general this fuzz
  target isn't too interesting in isolation. Almost everything it deals
  with likely won't pass compilation and is covered by the `compile`
  fuzz target, and otherwise interesting modules being instantiated can
  all theoretically be created by `wasm-smith` anyway.

* `instantiate-wasm-smith` and `instantiate-swarm` - these were both merged
  into a new `instantiate` target (replacing the old one from above).
  There wasn't really much need to keep these separate since they really
  only differed at this point in methods of timeout. Otherwise we much
  more heavily use `SwarmConfig` than wasm-smith's built-in options.

The intention is that we should still have basically the same coverage
of fuzzing as before, if not better because configuration is now
possible on some targets. Additionally there is one centralized point of
configuration for fuzzing for wasmtime, `Arbitrary for ModuleConfig`.
This internally creates an arbitrary `SwarmConfig` from `wasm-smith` and
then further tweaks it for Wasmtime's needs, such as enabling various
wasm proposals by default. In the future enabling a wasm proposal on
fuzzing should largely just be modifying this one trait implementation.

* fuzz: Sometimes disable the cranelift debug verifier

This commit disables the cranelift debug verifier if the input wasm
module might be "large" for the definition of "more than 10 functions".
While fuzzing we disable threads (set them to 1) and enable the
cranelift debug verifier. Coupled with a 20-30x slowdown this means that
a module with the maximum number of functions, 100, gives:

    60x / 100 functions / 30x slowdown = 20ms

With only 20 milliseconds per function this is even further halved by
the `differential` fuzz target compiling a module twice, which means
that, when compiling with a normal release mode Wasmtime, if any
function takes more than 10ms to compile then it's a candidate for
timing out while fuzzing. Given that the cranelift debug verifier can
more than double compilation time in fuzzing mode this actually means
that the real time budget for function compilation is more like 4ms.

The `wasm-smith` crate can pretty easily generate a large function that
takes 4ms to compile, and then when that function is multiplied 100x in
the `differential` fuzz target we trivially time out the fuzz target.

The hope of this commit is to buy back half our budget by disabling the
debug verifier for modules that may have many functions. Further
refinements can be implemented in the future such as limiting functions
for just the differential target as well.

* Fix the single-function-module fuzz configuration

* Tweak how features work in differential fuzzing

* Disable everything for baseline differential fuzzing
* Enable selectively for each engine afterwards
* Also forcibly enable reference types and bulk memory for spec tests

* Log wasms when compiling

* Add reference types support to v8 fuzzer

* Fix timeouts via fuel

The default store has "infinite" fuel so that needs to be consumed
before fuel is added back in.

* Remove fuzzing-specific tests

These no longer compile and also haven't been added to in a long time.
Most of the time a reduced form of original the fuzz test case is added
when a fuzz bug is fixed.
This commit is contained in:
Alex Crichton
2022-01-07 15:12:25 -06:00
committed by GitHub
parent ab5aea7b28
commit ab1d845ac1
25 changed files with 360 additions and 516 deletions

2
Cargo.lock generated
View File

@@ -3457,7 +3457,6 @@ dependencies = [
"wasmtime-cache", "wasmtime-cache",
"wasmtime-cranelift", "wasmtime-cranelift",
"wasmtime-environ", "wasmtime-environ",
"wasmtime-fuzzing",
"wasmtime-runtime", "wasmtime-runtime",
"wasmtime-wasi", "wasmtime-wasi",
"wasmtime-wasi-crypto", "wasmtime-wasi-crypto",
@@ -3528,7 +3527,6 @@ dependencies = [
"cranelift-wasm", "cranelift-wasm",
"libfuzzer-sys", "libfuzzer-sys",
"target-lexicon", "target-lexicon",
"wasm-smith",
"wasmtime", "wasmtime",
"wasmtime-fuzzing", "wasmtime-fuzzing",
] ]

View File

@@ -44,12 +44,13 @@ lazy_static = "1.4.0"
rustix = "0.31.0" rustix = "0.31.0"
[dev-dependencies] [dev-dependencies]
# depend again on wasmtime to activate its default features for tests
wasmtime = { path = "crates/wasmtime", version = "0.33.0" }
env_logger = "0.8.1" env_logger = "0.8.1"
filecheck = "0.5.0" filecheck = "0.5.0"
more-asserts = "0.2.1" more-asserts = "0.2.1"
tempfile = "3.1.0" tempfile = "3.1.0"
test-programs = { path = "crates/test-programs" } test-programs = { path = "crates/test-programs" }
wasmtime-fuzzing = { path = "crates/fuzzing" }
wasmtime-runtime = { path = "crates/runtime" } wasmtime-runtime = { path = "crates/runtime" }
tokio = { version = "1.8.0", features = ["rt", "time", "macros", "rt-multi-thread"] } tokio = { version = "1.8.0", features = ["rt", "time", "macros", "rt-multi-thread"] }
tracing-subscriber = "0.3.1" tracing-subscriber = "0.3.1"

View File

@@ -9,13 +9,15 @@
//! `Arbitrary` trait for the wrapped external tool. //! `Arbitrary` trait for the wrapped external tool.
pub mod api; pub mod api;
pub mod table_ops; pub mod table_ops;
use crate::oracles::{StoreLimits, Timeout};
use anyhow::Result; use anyhow::Result;
use arbitrary::{Arbitrary, Unstructured}; use arbitrary::{Arbitrary, Unstructured};
use std::sync::Arc; use std::sync::Arc;
use wasmtime::{LinearMemory, MemoryCreator, MemoryType}; use std::time::Duration;
use wasm_smith::SwarmConfig;
use wasmtime::{Engine, LinearMemory, MemoryCreator, MemoryType, Store};
#[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)]
enum OptLevel { enum OptLevel {
@@ -34,20 +36,34 @@ impl OptLevel {
} }
} }
/// Implementation of generating a `wasmtime::Config` arbitrarily /// Configuration for `wasmtime::Config` and generated modules for a session of
#[derive(Arbitrary, Debug, Eq, Hash, PartialEq)] /// fuzzing.
///
/// This configuration guides what modules are generated, how wasmtime
/// configuration is generated, and is typically itself generated through a call
/// to `Arbitrary` which allows for a form of "swarm testing".
#[derive(Arbitrary, Debug)]
pub struct Config { pub struct Config {
/// Configuration related to the `wasmtime::Config`.
pub wasmtime: WasmtimeConfig,
/// Configuration related to generated modules.
pub module_config: ModuleConfig,
}
/// Configuration related to `wasmtime::Config` and the various settings which
/// can be tweaked from within.
#[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)]
pub struct WasmtimeConfig {
opt_level: OptLevel, opt_level: OptLevel,
debug_info: bool, debug_info: bool,
canonicalize_nans: bool, canonicalize_nans: bool,
interruptable: bool, interruptable: bool,
#[allow(missing_docs)] consume_fuel: bool,
pub consume_fuel: bool,
memory_config: MemoryConfig, memory_config: MemoryConfig,
force_jump_veneers: bool, force_jump_veneers: bool,
} }
#[derive(Arbitrary, Debug, Eq, Hash, PartialEq)] #[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)]
enum MemoryConfig { enum MemoryConfig {
/// Configuration for linear memories which correspond to normal /// Configuration for linear memories which correspond to normal
/// configuration settings in `wasmtime` itself. This will tweak various /// configuration settings in `wasmtime` itself. This will tweak various
@@ -71,21 +87,42 @@ enum MemoryConfig {
impl Config { impl Config {
/// Converts this to a `wasmtime::Config` object /// Converts this to a `wasmtime::Config` object
pub fn to_wasmtime(&self) -> wasmtime::Config { pub fn to_wasmtime(&self) -> wasmtime::Config {
let mut cfg = crate::fuzz_default_config(wasmtime::Strategy::Auto).unwrap(); crate::init_fuzzing();
cfg.debug_info(self.debug_info)
.cranelift_nan_canonicalization(self.canonicalize_nans)
.cranelift_opt_level(self.opt_level.to_wasmtime())
.interruptable(self.interruptable)
.consume_fuel(self.consume_fuel);
if self.force_jump_veneers { let mut cfg = wasmtime::Config::new();
cfg.wasm_bulk_memory(true)
.wasm_reference_types(true)
.wasm_module_linking(self.module_config.config.module_linking_enabled)
.wasm_multi_memory(self.module_config.config.max_memories > 1)
.wasm_simd(self.module_config.config.simd_enabled)
.wasm_memory64(self.module_config.config.memory64_enabled)
.cranelift_nan_canonicalization(self.wasmtime.canonicalize_nans)
.cranelift_opt_level(self.wasmtime.opt_level.to_wasmtime())
.interruptable(self.wasmtime.interruptable)
.consume_fuel(self.wasmtime.consume_fuel);
// If the wasm-smith-generated module use nan canonicalization then we
// don't need to enable it, but if it doesn't enable it already then we
// enable this codegen option.
cfg.cranelift_nan_canonicalization(!self.module_config.config.canonicalize_nans);
// Enabling the verifier will at-least-double compilation time, which
// with a 20-30x slowdown in fuzzing can cause issues related to
// timeouts. If generated modules can have more than a small handful of
// functions then disable the verifier when fuzzing to try to lessen the
// impact of timeouts.
if self.module_config.config.max_funcs > 10 {
cfg.cranelift_debug_verifier(false);
}
if self.wasmtime.force_jump_veneers {
unsafe { unsafe {
cfg.cranelift_flag_set("wasmtime_linkopt_force_jump_veneer", "true") cfg.cranelift_flag_set("wasmtime_linkopt_force_jump_veneer", "true")
.unwrap(); .unwrap();
} }
} }
match &self.memory_config { match &self.wasmtime.memory_config {
MemoryConfig::Normal { MemoryConfig::Normal {
static_memory_maximum_size, static_memory_maximum_size,
static_memory_guard_size, static_memory_guard_size,
@@ -107,6 +144,30 @@ impl Config {
} }
return cfg; return cfg;
} }
/// Convenience function for generating a `Store<T>` using this
/// configuration.
pub fn to_store(&self) -> Store<StoreLimits> {
let engine = Engine::new(&self.to_wasmtime()).unwrap();
let mut store = Store::new(&engine, StoreLimits::new());
store.limiter(|s| s as &mut dyn wasmtime::ResourceLimiter);
if self.wasmtime.consume_fuel {
store.add_fuel(u64::max_value()).unwrap();
}
return store;
}
/// Generates an arbitrary method of timing out an instance, ensuring that
/// this configuration supports the returned timeout.
pub fn generate_timeout(&mut self, u: &mut Unstructured<'_>) -> arbitrary::Result<Timeout> {
if u.arbitrary()? {
self.wasmtime.interruptable = true;
Ok(Timeout::Time(Duration::from_secs(20)))
} else {
self.wasmtime.consume_fuel = true;
Ok(Timeout::Fuel(100_000))
}
}
} }
struct UnalignedMemoryCreator; struct UnalignedMemoryCreator;
@@ -194,40 +255,89 @@ impl<'a> Arbitrary<'a> for SpecTest {
} }
} }
/// Type alias for wasm-smith generated modules using wasmtime's default /// Default module-level configuration for fuzzing Wasmtime.
/// configuration. ///
pub type GeneratedModule = wasm_smith::ConfiguredModule<WasmtimeDefaultConfig>; /// Internally this uses `wasm-smith`'s own `SwarmConfig` but we further refine
/// the defaults here as well.
#[derive(Debug, Clone)]
pub struct ModuleConfig {
#[allow(missing_docs)]
pub config: SwarmConfig,
}
/// Wasmtime-specific default configuration for wasm-smith-generated modules. impl ModuleConfig {
#[derive(Arbitrary, Clone, Debug)] /// Uses this configuration and the supplied source of data to generate
pub struct WasmtimeDefaultConfig; /// a wasm module.
pub fn generate(&self, input: &mut Unstructured<'_>) -> arbitrary::Result<wasm_smith::Module> {
impl wasm_smith::Config for WasmtimeDefaultConfig { wasm_smith::Module::new(self.config.clone(), input)
// Allow multi-memory to get exercised
fn max_memories(&self) -> usize {
2
} }
// Allow multi-table (reference types) to get exercised /// Indicates that this configuration should be spec-test-compliant,
fn max_tables(&self) -> usize { /// disabling various features the spec tests assert are disabled.
4 pub fn set_spectest_compliant(&mut self) {
self.config.memory64_enabled = false;
self.config.simd_enabled = false;
self.config.bulk_memory_enabled = true;
self.config.reference_types_enabled = true;
self.config.max_memories = 1;
} }
// Turn some wasm features default-on for those that have a finished /// Indicates that this configuration is being used for differential
// implementation in Wasmtime. /// execution so only a single function should be generated since that's all
fn simd_enabled(&self) -> bool { /// that's going to be exercised.
true pub fn set_differential_config(&mut self) {
} self.config.allow_start_export = false;
// Make sure there's a type available for the function.
self.config.min_types = 1;
self.config.max_types = 1;
fn reference_types_enabled(&self) -> bool { // Generate one and only one function
true self.config.min_funcs = 1;
} self.config.max_funcs = 1;
fn bulk_memory_enabled(&self) -> bool { // Give the function a memory, but keep it small
true self.config.min_memories = 1;
} self.config.max_memories = 1;
self.config.max_memory_pages = 1;
self.config.memory_max_size_required = true;
fn memory64_enabled(&self) -> bool { // Don't allow any imports
true self.config.max_imports = 0;
// Try to get the function and the memory exported
self.config.min_exports = 2;
self.config.max_exports = 4;
// NaN is canonicalized at the wasm level for differential fuzzing so we
// can paper over NaN differences between engines.
self.config.canonicalize_nans = true;
// When diffing against a non-wasmtime engine then disable wasm
// features to get selectively re-enabled against each differential
// engine.
self.config.bulk_memory_enabled = false;
self.config.reference_types_enabled = false;
self.config.simd_enabled = false;
self.config.memory64_enabled = false;
}
}
impl<'a> Arbitrary<'a> for ModuleConfig {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<ModuleConfig> {
let mut config = SwarmConfig::arbitrary(u)?;
// Allow multi-memory by default.
config.max_memories = config.max_memories.max(2);
// Allow multi-table by default.
config.max_tables = config.max_tables.max(4);
// Allow enabling some various wasm proposals by default.
config.bulk_memory_enabled = u.arbitrary()?;
config.reference_types_enabled = u.arbitrary()?;
config.simd_enabled = u.arbitrary()?;
config.memory64_enabled = u.arbitrary()?;
Ok(ModuleConfig { config })
} }
} }

View File

@@ -14,13 +14,12 @@
//! //!
//! [swarm testing]: https://www.cs.utah.edu/~regehr/papers/swarm12.pdf //! [swarm testing]: https://www.cs.utah.edu/~regehr/papers/swarm12.pdf
use crate::generators::{Config, ModuleConfig};
use arbitrary::{Arbitrary, Unstructured}; use arbitrary::{Arbitrary, Unstructured};
use std::collections::BTreeSet; use std::collections::BTreeSet;
#[derive(Arbitrary, Debug)] #[derive(Arbitrary, Debug)]
struct Swarm { struct Swarm {
config_debug_info: bool,
config_interruptable: bool,
module_new: bool, module_new: bool,
module_drop: bool, module_drop: bool,
instance_new: bool, instance_new: bool,
@@ -32,37 +31,20 @@ struct Swarm {
#[derive(Arbitrary, Debug)] #[derive(Arbitrary, Debug)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum ApiCall { pub enum ApiCall {
ConfigNew, StoreNew(Config),
ConfigDebugInfo(bool), ModuleNew { id: usize, wasm: Vec<u8> },
ConfigInterruptable(bool), ModuleDrop { id: usize },
EngineNew, InstanceNew { id: usize, module: usize },
StoreNew, InstanceDrop { id: usize },
ModuleNew { CallExportedFunc { instance: usize, nth: usize },
id: usize,
wasm: super::GeneratedModule,
},
ModuleDrop {
id: usize,
},
InstanceNew {
id: usize,
module: usize,
},
InstanceDrop {
id: usize,
},
CallExportedFunc {
instance: usize,
nth: usize,
},
} }
use ApiCall::*; use ApiCall::*;
#[derive(Default)]
struct Scope { struct Scope {
id_counter: usize, id_counter: usize,
modules: BTreeSet<usize>, modules: BTreeSet<usize>,
instances: BTreeSet<usize>, instances: BTreeSet<usize>,
module_config: ModuleConfig,
} }
impl Scope { impl Scope {
@@ -87,11 +69,16 @@ impl<'a> Arbitrary<'a> for ApiCalls {
let swarm = Swarm::arbitrary(input)?; let swarm = Swarm::arbitrary(input)?;
let mut calls = vec![]; let mut calls = vec![];
arbitrary_config(input, &swarm, &mut calls)?; let config = Config::arbitrary(input)?;
calls.push(EngineNew); let module_config = config.module_config.clone();
calls.push(StoreNew); calls.push(StoreNew(config));
let mut scope = Scope::default(); let mut scope = Scope {
id_counter: 0,
modules: BTreeSet::default(),
instances: BTreeSet::default(),
module_config,
};
// Total limit on number of API calls we'll generate. This exists to // Total limit on number of API calls we'll generate. This exists to
// avoid libFuzzer timeouts. // avoid libFuzzer timeouts.
@@ -107,10 +94,13 @@ impl<'a> Arbitrary<'a> for ApiCalls {
if swarm.module_new { if swarm.module_new {
choices.push(|input, scope| { choices.push(|input, scope| {
let id = scope.next_id(); let id = scope.next_id();
let mut wasm = super::GeneratedModule::arbitrary(input)?; let mut wasm = scope.module_config.generate(input)?;
wasm.module.ensure_termination(1000); wasm.ensure_termination(1000);
scope.modules.insert(id); scope.modules.insert(id);
Ok(ModuleNew { id, wasm }) Ok(ModuleNew {
id,
wasm: wasm.to_bytes(),
})
}); });
} }
if swarm.module_drop && !scope.modules.is_empty() { if swarm.module_drop && !scope.modules.is_empty() {
@@ -156,44 +146,4 @@ impl<'a> Arbitrary<'a> for ApiCalls {
Ok(ApiCalls { calls }) Ok(ApiCalls { calls })
} }
fn size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::recursion_guard(depth, |depth| {
arbitrary::size_hint::or(
// This is the stuff we unconditionally need, which affects the
// minimum size.
arbitrary::size_hint::and(
<Swarm as Arbitrary>::size_hint(depth),
// `arbitrary_config` uses four bools:
// 2 when `swarm.config_debug_info` is true
// 2 when `swarm.config_interruptable` is true
<(bool, bool, bool, bool) as Arbitrary>::size_hint(depth),
),
// We can generate arbitrary `WasmOptTtf` instances, which have
// no upper bound on the number of bytes they consume. This sets
// the upper bound to `None`.
<super::GeneratedModule as Arbitrary>::size_hint(depth),
)
})
}
}
fn arbitrary_config(
input: &mut Unstructured,
swarm: &Swarm,
calls: &mut Vec<ApiCall>,
) -> arbitrary::Result<()> {
calls.push(ConfigNew);
if swarm.config_debug_info && bool::arbitrary(input)? {
calls.push(ConfigDebugInfo(bool::arbitrary(input)?));
}
if swarm.config_interruptable && bool::arbitrary(input)? {
calls.push(ConfigInterruptable(bool::arbitrary(input)?));
}
// TODO: flags, features, and compilation strategy.
Ok(())
} }

View File

@@ -1,7 +1,8 @@
//! Fuzzing infrastructure for Wasmtime. //! Fuzzing infrastructure for Wasmtime.
#![deny(missing_docs, missing_debug_implementations)] #![deny(missing_docs)]
pub use wasm_smith;
pub mod generators; pub mod generators;
pub mod oracles; pub mod oracles;
@@ -29,19 +30,3 @@ pub(crate) fn init_fuzzing() {
.build_global(); .build_global();
}) })
} }
/// Create default fuzzing config with given strategy
pub fn fuzz_default_config(strategy: wasmtime::Strategy) -> anyhow::Result<wasmtime::Config> {
init_fuzzing();
let mut config = wasmtime::Config::new();
config
.cranelift_nan_canonicalization(true)
.wasm_bulk_memory(true)
.wasm_reference_types(true)
.wasm_module_linking(true)
.wasm_multi_memory(true)
.wasm_simd(true)
.wasm_memory64(true)
.strategy(strategy)?;
Ok(config)
}

View File

@@ -12,8 +12,8 @@
pub mod dummy; pub mod dummy;
use crate::generators;
use anyhow::Context; use anyhow::Context;
use arbitrary::Arbitrary;
use log::{debug, warn}; use log::{debug, warn};
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use std::sync::{Arc, Condvar, Mutex}; use std::sync::{Arc, Condvar, Mutex};
@@ -28,7 +28,11 @@ mod v8;
static CNT: AtomicUsize = AtomicUsize::new(0); static CNT: AtomicUsize = AtomicUsize::new(0);
fn log_wasm(wasm: &[u8]) { /// Logs a wasm file to the filesystem to make it easy to figure out what wasm
/// was used when debugging.
pub fn log_wasm(wasm: &[u8]) {
super::init_fuzzing();
if !log::log_enabled!(log::Level::Debug) { if !log::log_enabled!(log::Level::Debug) {
return; return;
} }
@@ -47,21 +51,9 @@ fn log_wasm(wasm: &[u8]) {
} }
} }
fn create_store(engine: &Engine) -> Store<StoreLimits> { /// The `T` in `Store<T>` for fuzzing stores, used to limit resource
let mut store = Store::new( /// consumption during fuzzing.
&engine, pub struct StoreLimits {
StoreLimits {
// Limits tables/memories within a store to at most 1gb for now to
// exercise some larger address but not overflow various limits.
remaining_memory: 1 << 30,
oom: false,
},
);
store.limiter(|s| s as &mut dyn ResourceLimiter);
return store;
}
struct StoreLimits {
/// Remaining memory, in bytes, left to allocate /// Remaining memory, in bytes, left to allocate
remaining_memory: usize, remaining_memory: usize,
/// Whether or not an allocation request has been denied /// Whether or not an allocation request has been denied
@@ -69,6 +61,16 @@ struct StoreLimits {
} }
impl StoreLimits { impl StoreLimits {
/// Creates the default set of limits for all fuzzing stores.
pub fn new() -> StoreLimits {
StoreLimits {
// Limits tables/memories within a store to at most 1gb for now to
// exercise some larger address but not overflow various limits.
remaining_memory: 1 << 30,
oom: false,
}
}
fn alloc(&mut self, amt: usize) -> bool { fn alloc(&mut self, amt: usize) -> bool {
match self.remaining_memory.checked_sub(amt) { match self.remaining_memory.checked_sub(amt) {
Some(mem) => { Some(mem) => {
@@ -108,48 +110,22 @@ pub enum Timeout {
Fuel(u64), Fuel(u64),
} }
/// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
/// panic or segfault or anything else that can be detected "passively".
///
/// Performs initial validation, and returns early if the Wasm is invalid.
///
/// You can control which compiler is used via passing a `Strategy`.
pub fn instantiate(wasm: &[u8], known_valid: bool, strategy: Strategy) {
// Explicitly disable module linking for now since it's a breaking change to
// pre-module-linking modules due to imports
let mut cfg = crate::fuzz_default_config(strategy).unwrap();
cfg.wasm_module_linking(false);
instantiate_with_config(wasm, known_valid, cfg, Timeout::None);
}
/// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected /// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
/// panic or segfault or anything else that can be detected "passively". /// panic or segfault or anything else that can be detected "passively".
/// ///
/// The engine will be configured using provided config. /// The engine will be configured using provided config.
/// pub fn instantiate(wasm: &[u8], known_valid: bool, config: &generators::Config, timeout: Timeout) {
/// See also `instantiate` functions. let mut store = config.to_store();
pub fn instantiate_with_config(
wasm: &[u8],
known_valid: bool,
mut config: Config,
timeout: Timeout,
) {
crate::init_fuzzing();
config.interruptable(match &timeout {
Timeout::Time(_) => true,
_ => false,
});
config.consume_fuel(match &timeout {
Timeout::Fuel(_) => true,
_ => false,
});
let engine = Engine::new(&config).unwrap();
let mut store = create_store(&engine);
let mut timeout_state = SignalOnDrop::default(); let mut timeout_state = SignalOnDrop::default();
match timeout { match timeout {
Timeout::Fuel(fuel) => store.add_fuel(fuel).unwrap(), Timeout::Fuel(fuel) => {
// consume the default fuel in the store ...
let remaining = store.consume_fuel(0).unwrap();
store.consume_fuel(remaining - 1).unwrap();
// ... then add back in how much fuel we're allowing here
store.add_fuel(fuel).unwrap();
}
// If a timeout is requested then we spawn a helper thread to wait for // If a timeout is requested then we spawn a helper thread to wait for
// the requested time and then send us a signal to get interrupted. We // the requested time and then send us a signal to get interrupted. We
// also arrange for the thread's sleep to get interrupted if we return // also arrange for the thread's sleep to get interrupted if we return
@@ -167,7 +143,7 @@ pub fn instantiate_with_config(
} }
log_wasm(wasm); log_wasm(wasm);
let module = match Module::new(&engine, wasm) { let module = match Module::new(store.engine(), wasm) {
Ok(module) => module, Ok(module) => module,
Err(_) if !known_valid => return, Err(_) if !known_valid => return,
Err(e) => panic!("failed to compile module: {:?}", e), Err(e) => panic!("failed to compile module: {:?}", e),
@@ -216,34 +192,17 @@ fn instantiate_with_dummy(store: &mut Store<StoreLimits>, module: &Module) -> Op
panic!("failed to instantiate {:?}", e); panic!("failed to instantiate {:?}", e);
} }
/// Compile the Wasm buffer, and implicitly fail if we have an unexpected
/// panic or segfault or anything else that can be detected "passively".
///
/// Performs initial validation, and returns early if the Wasm is invalid.
///
/// You can control which compiler is used via passing a `Strategy`.
pub fn compile(wasm: &[u8], strategy: Strategy) {
crate::init_fuzzing();
let mut config = crate::fuzz_default_config(strategy).unwrap();
config.wasm_module_linking(false);
let engine = Engine::new(&config).unwrap();
log_wasm(wasm);
let _ = Module::new(&engine, wasm);
}
/// Instantiate the given Wasm module with each `Config` and call all of its /// Instantiate the given Wasm module with each `Config` and call all of its
/// exports. Modulo OOM, non-canonical NaNs, and usage of Wasm features that are /// exports. Modulo OOM, non-canonical NaNs, and usage of Wasm features that are
/// or aren't enabled for different configs, we should get the same results when /// or aren't enabled for different configs, we should get the same results when
/// we call the exported functions for all of our different configs. /// we call the exported functions for all of our different configs.
pub fn differential_execution( pub fn differential_execution(
module: &crate::generators::GeneratedModule, wasm: &[u8],
configs: &[crate::generators::Config], module_config: &generators::ModuleConfig,
configs: &[generators::WasmtimeConfig],
) { ) {
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
crate::init_fuzzing();
// We need at least two configs. // We need at least two configs.
if configs.len() < 2 if configs.len() < 2
// And all the configs should be unique. // And all the configs should be unique.
@@ -252,32 +211,18 @@ pub fn differential_execution(
return; return;
} }
let configs: Vec<_> = configs.iter().map(|c| (c.to_wasmtime(), c)).collect();
let mut export_func_results: HashMap<String, Result<Box<[Val]>, Trap>> = Default::default(); let mut export_func_results: HashMap<String, Result<Box<[Val]>, Trap>> = Default::default();
let wasm = module.module.to_bytes();
log_wasm(&wasm); log_wasm(&wasm);
for (mut config, fuzz_config) in configs { for fuzz_config in configs {
let fuzz_config = generators::Config {
module_config: module_config.clone(),
wasmtime: fuzz_config.clone(),
};
log::debug!("fuzz config: {:?}", fuzz_config); log::debug!("fuzz config: {:?}", fuzz_config);
// Disable module linking since it isn't enabled by default for
// `GeneratedModule` but is enabled by default for our fuzz config.
// Since module linking is currently a breaking change this is required
// to accept modules that would otherwise be broken by module linking.
config.wasm_module_linking(false);
// We don't want different configurations with different values for nan let mut store = fuzz_config.to_store();
// canonicalization since that can affect results. All configs should let module = Module::new(store.engine(), &wasm).unwrap();
// have the same value configured for this option, so `true` is
// arbitrarily chosen here.
config.cranelift_nan_canonicalization(true);
let engine = Engine::new(&config).unwrap();
let mut store = create_store(&engine);
if fuzz_config.consume_fuel {
store.add_fuel(u64::max_value()).unwrap();
}
let module = Module::new(&engine, &wasm).unwrap();
// TODO: we should implement tracing versions of these dummy imports // TODO: we should implement tracing versions of these dummy imports
// that record a trace of the order that imported functions were called // that record a trace of the order that imported functions were called
@@ -367,53 +312,26 @@ fn f64_equal(a: u64, b: u64) -> bool {
} }
/// Invoke the given API calls. /// Invoke the given API calls.
pub fn make_api_calls(api: crate::generators::api::ApiCalls) { pub fn make_api_calls(api: generators::api::ApiCalls) {
use crate::generators::api::ApiCall; use crate::generators::api::ApiCall;
use std::collections::HashMap; use std::collections::HashMap;
crate::init_fuzzing();
let mut config: Option<Config> = None;
let mut engine: Option<Engine> = None;
let mut store: Option<Store<StoreLimits>> = None; let mut store: Option<Store<StoreLimits>> = None;
let mut modules: HashMap<usize, Module> = Default::default(); let mut modules: HashMap<usize, Module> = Default::default();
let mut instances: HashMap<usize, Instance> = Default::default(); let mut instances: HashMap<usize, Instance> = Default::default();
for call in api.calls { for call in api.calls {
match call { match call {
ApiCall::ConfigNew => { ApiCall::StoreNew(config) => {
log::trace!("creating config");
assert!(config.is_none());
config = Some(crate::fuzz_default_config(wasmtime::Strategy::Cranelift).unwrap());
}
ApiCall::ConfigDebugInfo(b) => {
log::trace!("enabling debuginfo");
config.as_mut().unwrap().debug_info(b);
}
ApiCall::ConfigInterruptable(b) => {
log::trace!("enabling interruption");
config.as_mut().unwrap().interruptable(b);
}
ApiCall::EngineNew => {
log::trace!("creating engine");
assert!(engine.is_none());
engine = Some(Engine::new(config.as_ref().unwrap()).unwrap());
}
ApiCall::StoreNew => {
log::trace!("creating store"); log::trace!("creating store");
assert!(store.is_none()); assert!(store.is_none());
store = Some(create_store(engine.as_ref().unwrap())); store = Some(config.to_store());
} }
ApiCall::ModuleNew { id, wasm } => { ApiCall::ModuleNew { id, wasm } => {
log::debug!("creating module: {}", id); log::debug!("creating module: {}", id);
let wasm = wasm.module.to_bytes();
log_wasm(&wasm); log_wasm(&wasm);
let module = match Module::new(engine.as_ref().unwrap(), &wasm) { let module = match Module::new(store.as_ref().unwrap().engine(), &wasm) {
Ok(m) => m, Ok(m) => m,
Err(_) => continue, Err(_) => continue,
}; };
@@ -486,18 +404,10 @@ pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
/// Executes the wast `test` spectest with the `config` specified. /// Executes the wast `test` spectest with the `config` specified.
/// ///
/// Ensures that spec tests pass regardless of the `Config`. /// Ensures that spec tests pass regardless of the `Config`.
pub fn spectest(fuzz_config: crate::generators::Config, test: crate::generators::SpecTest) { pub fn spectest(mut fuzz_config: generators::Config, test: generators::SpecTest) {
crate::init_fuzzing(); fuzz_config.module_config.set_spectest_compliant();
log::debug!("running {:?} with {:?}", test.file, fuzz_config); log::debug!("running {:?} with {:?}", test.file, fuzz_config);
let mut config = fuzz_config.to_wasmtime(); let mut wast_context = WastContext::new(fuzz_config.to_store());
config.wasm_memory64(false);
config.wasm_module_linking(false);
config.wasm_multi_memory(false);
let mut store = create_store(&Engine::new(&config).unwrap());
if fuzz_config.consume_fuel {
store.add_fuel(u64::max_value()).unwrap();
}
let mut wast_context = WastContext::new(store);
wast_context.register_spectest().unwrap(); wast_context.register_spectest().unwrap();
wast_context wast_context
.run_buffer(test.file, test.contents.as_bytes()) .run_buffer(test.file, test.contents.as_bytes())
@@ -505,32 +415,23 @@ pub fn spectest(fuzz_config: crate::generators::Config, test: crate::generators:
} }
/// Execute a series of `table.get` and `table.set` operations. /// Execute a series of `table.get` and `table.set` operations.
pub fn table_ops( pub fn table_ops(fuzz_config: generators::Config, ops: generators::table_ops::TableOps) {
fuzz_config: crate::generators::Config,
ops: crate::generators::table_ops::TableOps,
) {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let expected_drops = Arc::new(AtomicUsize::new(ops.num_params() as usize)); let expected_drops = Arc::new(AtomicUsize::new(ops.num_params() as usize));
let num_dropped = Arc::new(AtomicUsize::new(0)); let num_dropped = Arc::new(AtomicUsize::new(0));
{ {
let mut config = fuzz_config.to_wasmtime(); let mut store = fuzz_config.to_store();
config.wasm_reference_types(true);
config.consume_fuel(true);
let engine = Engine::new(&config).unwrap();
let mut store = create_store(&engine);
store.add_fuel(100).unwrap();
let wasm = ops.to_wasm_binary(); let wasm = ops.to_wasm_binary();
log_wasm(&wasm); log_wasm(&wasm);
let module = match Module::new(&engine, &wasm) { let module = match Module::new(store.engine(), &wasm) {
Ok(m) => m, Ok(m) => m,
Err(_) => return, Err(_) => return,
}; };
let mut linker = Linker::new(&engine); let mut linker = Linker::new(store.engine());
// To avoid timeouts, limit the number of explicit GCs we perform per // To avoid timeouts, limit the number of explicit GCs we perform per
// test case. // test case.
@@ -648,70 +549,6 @@ pub fn table_ops(
} }
} }
/// Configuration options for wasm-smith such that generated modules always
/// conform to certain specifications: one exported function, one exported
/// memory.
#[derive(Default, Debug, Arbitrary, Clone)]
pub struct SingleFunctionModuleConfig<const SIMD: bool, const BULK: bool>;
impl<const SIMD: bool, const BULK: bool> wasm_smith::Config
for SingleFunctionModuleConfig<SIMD, BULK>
{
fn allow_start_export(&self) -> bool {
false
}
fn min_types(&self) -> usize {
1
}
fn min_funcs(&self) -> usize {
1
}
fn max_funcs(&self) -> usize {
1
}
fn min_memories(&self) -> u32 {
1
}
fn max_memories(&self) -> usize {
1
}
fn max_imports(&self) -> usize {
0
}
fn min_exports(&self) -> usize {
2
}
fn max_memory_pages(&self, _is_64: bool) -> u64 {
1
}
fn memory_max_size_required(&self) -> bool {
true
}
// NaN is canonicalized at the wasm level for differential fuzzing so we
// can paper over NaN differences between engines.
fn canonicalize_nans(&self) -> bool {
true
}
fn simd_enabled(&self) -> bool {
SIMD
}
fn bulk_memory_enabled(&self) -> bool {
BULK
}
}
/// Perform differential execution between Cranelift and wasmi, diffing the /// Perform differential execution between Cranelift and wasmi, diffing the
/// resulting memory image when execution terminates. This relies on the /// resulting memory image when execution terminates. This relies on the
/// module-under-test to be instrumented to bound the execution time. Invoke /// module-under-test to be instrumented to bound the execution time. Invoke
@@ -720,7 +557,7 @@ impl<const SIMD: bool, const BULK: bool> wasm_smith::Config
/// ///
/// May return `None` if we early-out due to a rejected fuzz config; these /// May return `None` if we early-out due to a rejected fuzz config; these
/// should be rare if modules are generated appropriately. /// should be rare if modules are generated appropriately.
pub fn differential_wasmi_execution(wasm: &[u8], config: &crate::generators::Config) -> Option<()> { pub fn differential_wasmi_execution(wasm: &[u8], config: &generators::Config) -> Option<()> {
crate::init_fuzzing(); crate::init_fuzzing();
log_wasm(wasm); log_wasm(wasm);
@@ -810,7 +647,7 @@ pub fn differential_wasmi_execution(wasm: &[u8], config: &crate::generators::Con
/// specification interpreter. /// specification interpreter.
/// ///
/// May return `None` if we early-out due to a rejected fuzz config. /// May return `None` if we early-out due to a rejected fuzz config.
pub fn differential_spec_execution(wasm: &[u8], config: &crate::generators::Config) -> Option<()> { pub fn differential_spec_execution(wasm: &[u8], config: &generators::Config) -> Option<()> {
crate::init_fuzzing(); crate::init_fuzzing();
debug!("config: {:#?}", config); debug!("config: {:#?}", config);
log_wasm(wasm); log_wasm(wasm);
@@ -887,20 +724,10 @@ pub fn differential_spec_execution(wasm: &[u8], config: &crate::generators::Conf
fn differential_store( fn differential_store(
wasm: &[u8], wasm: &[u8],
fuzz_config: &crate::generators::Config, fuzz_config: &generators::Config,
) -> (Module, Store<StoreLimits>) { ) -> (Module, Store<StoreLimits>) {
let mut config = fuzz_config.to_wasmtime(); let store = fuzz_config.to_store();
// forcibly disable NaN canonicalization because wasm-smith has already let module = Module::new(store.engine(), &wasm).expect("Wasmtime can compile module");
// been configured to canonicalize everything at the wasm level.
config.cranelift_nan_canonicalization(false);
let engine = Engine::new(&config).unwrap();
let mut store = create_store(&engine);
if fuzz_config.consume_fuel {
store.add_fuel(u64::max_value()).unwrap();
}
let module = Module::new(&engine, &wasm).expect("Wasmtime can compile module");
(module, store) (module, store)
} }
@@ -908,7 +735,7 @@ fn differential_store(
/// its `Val` results. /// its `Val` results.
fn run_in_wasmtime( fn run_in_wasmtime(
wasm: &[u8], wasm: &[u8],
config: &crate::generators::Config, config: &generators::Config,
params: &[Val], params: &[Val],
) -> anyhow::Result<Vec<Val>> { ) -> anyhow::Result<Vec<Val>> {
// Instantiate wasmtime module and instance. // Instantiate wasmtime module and instance.

View File

@@ -15,7 +15,6 @@ use wasmtime::*;
/// from happening. /// from happening.
pub fn differential_v8_execution(wasm: &[u8], config: &crate::generators::Config) -> Option<()> { pub fn differential_v8_execution(wasm: &[u8], config: &crate::generators::Config) -> Option<()> {
// Wasmtime setup // Wasmtime setup
crate::init_fuzzing();
log_wasm(wasm); log_wasm(wasm);
let (wasmtime_module, mut wasmtime_store) = super::differential_store(wasm, config); let (wasmtime_module, mut wasmtime_store) = super::differential_store(wasm, config);
log::trace!("compiled module with wasmtime"); log::trace!("compiled module with wasmtime");
@@ -79,11 +78,15 @@ pub fn differential_v8_execution(wasm: &[u8], config: &crate::generators::Config
ValType::I64 => Val::I64(0), ValType::I64 => Val::I64(0),
ValType::F32 => Val::F32(0), ValType::F32 => Val::F32(0),
ValType::F64 => Val::F64(0), ValType::F64 => Val::F64(0),
ValType::FuncRef => Val::FuncRef(None),
ValType::ExternRef => Val::ExternRef(None),
_ => unimplemented!(), _ => unimplemented!(),
}); });
v8_params.push(match param { v8_params.push(match param {
ValType::I32 | ValType::F32 | ValType::F64 => v8::Number::new(&mut scope, 0.0).into(), ValType::I32 | ValType::F32 | ValType::F64 => v8::Number::new(&mut scope, 0.0).into(),
ValType::I64 => v8::BigInt::new_from_i64(&mut scope, 0).into(), ValType::I64 => v8::BigInt::new_from_i64(&mut scope, 0).into(),
ValType::FuncRef => v8::null(&mut scope).into(),
ValType::ExternRef => v8::null(&mut scope).into(),
_ => unimplemented!(), _ => unimplemented!(),
}); });
} }
@@ -231,6 +234,19 @@ fn assert_val_match(a: &Val, b: &v8::Local<'_, v8::Value>, scope: &mut v8::Handl
b.to_number(scope).unwrap().value(), b.to_number(scope).unwrap().value(),
); );
} }
// Externref values can only come from us, the embedder, and we only
// give wasm null, so these values should always be null.
Val::ExternRef(ref wasmtime) => {
assert!(wasmtime.is_none());
assert!(b.is_null());
}
// In general we can't equate function references since wasm modules can
// create references to internal functions via `func.ref`, so we don't
// equate values here.
Val::FuncRef(_) => {}
_ => panic!("unsupported match {:?}", a), _ => panic!("unsupported match {:?}", a),
} }

View File

@@ -19,7 +19,6 @@ libfuzzer-sys = "0.4.0"
target-lexicon = "0.12" target-lexicon = "0.12"
wasmtime = { path = "../crates/wasmtime" } wasmtime = { path = "../crates/wasmtime" }
wasmtime-fuzzing = { path = "../crates/fuzzing" } wasmtime-fuzzing = { path = "../crates/fuzzing" }
wasm-smith = "0.7.0"
[features] [features]
# Leave a stub feature with no side-effects in place for now: the OSS-Fuzz # Leave a stub feature with no side-effects in place for now: the OSS-Fuzz
@@ -76,20 +75,8 @@ test = false
doc = false doc = false
[[bin]] [[bin]]
name = "instantiate-wasm-smith" name = "compile-maybe-invalid"
path = "fuzz_targets/instantiate-wasm-smith.rs" path = "fuzz_targets/compile-maybe-invalid.rs"
test = false
doc = false
[[bin]]
name = "instantiate-swarm"
path = "fuzz_targets/instantiate-swarm.rs"
test = false
doc = false
[[bin]]
name = "instantiate-maybe-invalid"
path = "fuzz_targets/instantiate-maybe-invalid.rs"
test = false test = false
doc = false doc = false

View File

@@ -0,0 +1,12 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use wasmtime::{Engine, Module};
use wasmtime_fuzzing::wasm_smith::MaybeInvalidModule;
fuzz_target!(|module: MaybeInvalidModule| {
let engine = Engine::default();
let wasm = module.to_bytes();
wasmtime_fuzzing::oracles::log_wasm(&wasm);
drop(Module::new(&engine, &wasm));
});

View File

@@ -1,9 +1,10 @@
#![no_main] #![no_main]
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use wasmtime::Strategy; use wasmtime::{Engine, Module};
use wasmtime_fuzzing::oracles;
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
oracles::compile(data, Strategy::Cranelift); let engine = Engine::default();
wasmtime_fuzzing::oracles::log_wasm(data);
drop(Module::new(&engine, data));
}); });

View File

@@ -1,14 +1,24 @@
#![no_main] #![no_main]
use libfuzzer_sys::arbitrary::{Result, Unstructured};
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use wasmtime_fuzzing::{generators, oracles}; use wasmtime_fuzzing::{generators, oracles};
fuzz_target!(|data: ( fuzz_target!(|data: &[u8]| {
generators::Config, // errors in `run` have to do with not enough input in `data`, which we
generators::Config, // ignore here since it doesn't affect how we'd like to fuzz.
generators::GeneratedModule, drop(run(data));
)| {
let (lhs, rhs, mut wasm) = data;
wasm.module.ensure_termination(1000);
oracles::differential_execution(&wasm, &[lhs, rhs]);
}); });
fn run(data: &[u8]) -> Result<()> {
let mut u = Unstructured::new(data);
let lhs: generators::WasmtimeConfig = u.arbitrary()?;
let rhs: generators::WasmtimeConfig = u.arbitrary()?;
let config: generators::ModuleConfig = u.arbitrary()?;
let mut module = config.generate(&mut u)?;
module.ensure_termination(1000);
oracles::differential_execution(&module.to_bytes(), &config, &[lhs, rhs]);
Ok(())
}

View File

@@ -1,13 +1,27 @@
#![no_main] #![no_main]
use libfuzzer_sys::arbitrary::{Result, Unstructured};
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use wasmtime_fuzzing::{generators, oracles}; use wasmtime_fuzzing::{generators, oracles};
fuzz_target!(|data: ( fuzz_target!(|data: &[u8]| {
generators::Config, // errors in `run` have to do with not enough input in `data`, which we
wasm_smith::ConfiguredModule<oracles::SingleFunctionModuleConfig<true, true>> // ignore here since it doesn't affect how we'd like to fuzz.
)| { drop(run(data));
let (config, mut wasm) = data;
wasm.module.ensure_termination(1000);
oracles::differential_v8_execution(&wasm.module.to_bytes(), &config);
}); });
fn run(data: &[u8]) -> Result<()> {
let mut u = Unstructured::new(data);
let mut config: generators::Config = u.arbitrary()?;
config.module_config.set_differential_config();
// Enable features that v8 has implemented
config.module_config.config.simd_enabled = true;
config.module_config.config.bulk_memory_enabled = true;
config.module_config.config.reference_types_enabled = true;
let mut module = config.module_config.generate(&mut u)?;
module.ensure_termination(1000);
oracles::differential_v8_execution(&module.to_bytes(), &config);
Ok(())
}

View File

@@ -1,13 +1,21 @@
#![no_main] #![no_main]
use libfuzzer_sys::arbitrary::{Result, Unstructured};
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use wasmtime_fuzzing::{generators, oracles}; use wasmtime_fuzzing::{generators, oracles};
fuzz_target!(|data: ( fuzz_target!(|data: &[u8]| {
generators::Config, // errors in `run` have to do with not enough input in `data`, which we
wasm_smith::ConfiguredModule<oracles::SingleFunctionModuleConfig<false, false>> // ignore here since it doesn't affect how we'd like to fuzz.
)| { drop(run(data));
let (config, mut wasm) = data;
wasm.module.ensure_termination(1000);
oracles::differential_wasmi_execution(&wasm.module.to_bytes(), &config);
}); });
fn run(data: &[u8]) -> Result<()> {
let mut u = Unstructured::new(data);
let mut config: generators::Config = u.arbitrary()?;
config.module_config.set_differential_config();
let mut module = config.module_config.generate(&mut u)?;
module.ensure_termination(1000);
oracles::differential_wasmi_execution(&module.to_bytes(), &config);
Ok(())
}

View File

@@ -1,21 +0,0 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use std::time::Duration;
use wasm_smith::MaybeInvalidModule;
use wasmtime::Strategy;
use wasmtime_fuzzing::oracles::{self, Timeout};
fuzz_target!(|pair: (bool, MaybeInvalidModule)| {
let (timeout_with_time, module) = pair;
oracles::instantiate_with_config(
&module.to_bytes(),
false,
wasmtime_fuzzing::fuzz_default_config(Strategy::Auto).unwrap(),
if timeout_with_time {
Timeout::Time(Duration::from_secs(20))
} else {
Timeout::Fuel(100_000)
},
);
});

View File

@@ -1,41 +0,0 @@
#![no_main]
use libfuzzer_sys::arbitrary::{Result, Unstructured};
use libfuzzer_sys::fuzz_target;
use std::time::Duration;
use wasm_smith::{Module, SwarmConfig};
use wasmtime::Strategy;
use wasmtime_fuzzing::oracles::{self, Timeout};
fuzz_target!(|data: &[u8]| {
// errors in `run` have to do with not enough input in `data`, which we
// ignore here since it doesn't affect how we'd like to fuzz.
drop(run(data));
});
fn run(data: &[u8]) -> Result<()> {
let mut u = Unstructured::new(data);
let timeout = if u.arbitrary()? {
Timeout::Time(Duration::from_secs(20))
} else {
Timeout::Fuel(100_000)
};
// Further configure `SwarmConfig` after we generate one to enable features
// that aren't otherwise enabled by default. We want to test all of these in
// Wasmtime.
let mut config: SwarmConfig = u.arbitrary()?;
config.module_linking_enabled = u.arbitrary()?;
config.memory64_enabled = u.arbitrary()?;
// Don't generate modules that allocate more than 6GB
config.max_memory_pages = 6 << 30;
let module = Module::new(config.clone(), &mut u)?;
let mut cfg = wasmtime_fuzzing::fuzz_default_config(Strategy::Auto).unwrap();
cfg.wasm_multi_memory(config.max_memories > 1);
cfg.wasm_module_linking(config.module_linking_enabled);
cfg.wasm_memory64(config.memory64_enabled);
oracles::instantiate_with_config(&module.to_bytes(), true, cfg, timeout);
Ok(())
}

View File

@@ -1,12 +0,0 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use wasmtime::Strategy;
use wasmtime_fuzzing::{generators::GeneratedModule, oracles};
fuzz_target!(|module: GeneratedModule| {
let mut module = module;
module.module.ensure_termination(1000);
let wasm_bytes = module.module.to_bytes();
oracles::instantiate(&wasm_bytes, true, Strategy::Auto);
});

View File

@@ -1,9 +1,36 @@
#![no_main] #![no_main]
use libfuzzer_sys::arbitrary::{Result, Unstructured};
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use wasmtime::Strategy; use wasmtime_fuzzing::oracles::Timeout;
use wasmtime_fuzzing::oracles; use wasmtime_fuzzing::{generators, oracles};
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
oracles::instantiate(data, false, Strategy::Auto); // errors in `run` have to do with not enough input in `data`, which we
// ignore here since it doesn't affect how we'd like to fuzz.
drop(run(data));
}); });
fn run(data: &[u8]) -> Result<()> {
let mut u = Unstructured::new(data);
let mut config: generators::Config = u.arbitrary()?;
// Pick either fuel, duration-based, or module-based timeout. Note that the
// module-based timeout is implemented with wasm-smith's
// `ensure_termination` option.
let timeout = if u.arbitrary()? {
config.generate_timeout(&mut u)?
} else {
Timeout::None
};
// Enable module linking for this fuzz target specifically
config.module_config.config.module_linking_enabled = u.arbitrary()?;
let mut module = config.module_config.generate(&mut u)?;
if let Timeout::None = timeout {
module.ensure_termination(1000);
}
oracles::instantiate(&module.to_bytes(), true, &config, timeout);
Ok(())
}

View File

@@ -1,29 +0,0 @@
//! Regression tests for bugs found via fuzzing.
//!
//! The `#[test]` goes in here, the Wasm binary goes in
//! `./fuzzing/some-descriptive-name.wasm`, and then the `#[test]` should
//! use the Wasm binary by including it via
//! `include_bytes!("./fuzzing/some-descriptive-name.wasm")`.
use wasmtime::{Config, Strategy};
use wasmtime_fuzzing::oracles::{self, Timeout};
#[test]
fn instantiate_empty_module() {
let data = wat::parse_str(include_str!("./fuzzing/empty.wat")).unwrap();
oracles::instantiate(&data, true, Strategy::Auto);
}
#[test]
fn instantiate_empty_module_with_memory() {
let data = wat::parse_str(include_str!("./fuzzing/empty_with_memory.wat")).unwrap();
oracles::instantiate(&data, true, Strategy::Auto);
}
#[test]
fn instantiate_module_that_compiled_to_x64_has_register_32() {
let mut config = Config::new();
config.debug_info(true);
let data = wat::parse_str(include_str!("./fuzzing/issue694.wat")).unwrap();
oracles::instantiate_with_config(&data, true, config, Timeout::None);
}

View File

@@ -1,2 +0,0 @@
This directory contains `.wasm` binaries generated during fuzzing that uncovered
a bug, and which we now use as regression tests in `../fuzzing.rs`.

View File

@@ -1 +0,0 @@
(module)

View File

@@ -1 +0,0 @@
(module (memory 1))

View File

@@ -7,7 +7,6 @@ mod externals;
mod fuel; mod fuel;
mod func; mod func;
mod funcref; mod funcref;
mod fuzzing;
mod gc; mod gc;
mod globals; mod globals;
mod host_funcs; mod host_funcs;

View File

@@ -44,3 +44,9 @@
"\ff\ff\ff\ff\0f" ;; index == u32::MAX (local) "\ff\ff\ff\ff\0f" ;; index == u32::MAX (local)
"\00" ;; empty string name "\00" ;; empty string name
) )
;; empty module
(module)
;; empty module with memory
(module (memory 1))