libafl_sugar (#215)
* fuzzer mod * libafl_sugar skeleton * build libafl_sugar * libfuzzer_stb_image_sugar * Delete log * qemu in libafl_sugar * docker * macos merda Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
parent
9591ed995e
commit
dfe39e2af7
@ -12,6 +12,7 @@ members = [
|
||||
"libafl_targets",
|
||||
"libafl_frida",
|
||||
"libafl_qemu",
|
||||
"libafl_sugar",
|
||||
"libafl_tests",
|
||||
]
|
||||
default-members = [
|
||||
|
@ -41,6 +41,9 @@ COPY libafl_qemu/Cargo.toml libafl_qemu/build.rs libafl_qemu/
|
||||
COPY scripts/dummy.rs libafl_qemu/src/lib.rs
|
||||
COPY libafl_qemu/src/weaks.c libafl_qemu/src/weaks.c
|
||||
|
||||
COPY libafl_sugar/Cargo.toml libafl_sugar/
|
||||
COPY scripts/dummy.rs libafl_sugar/src/lib.rs
|
||||
|
||||
COPY libafl_cc/Cargo.toml libafl_cc/Cargo.toml
|
||||
COPY scripts/dummy.rs libafl_cc/src/lib.rs
|
||||
COPY libafl_cc/build.rs libafl_cc/build.rs
|
||||
|
@ -127,7 +127,7 @@ pub fn libafl_main() {
|
||||
)
|
||||
});
|
||||
|
||||
// Create a PNG dictionary if not existing
|
||||
// Create a dictionary if not existing
|
||||
if state.metadata().get::<Tokens>().is_none() {
|
||||
for tokens_file in &token_files {
|
||||
state.add_metadata(Tokens::from_tokens_file(tokens_file)?);
|
||||
|
1
fuzzers/libfuzzer_stb_image_sugar/.gitignore
vendored
Normal file
1
fuzzers/libfuzzer_stb_image_sugar/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
libpng-*
|
25
fuzzers/libfuzzer_stb_image_sugar/Cargo.toml
Normal file
25
fuzzers/libfuzzer_stb_image_sugar/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "libfuzzer_stb_image"
|
||||
version = "0.5.0"
|
||||
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../../libafl/" }
|
||||
libafl_targets = { path = "../../libafl_targets/", features = ["sancov_pcguard_edges", "sancov_cmplog", "libfuzzer"] }
|
||||
libafl_sugar = { path = "../../libafl_sugar/" }
|
||||
|
||||
[build-dependencies]
|
||||
cc = { version = "1.0", features = ["parallel"] }
|
||||
num_cpus = "1.0"
|
71
fuzzers/libfuzzer_stb_image_sugar/Makefile
Normal file
71
fuzzers/libfuzzer_stb_image_sugar/Makefile
Normal file
@ -0,0 +1,71 @@
|
||||
FUZZER_NAME="libfuzzer_stb_image"
|
||||
PROJECT_DIR=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||
|
||||
PHONY: all
|
||||
|
||||
all: fuzzer
|
||||
|
||||
target/release/libafl_cxx: build.rs
|
||||
# Build the libpng libfuzzer library
|
||||
cargo build --release
|
||||
|
||||
libafl_cxx: target/release/libafl_cxx
|
||||
|
||||
libafl_cc: target/release/libafl_cxx
|
||||
|
||||
fuzzer: libafl_cxx
|
||||
# Build the libpng libfuzzer library
|
||||
cargo build --release
|
||||
cp $(PROJECT_DIR)/target/release/$(FUZZER_NAME) .
|
||||
|
||||
clean:
|
||||
rm ./$(FUZZER_NAME)
|
||||
|
||||
run: all
|
||||
./$(FUZZER_NAME) &
|
||||
sleep 0.2
|
||||
./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
|
||||
short_test: all
|
||||
timeout 11s ./$(FUZZER_NAME) &
|
||||
sleep 0.2
|
||||
timeout 10s taskset -c 0 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
timeout 10s taskset -c 1 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
timeout 10s taskset -c 2 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
timeout 10s taskset -c 3 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
|
||||
test: all
|
||||
timeout 60s ./$(FUZZER_NAME) &
|
||||
sleep 0.2
|
||||
timeout 59s taskset 0x00000001 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
timeout 59s taskset 0x00000002 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
timeout 59s taskset 0x00000004 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
timeout 59s taskset 0x00000008 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00000010 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00000020 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00000040 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00000080 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00000100 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00000200 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00000400 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00000800 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00001000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00002000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00004000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00008000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00010000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00020000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00040000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00080000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00100000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00200000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00400000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x00800000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x01000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x02000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x04000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x08000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x10000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x20000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x40000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
||||
# timeout 59s taskset 0x80000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null &
|
11
fuzzers/libfuzzer_stb_image_sugar/README.md
Normal file
11
fuzzers/libfuzzer_stb_image_sugar/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Libfuzzer for stb_image with libafl_sugar
|
||||
|
||||
This folder contains an example fuzzer for stb_image, using LLMP for fast multi-process fuzzing and crash detection.
|
||||
It has been tested on Linux and Windows.
|
||||
|
||||
## Build
|
||||
|
||||
To build this example, run `cargo build --release`.
|
||||
This will build the the fuzzer (src/main.rs) with the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback as a standalone binary.
|
||||
|
||||
Unlike the libpng example, in this example the harness (that entirely includes the program under test) is compiled in the `build.rs` file while building the crate, and linked with the fuzzer by cargo when producing the final binary, `target/release/libfuzzer_stb_image`.
|
27
fuzzers/libfuzzer_stb_image_sugar/build.rs
Normal file
27
fuzzers/libfuzzer_stb_image_sugar/build.rs
Normal file
@ -0,0 +1,27 @@
|
||||
// build.rs
|
||||
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let out_dir = out_dir.to_string_lossy().to_string();
|
||||
|
||||
println!("cargo:rerun-if-changed=harness.c");
|
||||
|
||||
// Enforce clang for its -fsanitize-coverage support.
|
||||
std::env::set_var("CC", "clang");
|
||||
std::env::set_var("CXX", "clang++");
|
||||
|
||||
cc::Build::new()
|
||||
// Use sanitizer coverage to track the edges in the PUT
|
||||
.flag("-fsanitize-coverage=trace-pc-guard,trace-cmp")
|
||||
// Take advantage of LTO (needs lld-link set in your cargo config)
|
||||
//.flag("-flto=thin")
|
||||
.flag("-Wno-sign-compare")
|
||||
.file("./harness.c")
|
||||
.compile("harness");
|
||||
|
||||
println!("cargo:rustc-link-search=native={}", &out_dir);
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
BIN
fuzzers/libfuzzer_stb_image_sugar/corpus/not_kitty.png
Normal file
BIN
fuzzers/libfuzzer_stb_image_sugar/corpus/not_kitty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 218 B |
BIN
fuzzers/libfuzzer_stb_image_sugar/corpus/not_kitty_alpha.png
Normal file
BIN
fuzzers/libfuzzer_stb_image_sugar/corpus/not_kitty_alpha.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 376 B |
BIN
fuzzers/libfuzzer_stb_image_sugar/corpus/not_kitty_gamma.png
Normal file
BIN
fuzzers/libfuzzer_stb_image_sugar/corpus/not_kitty_gamma.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 228 B |
BIN
fuzzers/libfuzzer_stb_image_sugar/corpus/not_kitty_icc.png
Normal file
BIN
fuzzers/libfuzzer_stb_image_sugar/corpus/not_kitty_icc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 427 B |
28
fuzzers/libfuzzer_stb_image_sugar/harness.c
Normal file
28
fuzzers/libfuzzer_stb_image_sugar/harness.c
Normal file
@ -0,0 +1,28 @@
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define STBI_ASSERT(x)
|
||||
#define STBI_NO_SIMD
|
||||
#define STBI_NO_LINEAR
|
||||
#define STBI_NO_STDIO
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
|
||||
#include "stb_image.h"
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
int x, y, channels;
|
||||
|
||||
if(!stbi_info_from_memory(data, size, &x, &y, &channels)) return 0;
|
||||
|
||||
/* exit if the image is larger than ~80MB */
|
||||
if(y && x > (80000000 / 4) / y) return 0;
|
||||
|
||||
unsigned char *img = stbi_load_from_memory(data, size, &x, &y, &channels, 4);
|
||||
|
||||
free(img);
|
||||
|
||||
// if (x > 10000) free(img); // free crash
|
||||
|
||||
return 0;
|
||||
}
|
44
fuzzers/libfuzzer_stb_image_sugar/src/main.rs
Normal file
44
fuzzers/libfuzzer_stb_image_sugar/src/main.rs
Normal file
@ -0,0 +1,44 @@
|
||||
//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts
|
||||
//! The example harness is built for `stb_image`.
|
||||
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
use libafl_sugar::InMemoryBytesCoverageSugar;
|
||||
use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input};
|
||||
|
||||
pub fn main() {
|
||||
// Registry the metadata types used in this fuzzer
|
||||
// Needed only on no_std
|
||||
//RegistryBuilder::register::<Tokens>();
|
||||
|
||||
println!(
|
||||
"Workdir: {:?}",
|
||||
env::current_dir().unwrap().to_string_lossy().to_string()
|
||||
);
|
||||
fuzz(
|
||||
&[PathBuf::from("./input")],
|
||||
PathBuf::from("./output"),
|
||||
&[1],
|
||||
1337,
|
||||
);
|
||||
}
|
||||
|
||||
/// The actual fuzzer
|
||||
fn fuzz(input_dirs: &[PathBuf], output_dir: PathBuf, cores: &[usize], broker_port: u16) {
|
||||
// Call LLVMFUzzerInitialize() if present.
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if libfuzzer_initialize(&args) == -1 {
|
||||
println!("Warning: LLVMFuzzerInitialize failed with -1")
|
||||
}
|
||||
|
||||
InMemoryBytesCoverageSugar::builder()
|
||||
.input_dirs(input_dirs)
|
||||
.output_dir(output_dir)
|
||||
.cores(cores)
|
||||
.broker_port(broker_port)
|
||||
.harness(|buf| {
|
||||
libfuzzer_test_one_input(buf);
|
||||
})
|
||||
.build()
|
||||
.run();
|
||||
}
|
7762
fuzzers/libfuzzer_stb_image_sugar/stb_image.h
Normal file
7762
fuzzers/libfuzzer_stb_image_sugar/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
13
libafl_sugar/Cargo.toml
Normal file
13
libafl_sugar/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "libafl_sugar"
|
||||
version = "0.1.0"
|
||||
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../libafl", version = "0.5.0" }
|
||||
libafl_targets = { path = "../libafl_targets", version = "0.5.0" }
|
||||
libafl_qemu = { path = "../libafl_qemu", version = "0.5.0" }
|
||||
typed-builder = "0.9.0" # Implement the builder pattern at compiletime
|
238
libafl_sugar/src/inmemory.rs
Normal file
238
libafl_sugar/src/inmemory.rs
Normal file
@ -0,0 +1,238 @@
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
use std::{fs, net::SocketAddr, path::PathBuf, time::Duration};
|
||||
|
||||
use libafl::{
|
||||
bolts::{
|
||||
current_nanos,
|
||||
launcher::Launcher,
|
||||
rands::StdRand,
|
||||
shmem::{ShMemProvider, StdShMemProvider},
|
||||
tuples::{tuple_list, Merge},
|
||||
},
|
||||
corpus::{
|
||||
CachedOnDiskCorpus, Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus,
|
||||
QueueCorpusScheduler,
|
||||
},
|
||||
executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor},
|
||||
feedback_or, feedback_or_fast,
|
||||
feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
generators::RandBytesGenerator,
|
||||
inputs::{BytesInput, HasTargetBytes},
|
||||
mutators::scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator},
|
||||
mutators::token_mutations::Tokens,
|
||||
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
|
||||
stages::StdMutationalStage,
|
||||
state::{HasCorpus, HasMetadata, StdState},
|
||||
stats::MultiStats,
|
||||
};
|
||||
|
||||
use libafl_targets::{EDGES_MAP, MAX_EDGES_NUM};
|
||||
|
||||
use crate::{CORPUS_CACHE_SIZE, DEFAULT_TIMEOUT_SECS};
|
||||
|
||||
#[derive(TypedBuilder)]
|
||||
pub struct InMemoryBytesCoverageSugar<'a, H>
|
||||
where
|
||||
H: FnMut(&[u8]),
|
||||
{
|
||||
/// Laucher configuration (default is random)
|
||||
#[builder(default = None, setter(strip_option))]
|
||||
configuration: Option<String>,
|
||||
/// Timeout of the executor
|
||||
#[builder(default = None, setter(strip_option))]
|
||||
timeout: Option<u64>,
|
||||
/// Input directories
|
||||
input_dirs: &'a [PathBuf],
|
||||
/// Output directory
|
||||
output_dir: PathBuf,
|
||||
/// Dictionary
|
||||
#[builder(default = None, setter(strip_option))]
|
||||
tokens_file: Option<PathBuf>,
|
||||
/// Flag if use CmpLog
|
||||
//#[builder(default = false)]
|
||||
//use_cmplog: bool,
|
||||
#[builder(default = 1337_u16)]
|
||||
broker_port: u16,
|
||||
/// The list of cores to run on
|
||||
cores: &'a [usize],
|
||||
/// The `ip:port` address of another broker to connect our new broker to for multi-machine
|
||||
/// clusters.
|
||||
#[builder(default = None, setter(strip_option))]
|
||||
remote_broker_addr: Option<SocketAddr>,
|
||||
/// Bytes harness
|
||||
#[builder(setter(strip_option))]
|
||||
harness: Option<H>,
|
||||
}
|
||||
|
||||
impl<'a, H> InMemoryBytesCoverageSugar<'a, H>
|
||||
where
|
||||
H: FnMut(&[u8]),
|
||||
{
|
||||
pub fn run(&mut self) {
|
||||
let conf = self
|
||||
.configuration
|
||||
.take()
|
||||
.unwrap_or_else(|| "default".into());
|
||||
|
||||
let timeout = Duration::from_secs(self.timeout.unwrap_or(DEFAULT_TIMEOUT_SECS));
|
||||
|
||||
let mut out_dir = self.output_dir.clone();
|
||||
if fs::create_dir(&out_dir).is_err() {
|
||||
println!("Out dir at {:?} already exists.", &out_dir);
|
||||
if !out_dir.is_dir() {
|
||||
panic!("Out dir at {:?} is not a valid directory!", &out_dir);
|
||||
}
|
||||
}
|
||||
let mut crashes = out_dir.clone();
|
||||
crashes.push("crashes");
|
||||
out_dir.push("queue");
|
||||
|
||||
let mut harness_bytes = self.harness.take().unwrap();
|
||||
|
||||
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
|
||||
|
||||
let stats = MultiStats::new(|s| println!("{}", s));
|
||||
|
||||
let mut run_client = |state: Option<StdState<_, _, _, _, _>>, mut mgr| {
|
||||
// Create an observation channel using the coverage map
|
||||
let edges = unsafe { &mut EDGES_MAP[0..MAX_EDGES_NUM] };
|
||||
let edges_observer = HitcountsMapObserver::new(StdMapObserver::new("edges", edges));
|
||||
|
||||
// Create an observation channel to keep track of the execution time
|
||||
let time_observer = TimeObserver::new("time");
|
||||
|
||||
// The state of the edges feedback.
|
||||
let feedback_state = MapFeedbackState::with_observer(&edges_observer);
|
||||
|
||||
// Feedback to rate the interestingness of an input
|
||||
// This one is composed by two Feedbacks in OR
|
||||
let feedback = feedback_or!(
|
||||
// New maximization map feedback linked to the edges observer and the feedback state
|
||||
MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false),
|
||||
// Time feedback, this one does not need a feedback state
|
||||
TimeFeedback::new_with_observer(&time_observer)
|
||||
);
|
||||
|
||||
// A feedback to choose if an input is a solution or not
|
||||
let objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
StdState::new(
|
||||
// RNG
|
||||
StdRand::with_seed(current_nanos()),
|
||||
// Corpus that will be evolved, we keep a part in memory for performance
|
||||
CachedOnDiskCorpus::new(out_dir.clone(), CORPUS_CACHE_SIZE).unwrap(),
|
||||
// Corpus in which we store solutions (crashes in this example),
|
||||
// on disk so the user can get them after stopping the fuzzer
|
||||
OnDiskCorpus::new(crashes.clone()).unwrap(),
|
||||
// States of the feedbacks.
|
||||
// They are the data related to the feedbacks that you want to persist in the State.
|
||||
tuple_list!(feedback_state),
|
||||
)
|
||||
});
|
||||
|
||||
// Create a dictionary if not existing
|
||||
if let Some(tokens_file) = &self.tokens_file {
|
||||
if state.metadata().get::<Tokens>().is_none() {
|
||||
state.add_metadata(Tokens::from_tokens_file(tokens_file)?);
|
||||
}
|
||||
}
|
||||
|
||||
// A minimization+queue policy to get testcasess from the corpus
|
||||
let scheduler =
|
||||
IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
|
||||
|
||||
// A fuzzer with feedbacks and a corpus scheduler
|
||||
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||
|
||||
// The wrapped harness function, calling out to the LLVM-style harness
|
||||
let mut harness = |input: &BytesInput| {
|
||||
let target = input.target_bytes();
|
||||
let buf = target.as_slice();
|
||||
(harness_bytes)(buf);
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
// Create the executor for an in-process function with one observer for edge coverage and one for the execution time
|
||||
let mut executor = TimeoutExecutor::new(
|
||||
InProcessExecutor::new(
|
||||
&mut harness,
|
||||
tuple_list!(edges_observer, time_observer),
|
||||
&mut fuzzer,
|
||||
&mut state,
|
||||
&mut mgr,
|
||||
)?,
|
||||
timeout,
|
||||
);
|
||||
|
||||
// In case the corpus is empty (on first run), reset
|
||||
if state.corpus().count() < 1 {
|
||||
if self.input_dirs.is_empty() {
|
||||
// Generator of printable bytearrays of max size 32
|
||||
let mut generator = RandBytesGenerator::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");
|
||||
println!(
|
||||
"We imported {} inputs from the generator.",
|
||||
state.corpus().count()
|
||||
);
|
||||
} else {
|
||||
println!("Loading from {:?}", &self.input_dirs);
|
||||
// Load from disk
|
||||
state
|
||||
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &self.input_dirs)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("Failed to load initial corpus at {:?}", &self.input_dirs)
|
||||
});
|
||||
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||
}
|
||||
}
|
||||
|
||||
if self.tokens_file.is_some() {
|
||||
// Setup a basic mutator
|
||||
let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
|
||||
let mutational = StdMutationalStage::new(mutator);
|
||||
|
||||
// The order of the stages matter!
|
||||
let mut stages = tuple_list!(mutational);
|
||||
|
||||
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
|
||||
} else {
|
||||
// Setup a basic mutator
|
||||
let mutator = StdScheduledMutator::new(havoc_mutations());
|
||||
let mutational = StdMutationalStage::new(mutator);
|
||||
|
||||
// The order of the stages matter!
|
||||
let mut stages = tuple_list!(mutational);
|
||||
|
||||
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let launcher = Launcher::builder()
|
||||
.shmem_provider(shmem_provider)
|
||||
.configuration(conf)
|
||||
.stats(stats)
|
||||
.run_client(&mut run_client)
|
||||
.cores(&self.cores)
|
||||
.broker_port(self.broker_port)
|
||||
.remote_broker_addr(self.remote_broker_addr);
|
||||
#[cfg(unix)]
|
||||
let launcher = launcher.stdout_file(Some("/dev/null"));
|
||||
launcher.build().launch().expect("Launcher failed");
|
||||
}
|
||||
}
|
12
libafl_sugar/src/lib.rs
Normal file
12
libafl_sugar/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
||||
//! Sugar API to simplify the life of the naibe user of `LibAFL`
|
||||
|
||||
pub mod inmemory;
|
||||
pub use inmemory::InMemoryBytesCoverageSugar;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod qemu;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use qemu::QemuBytesCoverageSugar;
|
||||
|
||||
pub const DEFAULT_TIMEOUT_SECS: u64 = 1200;
|
||||
pub const CORPUS_CACHE_SIZE: usize = 4096;
|
244
libafl_sugar/src/qemu.rs
Normal file
244
libafl_sugar/src/qemu.rs
Normal file
@ -0,0 +1,244 @@
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
use std::{fs, net::SocketAddr, path::PathBuf, time::Duration};
|
||||
|
||||
use libafl::{
|
||||
bolts::{
|
||||
current_nanos,
|
||||
launcher::Launcher,
|
||||
rands::StdRand,
|
||||
shmem::{ShMemProvider, StdShMemProvider},
|
||||
tuples::{tuple_list, Merge},
|
||||
},
|
||||
corpus::{
|
||||
CachedOnDiskCorpus, Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus,
|
||||
QueueCorpusScheduler,
|
||||
},
|
||||
executors::{ExitKind, TimeoutExecutor},
|
||||
feedback_or, feedback_or_fast,
|
||||
feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
generators::RandBytesGenerator,
|
||||
inputs::{BytesInput, HasTargetBytes},
|
||||
mutators::scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator},
|
||||
mutators::token_mutations::Tokens,
|
||||
observers::{HitcountsMapObserver, TimeObserver, VariableMapObserver},
|
||||
stages::StdMutationalStage,
|
||||
state::{HasCorpus, HasMetadata, StdState},
|
||||
stats::MultiStats,
|
||||
};
|
||||
|
||||
pub use libafl_qemu::emu;
|
||||
use libafl_qemu::{hooks, QemuExecutor};
|
||||
|
||||
use crate::{CORPUS_CACHE_SIZE, DEFAULT_TIMEOUT_SECS};
|
||||
|
||||
#[derive(TypedBuilder)]
|
||||
pub struct QemuBytesCoverageSugar<'a, H>
|
||||
where
|
||||
H: FnMut(&[u8]),
|
||||
{
|
||||
/// Laucher configuration (default is random)
|
||||
#[builder(default = None, setter(strip_option))]
|
||||
configuration: Option<String>,
|
||||
/// Timeout of the executor
|
||||
#[builder(default = None, setter(strip_option))]
|
||||
timeout: Option<u64>,
|
||||
/// Input directories
|
||||
input_dirs: &'a [PathBuf],
|
||||
/// Output directory
|
||||
output_dir: PathBuf,
|
||||
/// Dictionary
|
||||
#[builder(default = None, setter(strip_option))]
|
||||
tokens_file: Option<PathBuf>,
|
||||
/// Flag if use CmpLog
|
||||
//#[builder(default = false)]
|
||||
//use_cmplog: bool,
|
||||
#[builder(default = 1337_u16)]
|
||||
broker_port: u16,
|
||||
/// The list of cores to run on
|
||||
cores: &'a [usize],
|
||||
/// The `ip:port` address of another broker to connect our new broker to for multi-machine
|
||||
/// clusters.
|
||||
#[builder(default = None, setter(strip_option))]
|
||||
remote_broker_addr: Option<SocketAddr>,
|
||||
/// Bytes harness
|
||||
#[builder(setter(strip_option))]
|
||||
harness: Option<H>,
|
||||
}
|
||||
|
||||
impl<'a, H> QemuBytesCoverageSugar<'a, H>
|
||||
where
|
||||
H: FnMut(&[u8]),
|
||||
{
|
||||
pub fn run(&mut self) {
|
||||
let conf = self
|
||||
.configuration
|
||||
.take()
|
||||
.unwrap_or_else(|| "default".into());
|
||||
|
||||
let timeout = Duration::from_secs(self.timeout.unwrap_or(DEFAULT_TIMEOUT_SECS));
|
||||
|
||||
let mut out_dir = self.output_dir.clone();
|
||||
if fs::create_dir(&out_dir).is_err() {
|
||||
println!("Out dir at {:?} already exists.", &out_dir);
|
||||
if !out_dir.is_dir() {
|
||||
panic!("Out dir at {:?} is not a valid directory!", &out_dir);
|
||||
}
|
||||
}
|
||||
let mut crashes = out_dir.clone();
|
||||
crashes.push("crashes");
|
||||
out_dir.push("queue");
|
||||
|
||||
let mut harness_bytes = self.harness.take().unwrap();
|
||||
|
||||
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
|
||||
|
||||
let stats = MultiStats::new(|s| println!("{}", s));
|
||||
|
||||
let mut run_client = |state: Option<StdState<_, _, _, _, _>>, mut mgr| {
|
||||
// Create an observation channel using the coverage map
|
||||
let edges = unsafe { &mut hooks::EDGES_MAP };
|
||||
let edges_counter = unsafe { &mut hooks::MAX_EDGES_NUM };
|
||||
let edges_observer =
|
||||
HitcountsMapObserver::new(VariableMapObserver::new("edges", edges, edges_counter));
|
||||
|
||||
// Create an observation channel to keep track of the execution time
|
||||
let time_observer = TimeObserver::new("time");
|
||||
|
||||
// The state of the edges feedback.
|
||||
let feedback_state = MapFeedbackState::with_observer(&edges_observer);
|
||||
|
||||
// Feedback to rate the interestingness of an input
|
||||
// This one is composed by two Feedbacks in OR
|
||||
let feedback = feedback_or!(
|
||||
// New maximization map feedback linked to the edges observer and the feedback state
|
||||
MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false),
|
||||
// Time feedback, this one does not need a feedback state
|
||||
TimeFeedback::new_with_observer(&time_observer)
|
||||
);
|
||||
|
||||
// A feedback to choose if an input is a solution or not
|
||||
let objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
StdState::new(
|
||||
// RNG
|
||||
StdRand::with_seed(current_nanos()),
|
||||
// Corpus that will be evolved, we keep a part in memory for performance
|
||||
CachedOnDiskCorpus::new(out_dir.clone(), CORPUS_CACHE_SIZE).unwrap(),
|
||||
// Corpus in which we store solutions (crashes in this example),
|
||||
// on disk so the user can get them after stopping the fuzzer
|
||||
OnDiskCorpus::new(crashes.clone()).unwrap(),
|
||||
// States of the feedbacks.
|
||||
// They are the data related to the feedbacks that you want to persist in the State.
|
||||
tuple_list!(feedback_state),
|
||||
)
|
||||
});
|
||||
|
||||
// Create a dictionary if not existing
|
||||
if let Some(tokens_file) = &self.tokens_file {
|
||||
if state.metadata().get::<Tokens>().is_none() {
|
||||
state.add_metadata(Tokens::from_tokens_file(tokens_file)?);
|
||||
}
|
||||
}
|
||||
|
||||
// A minimization+queue policy to get testcasess from the corpus
|
||||
let scheduler =
|
||||
IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
|
||||
|
||||
// A fuzzer with feedbacks and a corpus scheduler
|
||||
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||
|
||||
// The wrapped harness function, calling out to the LLVM-style harness
|
||||
let mut harness = |input: &BytesInput| {
|
||||
let target = input.target_bytes();
|
||||
let buf = target.as_slice();
|
||||
(harness_bytes)(buf);
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
let executor = QemuExecutor::new(
|
||||
&mut harness,
|
||||
tuple_list!(edges_observer, time_observer),
|
||||
&mut fuzzer,
|
||||
&mut state,
|
||||
&mut mgr,
|
||||
)?;
|
||||
|
||||
// Track edge coverage
|
||||
executor.hook_edge_generation(hooks::gen_unique_edge_ids);
|
||||
executor.hook_edge_execution(hooks::trace_edge_hitcount);
|
||||
|
||||
// Create the executor for an in-process function with one observer for edge coverage and one for the execution time
|
||||
let mut executor = TimeoutExecutor::new(executor, timeout);
|
||||
|
||||
// In case the corpus is empty (on first run), reset
|
||||
if state.corpus().count() < 1 {
|
||||
if self.input_dirs.is_empty() {
|
||||
// Generator of printable bytearrays of max size 32
|
||||
let mut generator = RandBytesGenerator::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");
|
||||
println!(
|
||||
"We imported {} inputs from the generator.",
|
||||
state.corpus().count()
|
||||
);
|
||||
} else {
|
||||
println!("Loading from {:?}", &self.input_dirs);
|
||||
// Load from disk
|
||||
state
|
||||
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &self.input_dirs)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("Failed to load initial corpus at {:?}", &self.input_dirs)
|
||||
});
|
||||
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||
}
|
||||
}
|
||||
|
||||
if self.tokens_file.is_some() {
|
||||
// Setup a basic mutator
|
||||
let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
|
||||
let mutational = StdMutationalStage::new(mutator);
|
||||
|
||||
// The order of the stages matter!
|
||||
let mut stages = tuple_list!(mutational);
|
||||
|
||||
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
|
||||
} else {
|
||||
// Setup a basic mutator
|
||||
let mutator = StdScheduledMutator::new(havoc_mutations());
|
||||
let mutational = StdMutationalStage::new(mutator);
|
||||
|
||||
// The order of the stages matter!
|
||||
let mut stages = tuple_list!(mutational);
|
||||
|
||||
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let launcher = Launcher::builder()
|
||||
.shmem_provider(shmem_provider)
|
||||
.configuration(conf)
|
||||
.stats(stats)
|
||||
.run_client(&mut run_client)
|
||||
.cores(&self.cores)
|
||||
.broker_port(self.broker_port)
|
||||
.remote_broker_addr(self.remote_broker_addr);
|
||||
#[cfg(unix)]
|
||||
let launcher = launcher.stdout_file(Some("/dev/null"));
|
||||
launcher.build().launch().expect("Launcher failed");
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user