Skip to content

Commit

Permalink
Custom Executor Example (#2570)
Browse files Browse the repository at this point in the history
* [WIP] Custom Executor Example

* readme

* src/main.rs

* Finish

* fix warnings

* reame

* CI
  • Loading branch information
domenukk authored Nov 5, 2024
1 parent b5c9bff commit 36a24ab
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ jobs:
- ./fuzzers/baby/backtrace_baby_fuzzers/rust_code_with_inprocess_executor
- ./fuzzers/baby/backtrace_baby_fuzzers/command_executor
- ./fuzzers/baby/backtrace_baby_fuzzers/forkserver_executor
- ./fuzzers/baby/baby_fuzzer_custom_executor

# Binary-only
- ./fuzzers/binary_only/fuzzbench_fork_qemu
Expand Down
2 changes: 2 additions & 0 deletions fuzzers/baby/baby_fuzzer_custom_executor/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
libpng-*
corpus
27 changes: 27 additions & 0 deletions fuzzers/baby/baby_fuzzer_custom_executor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "fuzzer_custom_executor"
version = "0.13.2"
authors = [
"Andrea Fioraldi <[email protected]>",
"Dominik Maier <[email protected]>",
]
edition = "2021"

[features]
default = ["std"]
tui = []
std = []

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
lto = true
codegen-units = 1
opt-level = 3
debug = true

[dependencies]
libafl = { path = "../../../libafl/" }
libafl_bolts = { path = "../../../libafl_bolts/" }
50 changes: 50 additions & 0 deletions fuzzers/baby/baby_fuzzer_custom_executor/Makefile.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Variables
[env]
FUZZER_NAME = 'fuzzer_custom_executor'
PROJECT_DIR = { script = ["pwd"] }
CARGO_TARGET_DIR = { value = "target", condition = { env_not_set = [
"CARGO_TARGET_DIR",
] } }
PROFILE = { value = "release" }
PROFILE_DIR = { value = "release" }
FUZZER = '${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME}'

[tasks.build]
alias = "fuzzer"

[tasks.fuzzer]
description = "Build the fuzzer"
script = "cargo build --profile=${PROFILE}"

[tasks.run]
description = "Run the fuzzer"
command = "${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME}"
dependencies = ["fuzzer"]

[tasks.test]
description = "Run a short test"
linux_alias = "test_unix"
mac_alias = "test_unix"
windows_alias = "unsupported"

[tasks.test_unix]
script_runner = "@shell"
script = '''
timeout 30s ${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME} | tee fuzz_stdout.log || true
if grep -qa "objectives: 1" fuzz_stdout.log; then
echo "Fuzzer is working"
else
echo "Fuzzer does not generate any testcases or any crashes"
exit 1
fi
'''
dependencies = ["fuzzer"]

# Clean up
[tasks.clean]
# Disable default `clean` definition
clear = true
script_runner = "@shell"
script = '''
cargo clean
'''
12 changes: 12 additions & 0 deletions fuzzers/baby/baby_fuzzer_custom_executor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Baby fuzzer with Custom Executor

This is a minimalistic example about how to create a LibAFL-based fuzzer.

In contrast to the normal baby fuzzer, this uses a (very simple) custom executor.

The custom executor won't catch any timeouts or actual errors (i.e., memory corruptions, etc.) in the target.

The tested program is a simple Rust function without any instrumentation.
For real fuzzing, you will want to add some sort to add coverage or other feedback.

You can run this example using `cargo run`, and you can enable the TUI feature by running `cargo run --features tui`.
163 changes: 163 additions & 0 deletions fuzzers/baby/baby_fuzzer_custom_executor/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#[cfg(windows)]
use std::ptr::write_volatile;
use std::{
marker::PhantomData,
path::PathBuf,
ptr::{addr_of, addr_of_mut, write},
};

#[cfg(feature = "tui")]
use libafl::monitors::tui::TuiMonitor;
#[cfg(not(feature = "tui"))]
use libafl::monitors::SimpleMonitor;
use libafl::{
corpus::{InMemoryCorpus, OnDiskCorpus},
events::SimpleEventManager,
executors::{Executor, ExitKind, WithObservers},
feedback_and_fast,
feedbacks::{CrashFeedback, MaxMapFeedback},
fuzzer::{Fuzzer, StdFuzzer},
generators::RandPrintablesGenerator,
inputs::HasTargetBytes,
mutators::{havoc_mutations::havoc_mutations, scheduled::StdScheduledMutator},
observers::StdMapObserver,
schedulers::QueueScheduler,
stages::mutational::StdMutationalStage,
state::{HasExecutions, State, StdState, UsesState},
};
use libafl_bolts::{current_nanos, nonzero, rands::StdRand, tuples::tuple_list, AsSlice};

/// Coverage map with explicit assignments due to the lack of instrumentation
static mut SIGNALS: [u8; 16] = [0; 16];
static mut SIGNALS_PTR: *mut u8 = addr_of_mut!(SIGNALS) as _;
static SIGNALS_LEN: usize = unsafe { (*addr_of!(SIGNALS)).len() };

/// Assign a signal to the signals map
fn signals_set(idx: usize) {
unsafe { write(SIGNALS_PTR.add(idx), 1) };
}

struct CustomExecutor<S: State> {
phantom: PhantomData<S>,
}

impl<S: State> CustomExecutor<S> {
pub fn new(_state: &S) -> Self {
Self {
phantom: PhantomData,
}
}
}

impl<S: State> UsesState for CustomExecutor<S> {
type State = S;
}

impl<EM, S, Z> Executor<EM, Z> for CustomExecutor<S>
where
EM: UsesState<State = S>,
S: State + HasExecutions,
Z: UsesState<State = S>,
Self::Input: HasTargetBytes,
{
fn run_target(
&mut self,
_fuzzer: &mut Z,
state: &mut Self::State,
_mgr: &mut EM,
input: &Self::Input,
) -> Result<ExitKind, libafl::Error> {
// We need to keep track of the exec count.
*state.executions_mut() += 1;

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);
if buf.len() > 2 && buf[2] == b'c' {
return Ok(ExitKind::Crash);
}
}
}
Ok(ExitKind::Ok)
}
}

