Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dust off the fuzzers, update dependencies and use wasm-smith. #2186

Merged
merged 8 commits into from
Mar 16, 2021
6 changes: 3 additions & 3 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

[package]
name = "wasmer-bin-fuzz"
version = "0.0.0"
Expand All @@ -9,6 +8,9 @@ edition = "2018"
[package.metadata]
cargo-fuzz = true

[dependencies]
wasm-smith = "0.4.0"
libfuzzer-sys = "0.4.0"
[dependencies.wasmer]
path = "../lib/api"
[dependencies.wasmer-compiler-cranelift]
Expand All @@ -21,8 +23,6 @@ path = "../lib/compiler-singlepass"
path = "../lib/engine-jit"
[dependencies.wasmer-engine-native]
path = "../lib/engine-native"
[dependencies.libfuzzer-sys]
git = "https://github.com/rust-fuzz/libfuzzer-sys.git"

# Prevent this from interfering with workspaces
[workspace]
Expand Down
17 changes: 13 additions & 4 deletions fuzz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ You should see output that looks something like this:
#1409042 NEW cov: 115073 ft: 503951 corp: 4667/1814Kb lim: 4096 exec/s: 884 rss: 857Mb L: 174/4096 MS: 2 ChangeByte-ChangeASCIIInt-
```

It will continue to generate random inputs forever, until it finds a bug or is terminated. The testcases for bugs it finds go into `fuzz/artifacts/jit_cranelift` and you can rerun the fuzzer on a single input by passing it on the command line `cargo fuzz run jit_cranelift my_testcase.wasm`.
It will continue to generate random inputs forever, until it finds a bug or is terminated. The testcases for bugs it finds go into `fuzz/artifacts/jit_cranelift` and you can rerun the fuzzer on a single input by passing it on the command line `cargo fuzz run jit_cranelift /path/to/testcase`.

## Seeding the corpus, optional
## The corpus

The fuzzer works best when it has examples of small Wasm files to start with. Using `wast2json` from [wabt](https://github.com/WebAssembly/wabt), we can easily produce `.wasm` files out of the WebAssembly spec tests.
Each fuzzer has an individual corpus under fuzz/corpus/test_name, created on first run if not already present. The validate fuzzer works directly with `.wasm` files as bytes and works best if seeded with examples of small Wasm file. Using `wast2json` from [wabt](https://github.com/WebAssembly/wabt), we can easily produce `.wasm` files out of the WebAssembly spec tests.

```sh
mkdir spec-test-corpus
Expand All @@ -49,4 +49,13 @@ mv spec-test-corpus/*.wasm fuzz/corpus/validate/
rm -r spec-test-corpus
```

The corpus directory is created on the first run of the fuzzer. If it doesn't exist, run it first and then seed the corpus. The fuzzer will pick up new files added to the corpus while it is running.
The others fuzzers use `wasm-smith` which means that the testcase files are the input to the wasm generator, not the valid `.wasm` bytes themselves. In order to debug a testcase, you may find that you need to convert it into a `.wasm` file. Using the standalone `wasm-smith` tool doesn't work for this purpose because we use a custom configuration to our `wasm_smith::Module`. Instead, add some code to the fuzzer target:

```rust
use std::fs::File;
use std::io::Write;
let mut file = File::create("/tmp/crash.wasm").unwrap();
file.write_all(&wasm_bytes).unwrap();
```

and run it over just the one testcase.
25 changes: 0 additions & 25 deletions fuzz/fuzz_targets/headless_cranelift.rs

This file was deleted.

42 changes: 33 additions & 9 deletions fuzz/fuzz_targets/jit_cranelift.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,45 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;

use libfuzzer_sys::{arbitrary, arbitrary::Arbitrary, fuzz_target};
use wasm_smith::{Config, ConfiguredModule};
use wasmer::{imports, CompilerConfig, Instance, Module, Store};
use wasmer_compiler_cranelift::Cranelift;
use wasmer_engine_jit::JIT;

fuzz_target!(|wasm_bytes: &[u8]| {
#[derive(Arbitrary, Debug, Default, Copy, Clone)]
struct NoImportsConfig;
impl Config for NoImportsConfig {
fn max_imports(&self) -> usize {
0
}
fn max_memory_pages(&self) -> u32 {
// https://github.com/wasmerio/wasmer/issues/2187
65535
}
fn allow_start_export(&self) -> bool {
false
}
}

fuzz_target!(|module: ConfiguredModule<NoImportsConfig>| {
let wasm_bytes = module.to_bytes();
let mut compiler = Cranelift::default();
compiler.canonicalize_nans(true);
compiler.enable_verifier();
let store = Store::new(&JIT::new(compiler).engine());
match Module::validate(&store, wasm_bytes) {
Ok(_) => {
let module = Module::new(&store, wasm_bytes).unwrap();
let _instance = Instance::new(&module, &imports! {});
let module = Module::new(&store, &wasm_bytes).unwrap();
match Instance::new(&module, &imports! {}) {
Ok(_) => {}
Err(e) => {
let error_message = format!("{}", e);
if error_message
.contains("RuntimeError: memory out of bounds: data segment does not fit")
|| error_message
.contains("RuntimeError: table out of bounds: elements segment does not fit")
{
return;
}
panic!("{}", e);
}
Err(_) => {}
};
}
});
42 changes: 33 additions & 9 deletions fuzz/fuzz_targets/jit_llvm.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,45 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;

use libfuzzer_sys::{arbitrary, arbitrary::Arbitrary, fuzz_target};
use wasm_smith::{Config, ConfiguredModule};
use wasmer::{imports, CompilerConfig, Instance, Module, Store};
use wasmer_compiler_llvm::LLVM;
use wasmer_engine_jit::JIT;

fuzz_target!(|wasm_bytes: &[u8]| {
#[derive(Arbitrary, Debug, Default, Copy, Clone)]
struct NoImportsConfig;
impl Config for NoImportsConfig {
fn max_imports(&self) -> usize {
0
}
fn max_memory_pages(&self) -> u32 {
// https://github.com/wasmerio/wasmer/issues/2187
65535
}
fn allow_start_export(&self) -> bool {
false
}
}

fuzz_target!(|module: ConfiguredModule<NoImportsConfig>| {
let wasm_bytes = module.to_bytes();
let mut compiler = LLVM::default();
compiler.canonicalize_nans(true);
compiler.enable_verifier();
let store = Store::new(&JIT::new(compiler).engine());
match Module::validate(&store, wasm_bytes) {
Ok(_) => {
let module = Module::new(&store, wasm_bytes).unwrap();
let _instance = Instance::new(&module, &imports! {});
let module = Module::new(&store, &wasm_bytes).unwrap();
match Instance::new(&module, &imports! {}) {
Ok(_) => {}
Err(e) => {
let error_message = format!("{}", e);
if error_message
.contains("RuntimeError: memory out of bounds: data segment does not fit")
|| error_message
.contains("RuntimeError: table out of bounds: elements segment does not fit")
{
return;
}
panic!("{}", e);
}
Err(_) => {}
};
}
});
51 changes: 43 additions & 8 deletions fuzz/fuzz_targets/jit_singlepass.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,54 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;

use libfuzzer_sys::{arbitrary, arbitrary::Arbitrary, fuzz_target};
use wasm_smith::{Config, ConfiguredModule};
use wasmer::{imports, Instance, Module, Store};
use wasmer_compiler_singlepass::Singlepass;
use wasmer_engine_jit::JIT;

fuzz_target!(|wasm_bytes: &[u8]| {
#[derive(Arbitrary, Debug, Default, Copy, Clone)]
struct NoImportsConfig;
impl Config for NoImportsConfig {
fn max_imports(&self) -> usize {
0
}
fn max_memory_pages(&self) -> u32 {
// https://github.com/wasmerio/wasmer/issues/2187
65535
}
fn allow_start_export(&self) -> bool {
false
}
}

fuzz_target!(|module: ConfiguredModule<NoImportsConfig>| {
let wasm_bytes = module.to_bytes();
let compiler = Singlepass::default();
let store = Store::new(&JIT::new(compiler).engine());
match Module::validate(&store, wasm_bytes) {
Ok(_) => {
let module = Module::new(&store, wasm_bytes).unwrap();
let _instance = Instance::new(&module, &imports! {});
let module = Module::new(&store, &wasm_bytes);
let module = match module {
Ok(m) => m,
Err(e) => {
let error_message = format!("{}", e);
if error_message.contains("Validation error: invalid result arity: func type returns multiple values") || error_message.contains("Validation error: blocks, loops, and ifs accept no parameters when multi-value is not enabled") || error_message.contains("multi-value returns not yet implemented") {
return;
}
panic!("{}", e);
}
Err(_) => {}
};
match Instance::new(&module, &imports! {}) {
Ok(_) => {}
Err(e) => {
let error_message = format!("{}", e);
if
error_message
.contains("RuntimeError: memory out of bounds: data segment does not fit")
|| error_message
.contains("RuntimeError: table out of bounds: elements segment does not fit")
{
return;
}
panic!("{}", e);
}
}
});
45 changes: 35 additions & 10 deletions fuzz/fuzz_targets/native_cranelift.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,50 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;

use libfuzzer_sys::{arbitrary, arbitrary::Arbitrary, fuzz_target};
use wasm_smith::{Config, ConfiguredModule};
use wasmer::{imports, Instance, Module, Store};
use wasmer_compiler_cranelift::Cranelift;
use wasmer_engine_native::Native;

fuzz_target!(|wasm_bytes: &[u8]| {
#[derive(Arbitrary, Debug, Default, Copy, Clone)]
struct NoImportsConfig;
impl Config for NoImportsConfig {
fn max_imports(&self) -> usize {
0
}
fn max_memory_pages(&self) -> u32 {
// https://github.com/wasmerio/wasmer/issues/2187
65535
}
fn allow_start_export(&self) -> bool {
false
}
}

fuzz_target!(|module: ConfiguredModule<NoImportsConfig>| {
let serialized = {
let mut compiler = Cranelift::default();
let wasm_bytes = module.to_bytes();
let compiler = Cranelift::default();
let store = Store::new(&Native::new(compiler).engine());
match Module::validate(&store, wasm_bytes) {
Err(_) => return,
Ok(_) => {}
};
let module = Module::new(&store, wasm_bytes).unwrap();
let module = Module::new(&store, &wasm_bytes).unwrap();
module.serialize().unwrap()
};

let engine = Native::headless().engine();
let store = Store::new(&engine);
let module = unsafe { Module::deserialize(&store, serialized.as_slice()) }.unwrap();
let _instance = Instance::new(&module, &imports! {});
match Instance::new(&module, &imports! {}) {
Ok(_) => {}
Err(e) => {
let error_message = format!("{}", e);
if error_message
.contains("RuntimeError: memory out of bounds: data segment does not fit")
|| error_message
.contains("RuntimeError: table out of bounds: elements segment does not fit")
{
return;
}
panic!("{}", e);
}
}
});
7 changes: 3 additions & 4 deletions fuzz/fuzz_targets/validate.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;

use wasmer::{imports, Instance, Module, Store};
use libfuzzer_sys::fuzz_target;
use wasmer::{Module, Store};
use wasmer_compiler_cranelift::Cranelift;
use wasmer_engine_jit::JIT;

fuzz_target!(|wasm_bytes: &[u8]| {
let compiler = Cranelift::default();
let store = Store::new(&JIT::new(compiler).engine());
Module::validate(&store, wasm_bytes);
let _ignored = Module::validate(&store, wasm_bytes);
});