Add example for WASM (#1093)
* add baby_fuzzer for wasm targets * elaborate in README --------- Co-authored-by: Dominik Maier <domenukk@gmail.com> Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com>
This commit is contained in:
parent
c0f229ec23
commit
0727c80347
8
.github/workflows/build_and_test.yml
vendored
8
.github/workflows/build_and_test.yml
vendored
@ -199,6 +199,14 @@ jobs:
|
|||||||
uses: baptiste0928/cargo-install@v1.3.0
|
uses: baptiste0928/cargo-install@v1.3.0
|
||||||
with:
|
with:
|
||||||
crate: cargo-make
|
crate: cargo-make
|
||||||
|
- name: install wasm-pack
|
||||||
|
uses: baptiste0928/cargo-install@v1.3.0
|
||||||
|
with:
|
||||||
|
crate: wasm-pack
|
||||||
|
- name: install chrome
|
||||||
|
uses: browser-actions/setup-chrome@v1
|
||||||
|
with:
|
||||||
|
chrome-version: stable
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true # recursively checkout submodules
|
submodules: true # recursively checkout submodules
|
||||||
|
2
fuzzers/baby_fuzzer_wasm/.cargo/config.toml
Normal file
2
fuzzers/baby_fuzzer_wasm/.cargo/config.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[build]
|
||||||
|
target = "wasm32-unknown-unknown"
|
6
fuzzers/baby_fuzzer_wasm/.gitignore
vendored
Normal file
6
fuzzers/baby_fuzzer_wasm/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
|
bin/
|
||||||
|
wasm-pack.log
|
||||||
|
.idea/
|
35
fuzzers/baby_fuzzer_wasm/Cargo.toml
Normal file
35
fuzzers/baby_fuzzer_wasm/Cargo.toml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
[package]
|
||||||
|
name = "baby_fuzzer_wasm"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Addison Crump <research@addisoncrump.info>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["console_error_panic_hook"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
js-sys = "0.3"
|
||||||
|
wasm-bindgen = "0.2.63"
|
||||||
|
|
||||||
|
libafl = { path = "../../libafl", default-features = false }
|
||||||
|
|
||||||
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
|
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||||
|
# code size when deploying.
|
||||||
|
console_error_panic_hook = { version = "0.1.6", optional = true }
|
||||||
|
|
||||||
|
[dependencies.web-sys]
|
||||||
|
version = "0.3"
|
||||||
|
features = ['console', 'Window', 'Performance', 'PerformanceTiming']
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
wasm-bindgen-test = "0.3.13"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
29
fuzzers/baby_fuzzer_wasm/Makefile.toml
Normal file
29
fuzzers/baby_fuzzer_wasm/Makefile.toml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
[env]
|
||||||
|
FUZZER_NAME="fuzzer"
|
||||||
|
PROJECT_DIR = { script = ["pwd"] }
|
||||||
|
|
||||||
|
[tasks.unsupported]
|
||||||
|
script_runner="@shell"
|
||||||
|
script='''
|
||||||
|
echo "Cargo-make not integrated yet on this"
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Fuzzer
|
||||||
|
[tasks.build]
|
||||||
|
command = "wasm-pack"
|
||||||
|
args = ["build", "--target", "web"]
|
||||||
|
|
||||||
|
# Test
|
||||||
|
[tasks.test]
|
||||||
|
linux_alias = "test_unix"
|
||||||
|
mac_alias = "test_unix"
|
||||||
|
windows_alias = "unsupported"
|
||||||
|
|
||||||
|
[tasks.test_unix]
|
||||||
|
command = "wasm-pack"
|
||||||
|
args = ["test", "--chrome", "--headless"]
|
||||||
|
|
||||||
|
# Clean
|
||||||
|
[tasks.clean]
|
||||||
|
command = "cargo"
|
||||||
|
args = ["clean"]
|
12
fuzzers/baby_fuzzer_wasm/README.md
Normal file
12
fuzzers/baby_fuzzer_wasm/README.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# libafl-wasm
|
||||||
|
|
||||||
|
A brief demo demonstrating libafl's compatibility with WASM, and how to do it.
|
||||||
|
|
||||||
|
In this example, the entire LibAFL harness and target are present in a WASM binary, which is then loaded by [the example
|
||||||
|
webpage](pkg/index.html). To run this example, do `cargo make build`, then open [the example webpage](pkg/index.html) in
|
||||||
|
your browser (via something like `python3 -m http.server`). The fuzzer will execute until finding a solution and will
|
||||||
|
write the fuzzer log to your console.
|
||||||
|
|
||||||
|
In a real fuzzing campaign, you would likely need to also create a LibAFL Corpus implementation which was backed by
|
||||||
|
JavaScript, and restart the fuzzing campaign by re-invoking the fuzzer and providing the associated corpora. This is
|
||||||
|
not demonstrated in this barebones example.
|
1
fuzzers/baby_fuzzer_wasm/pkg/.gitignore
vendored
Normal file
1
fuzzers/baby_fuzzer_wasm/pkg/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*
|
14
fuzzers/baby_fuzzer_wasm/pkg/index.html
Normal file
14
fuzzers/baby_fuzzer_wasm/pkg/index.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>libafl_wasm test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module">
|
||||||
|
import libafl_wasm from './libafl_wasm.js'
|
||||||
|
|
||||||
|
libafl_wasm().then(wasm => wasm.fuzz())
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
15
fuzzers/baby_fuzzer_wasm/pkg/package.json
Normal file
15
fuzzers/baby_fuzzer_wasm/pkg/package.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "baby_fuzzer_wasm",
|
||||||
|
"collaborators": [
|
||||||
|
"Addison Crump <research@addisoncrump.info>"
|
||||||
|
],
|
||||||
|
"version": "0.1.0",
|
||||||
|
"files": [
|
||||||
|
"baby_fuzzer_wasm_bg.wasm",
|
||||||
|
"baby_fuzzer_wasm.js",
|
||||||
|
"baby_fuzzer_wasm.d.ts"
|
||||||
|
],
|
||||||
|
"module": "baby_fuzzer_wasm.js",
|
||||||
|
"types": "baby_fuzzer_wasm.d.ts",
|
||||||
|
"sideEffects": false
|
||||||
|
}
|
134
fuzzers/baby_fuzzer_wasm/src/lib.rs
Normal file
134
fuzzers/baby_fuzzer_wasm/src/lib.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
mod utils;
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
bolts::{current_nanos, rands::StdRand, tuples::tuple_list, AsSlice},
|
||||||
|
corpus::{Corpus, InMemoryCorpus},
|
||||||
|
events::SimpleEventManager,
|
||||||
|
executors::{ExitKind, InProcessExecutor},
|
||||||
|
feedbacks::{CrashFeedback, MaxMapFeedback},
|
||||||
|
generators::RandPrintablesGenerator,
|
||||||
|
inputs::{BytesInput, HasTargetBytes},
|
||||||
|
monitors::SimpleMonitor,
|
||||||
|
mutators::{havoc_mutations, StdScheduledMutator},
|
||||||
|
observers::StdMapObserver,
|
||||||
|
schedulers::QueueScheduler,
|
||||||
|
stages::StdMutationalStage,
|
||||||
|
state::{HasSolutions, StdState},
|
||||||
|
Fuzzer, StdFuzzer,
|
||||||
|
};
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use web_sys::{Performance, Window};
|
||||||
|
|
||||||
|
use crate::utils::set_panic_hook;
|
||||||
|
|
||||||
|
// defined for internal use by libafl
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn external_current_millis() -> u64 {
|
||||||
|
let window: Window = web_sys::window().expect("should be in browser to run this demo");
|
||||||
|
let performance: Performance = window
|
||||||
|
.performance()
|
||||||
|
.expect("should be in browser to run this demo");
|
||||||
|
let now = performance.now() as u64;
|
||||||
|
now
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn fuzz() {
|
||||||
|
set_panic_hook();
|
||||||
|
|
||||||
|
let mut signals = [0u8; 64];
|
||||||
|
let signals_ptr = signals.as_mut_ptr();
|
||||||
|
let signals_set = |i: usize| unsafe {
|
||||||
|
*signals_ptr.offset(i as isize) += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The closure that we want to fuzz
|
||||||
|
let mut harness = |input: &BytesInput| {
|
||||||
|
let target = input.target_bytes();
|
||||||
|
let buf = target.as_slice();
|
||||||
|
signals_set(0);
|
||||||
|
if !buf.is_empty() && buf[0] == b'a' {
|
||||||
|
signals_set(1);
|
||||||
|
if buf.len() > 1 && buf[1] == b'b' {
|
||||||
|
signals_set(2);
|
||||||
|
#[allow(clippy::manual_assert)]
|
||||||
|
if buf.len() > 2 && buf[2] == b'c' {
|
||||||
|
// WASM cannot handle traps: https://webassembly.github.io/spec/core/intro/overview.html
|
||||||
|
// in a "real" fuzzing campaign, you should prefer to setup trap handling in JS,
|
||||||
|
// but we do not do this for demonstration purposes
|
||||||
|
return ExitKind::Crash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExitKind::Ok
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create an observation channel using the signals map
|
||||||
|
let observer =
|
||||||
|
unsafe { StdMapObserver::from_mut_ptr("signals", signals.as_mut_ptr(), signals.len()) };
|
||||||
|
|
||||||
|
// Feedback to rate the interestingness of an input
|
||||||
|
let mut feedback = MaxMapFeedback::new(&observer);
|
||||||
|
|
||||||
|
// A feedback to choose if an input is a solution or not
|
||||||
|
let mut objective = CrashFeedback::new();
|
||||||
|
|
||||||
|
// create a State from scratch
|
||||||
|
let mut state = StdState::new(
|
||||||
|
// RNG
|
||||||
|
StdRand::with_seed(current_nanos()),
|
||||||
|
// Corpus that will be evolved, we keep it in memory for performance
|
||||||
|
InMemoryCorpus::new(),
|
||||||
|
// In a "real" fuzzing campaign, you should stash solutions in a JS array instead
|
||||||
|
InMemoryCorpus::new(),
|
||||||
|
// States of the feedbacks.
|
||||||
|
// The feedbacks can report the data that should persist in the State.
|
||||||
|
&mut feedback,
|
||||||
|
// Same for objective feedbacks
|
||||||
|
&mut objective,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// The Monitor trait define how the fuzzer stats are reported to the user
|
||||||
|
let monitor = SimpleMonitor::new(|s| {
|
||||||
|
web_sys::console::log_1(&s.into());
|
||||||
|
});
|
||||||
|
|
||||||
|
// The event manager handle the various events generated during the fuzzing loop
|
||||||
|
// such as the notification of the addition of a new item to the corpus
|
||||||
|
let mut mgr = SimpleEventManager::new(monitor);
|
||||||
|
|
||||||
|
// A queue policy to get testcasess from the corpus
|
||||||
|
let scheduler = QueueScheduler::new();
|
||||||
|
|
||||||
|
// A fuzzer with feedbacks and a corpus scheduler
|
||||||
|
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||||
|
|
||||||
|
// Create the executor for an in-process function with just one observer
|
||||||
|
let mut executor = InProcessExecutor::new(
|
||||||
|
&mut harness,
|
||||||
|
tuple_list!(observer),
|
||||||
|
&mut fuzzer,
|
||||||
|
&mut state,
|
||||||
|
&mut mgr,
|
||||||
|
)
|
||||||
|
.expect("Failed to create the Executor");
|
||||||
|
|
||||||
|
// Generator of printable bytearrays of max size 32
|
||||||
|
let mut generator = RandPrintablesGenerator::new(32);
|
||||||
|
|
||||||
|
// Generate 8 initial inputs
|
||||||
|
state
|
||||||
|
.generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
|
||||||
|
.expect("Failed to generate the initial corpus");
|
||||||
|
|
||||||
|
// Setup a mutational stage with a basic bytes mutator
|
||||||
|
let mutator = StdScheduledMutator::new(havoc_mutations());
|
||||||
|
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
|
||||||
|
|
||||||
|
while state.solutions().is_empty() {
|
||||||
|
fuzzer
|
||||||
|
.fuzz_one(&mut stages, &mut executor, &mut state, &mut mgr)
|
||||||
|
.expect("Error in the fuzzing loop");
|
||||||
|
}
|
||||||
|
}
|
10
fuzzers/baby_fuzzer_wasm/src/utils.rs
Normal file
10
fuzzers/baby_fuzzer_wasm/src/utils.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
pub fn set_panic_hook() {
|
||||||
|
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||||
|
// `set_panic_hook` function at least once during initialization, and then
|
||||||
|
// we will get better error messages if our code ever panics.
|
||||||
|
//
|
||||||
|
// For more details see
|
||||||
|
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||||
|
#[cfg(feature = "console_error_panic_hook")]
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
}
|
14
fuzzers/baby_fuzzer_wasm/tests/web.rs
Normal file
14
fuzzers/baby_fuzzer_wasm/tests/web.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
//! Test suite for the Web and headless browsers.
|
||||||
|
|
||||||
|
#![cfg(target_arch = "wasm32")]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen_test;
|
||||||
|
use baby_fuzzer_wasm::fuzz;
|
||||||
|
use wasm_bindgen_test::*;
|
||||||
|
|
||||||
|
wasm_bindgen_test_configure!(run_in_browser);
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn test_fuzz() {
|
||||||
|
fuzz();
|
||||||
|
}
|
15
fuzzers/baby_fuzzer_wasm/webdriver.json
Normal file
15
fuzzers/baby_fuzzer_wasm/webdriver.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"moz:firefoxOptions": {
|
||||||
|
"prefs": {
|
||||||
|
"media.navigator.streams.fake": true,
|
||||||
|
"media.navigator.permission.disabled": true
|
||||||
|
},
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
"goog:chromeOptions": {
|
||||||
|
"args": [
|
||||||
|
"--use-fake-device-for-media-stream",
|
||||||
|
"--use-fake-ui-for-media-stream"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user