#[allow(clippy::similar_names, clippy::manual_assert)]
pub fn main() {
// Create an observation channel using the signals map
let observer = unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_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 = feedback_and_fast!(
// Look for crashes.
CrashFeedback::new(),
// We `and` the MaxMapFeedback to only end up with crashes that trigger new coverage.
// We use the _fast variant to make sure it's not evaluated every time, even if the crash didn't trigger..
// We have to give this one a name since it differs from the first map.
MaxMapFeedback::with_name("on_crash", &observer)
);

// 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(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(),
// 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 displayed to the user
#[cfg(not(feature = "tui"))]
let mon = SimpleMonitor::new(|s| println!("{s}"));
#[cfg(feature = "tui")]
let mon = TuiMonitor::builder()
.title("Baby Fuzzer")
.enhanced_graphics(false)
.build();

// 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(mon);

// 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 executor = CustomExecutor::new(&state);

let mut executor = WithObservers::new(executor, tuple_list!(observer));

// Generator of printable bytearrays of max size 32
let mut generator = RandPrintablesGenerator::new(nonzero!(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));

fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
.expect("Error in the fuzzing loop");
}
2 changes: 1 addition & 1 deletion libafl/src/feedbacks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,7 @@ impl ExitKindLogic for GenericDiffLogic {
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ExitKindFeedback<L> {
#[cfg(feature = "track_hit_feedbacks")]
// The previous run's result of `Self::is_interesting`
/// The previous run's result of [`Self::is_interesting`]
last_result: Option<bool>,
name: Cow<'static, str>,
phantom: PhantomData<fn() -> L>,
Expand Down

0 comments on commit 36a24ab

Please sign in to comment.