Custom Executor Example (#2570)

* [WIP] Custom Executor Example

* readme

* src/main.rs

* Finish

* fix warnings

* reame

* CI
This commit is contained in:
Dominik Maier 2024-11-05 16:49:07 +01:00 committed by GitHub
parent b5c9bffe50
commit 36a24ab418
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 256 additions and 1 deletions

View File

@ -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

View File

@ -0,0 +1,2 @@
libpng-*
corpus

View File

@ -0,0 +1,27 @@
[package]
name = "fuzzer_custom_executor"
version = "0.13.2"
authors = [
"Andrea Fioraldi <andreafioraldi@gmail.com>",
"Dominik Maier <domenukk@gmail.com>",
]
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/" }

View File

@ -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
'''

View File

@ -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`.

View File

@ -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");
}

View File

@ -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>,