#![forbid(unexpected_cfgs)]
#![warn(clippy::cargo)]
#![allow(ambiguous_glob_reexports)]
#![deny(clippy::cargo_common_metadata)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![allow(
clippy::unreadable_literal,
clippy::type_repetition_in_bounds,
clippy::missing_errors_doc,
clippy::cast_possible_truncation,
clippy::used_underscore_binding,
clippy::ptr_as_ptr,
clippy::missing_panics_doc,
clippy::missing_docs_in_private_items,
clippy::module_name_repetitions,
clippy::ptr_cast_constness,
clippy::unsafe_derive_deserialize,
clippy::similar_names,
clippy::too_many_lines
)]
#![cfg_attr(not(test), warn(
missing_debug_implementations,
missing_docs,
//trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
//unused_results
))]
#![cfg_attr(test, deny(
missing_debug_implementations,
missing_docs,
//trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
unused_must_use,
//unused_results
))]
#![cfg_attr(
test,
deny(
bad_style,
dead_code,
improper_ctypes,
non_shorthand_field_patterns,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
unconditional_recursion,
unused,
unused_allocation,
unused_comparisons,
unused_parens,
while_true
)
)]
#![allow(clippy::borrow_as_ptr)]
#![allow(clippy::borrow_deref_ref)]
use core::ffi::{CStr, c_char, c_int};
#[cfg(unix)]
use std::{
os::fd::RawFd,
{fs::File, io::stderr},
};
#[cfg(unix)]
use env_logger::Target;
use libafl::{
Error,
inputs::{BytesInput, HasTargetBytes, Input},
};
use libafl_bolts::AsSlice;
use libc::_exit;
use mimalloc::MiMalloc;
use crate::options::{LibfuzzerMode, LibfuzzerOptions};
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
mod corpus;
mod feedbacks;
mod fuzz;
mod merge;
mod misc;
mod observers;
mod options;
mod report;
mod schedulers;
mod tmin;
mod harness_wrap {
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(unused)]
#![allow(improper_ctypes)]
#![allow(clippy::unreadable_literal)]
#![allow(missing_docs)]
#![allow(unused_qualifications)]
include!(concat!(env!("OUT_DIR"), "/harness_wrap.rs"));
}
pub(crate) use harness_wrap::libafl_libfuzzer_test_one_input;
#[expect(clippy::struct_excessive_bools)]
struct CustomMutationStatus {
std_mutational: bool,
std_no_mutate: bool,
std_no_crossover: bool,
custom_mutation: bool,
custom_crossover: bool,
}
impl CustomMutationStatus {
fn new() -> Self {
let custom_mutation = libafl_targets::libfuzzer::has_custom_mutator();
let custom_crossover = libafl_targets::libfuzzer::has_custom_crossover();
let std_mutational = !(custom_mutation || custom_crossover);
let std_no_mutate = !std_mutational && custom_mutation && !custom_crossover;
let std_no_crossover = !std_mutational && !custom_mutation && custom_crossover;
Self {
std_mutational,
std_no_mutate,
std_no_crossover,
custom_mutation,
custom_crossover,
}
}
}
macro_rules! fuzz_with {
($options:ident, $harness:ident, $operation:expr, $and_then:expr, $edge_maker:expr, $extra_feedback:expr, $extra_obsv:expr) => {{
use libafl_bolts::{
rands::StdRand,
tuples::{Merge, tuple_list},
AsSlice,
nonnull_raw_mut,
};
use libafl::{
corpus::Corpus,
executors::{ExitKind, InProcessExecutor, ShadowExecutor},
feedback_and_fast, feedback_not, feedback_or, feedback_or_fast,
feedbacks::{ConstFeedback, CrashFeedback, MaxMapFeedback, NewHashFeedback, TimeFeedback, TimeoutFeedback},
generators::RandBytesGenerator,
inputs::{BytesInput, HasTargetBytes, GeneralizedInputMetadata},
mutators::{
GrimoireExtensionMutator, GrimoireRecursiveReplacementMutator, GrimoireRandomDeleteMutator,
GrimoireStringReplacementMutator, havoc_crossover, havoc_mutations, havoc_mutations_no_crossover,
I2SRandReplace, HavocScheduledMutator, UnicodeCategoryRandMutator, UnicodeSubcategoryRandMutator,
UnicodeCategoryTokenReplaceMutator, UnicodeSubcategoryTokenReplaceMutator, Tokens, tokens_mutations,
UnicodeInput,
},
observers::{stacktrace::BacktraceObserver, TimeObserver, CanTrack, ConstMapObserver},
schedulers::{
IndexesLenTimeMinimizerScheduler, powersched::PowerSchedule, PowerQueueScheduler,
},
stages::{
CalibrationStage, GeneralizationStage, IfStage, StdMutationalStage,
StdPowerMutationalStage, UnicodeIdentificationStage, ShadowTracingStage,
},
state::{HasCorpus, StdState},
StdFuzzer,
};
use libafl_targets::{CmpLogObserver, LLVMCustomMutator, OomFeedback, OomObserver, CMP_MAP};
use libafl_bolts::nonzero;
use rand::{rng, RngExt};
use std::{env::temp_dir, fs::create_dir, path::PathBuf};
use crate::{
CustomMutationStatus,
corpus::{ArtifactCorpus, LibfuzzerCorpus},
feedbacks::{LibfuzzerCrashCauseFeedback, LibfuzzerKeepFeedback, ShrinkMapFeedback},
misc::should_use_grimoire,
observers::{MappedEdgeMapObserver, SizeValueObserver},
};
let edge_maker = &$edge_maker;
let closure = |mut state: Option<_>, mut mgr, _cpu_id| {
let mutator_status = CustomMutationStatus::new();
let grimoire_metadata = should_use_grimoire(&mut state, &$options, &mutator_status)?;
let grimoire = grimoire_metadata.should();
let edges_observer = edge_maker().track_indices().track_novelties();
let size_edges_observer = MappedEdgeMapObserver::new(edge_maker(), SizeValueObserver::default());
let keep_observer = LibfuzzerKeepFeedback::new();
let keep = keep_observer.keep();
let time_observer = TimeObserver::new("time");
let oom_observer = OomObserver::new($options.rss_limit(), $options.malloc_limit());
let cmplog_observer = CmpLogObserver::new("cmplog", true);
let value_profile_observer = unsafe { ConstMapObserver::from_mut_ptr("cmps", nonnull_raw_mut!(CMP_MAP)) };
let backtrace_observer = BacktraceObserver::owned(
"BacktraceObserver",
if $options.forks().is_some() || $options.tui() { libafl::observers::HarnessType::Child } else { libafl::observers::HarnessType::InProcess }
);
let map_feedback = MaxMapFeedback::new(&edges_observer);
let shrinking_map_feedback = ShrinkMapFeedback::new(&size_edges_observer);
let value_profile_feedback = MaxMapFeedback::new(&value_profile_observer);
let generalization = GeneralizationStage::new(&edges_observer);
let generalization = IfStage::new(|_, _, _, _| Ok(grimoire.into()), tuple_list!(generalization));
let calibration = CalibrationStage::new(&map_feedback);
let add_extra_feedback = $extra_feedback;
let coverage_feedback = add_extra_feedback(
feedback_or!(
map_feedback,
feedback_and_fast!(ConstFeedback::new($options.shrink()), shrinking_map_feedback),
TimeFeedback::new(&time_observer)
),
value_profile_feedback
);
let mut feedback = feedback_and_fast!(
feedback_not!(
feedback_or_fast!(
OomFeedback,
CrashFeedback::new(),
TimeoutFeedback::new()
)
),
keep_observer,
coverage_feedback
);
let mut objective = feedback_or_fast!(
LibfuzzerCrashCauseFeedback::new($options.artifact_prefix().clone()),
OomFeedback,
feedback_and_fast!(
CrashFeedback::new(),
feedback_or_fast!(ConstFeedback::new(!$options.dedup()), NewHashFeedback::new(&backtrace_observer))
),
TimeoutFeedback::new()
);
let corpus_dir = if let Some(main) = $options.dirs().first() {
main.clone()
} else {
let mut rng = rng();
let mut dir = PathBuf::new();
let mut last = Ok(());
for _ in 0..8 {
dir = temp_dir().join(format!("libafl-corpus-{}", rng.random::<u64>()));
last = create_dir(&dir);
if last.is_ok() {
break;
}
}
last?;
dir
};
let crash_corpus = ArtifactCorpus::new();
let mut state = state.unwrap_or_else(|| {
StdState::new(
StdRand::new(),
LibfuzzerCorpus::new(corpus_dir.clone(), 4096),
crash_corpus,
&mut feedback,
&mut objective,
)
.expect("Failed to create state")
});
state.metadata_map_mut().insert_boxed(grimoire_metadata);
let unicode_used = $options.unicode();
let unicode_mutator = HavocScheduledMutator::new(
tuple_list!(
UnicodeCategoryRandMutator,
UnicodeSubcategoryRandMutator,
UnicodeSubcategoryRandMutator,
UnicodeSubcategoryRandMutator,
UnicodeSubcategoryRandMutator,
)
);
let unicode_replace_mutator = HavocScheduledMutator::new(
tuple_list!(
UnicodeCategoryTokenReplaceMutator,
UnicodeSubcategoryTokenReplaceMutator,
UnicodeSubcategoryTokenReplaceMutator,
UnicodeSubcategoryTokenReplaceMutator,
UnicodeSubcategoryTokenReplaceMutator,
)
);
let unicode_power = StdMutationalStage::<_, _, UnicodeInput, BytesInput, _, _, _>::transforming(unicode_mutator);
let unicode_replace_power = StdMutationalStage::<_, _, UnicodeInput, BytesInput, _, _, _>::transforming(unicode_replace_mutator);
let unicode_analysis = UnicodeIdentificationStage::new();
let unicode_analysis = IfStage::new(|_, _, _, _| Ok((unicode_used && mutator_status.std_mutational).into()), tuple_list!(unicode_analysis, unicode_power, unicode_replace_power));
if !state.has_metadata::<Tokens>() {
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
let mut toks = if let Some(tokens) = $options.dict() {
tokens.clone()
} else {
Tokens::default()
};
#[cfg(not(any(target_os = "linux", target_vendor = "apple")))]
let toks = if let Some(tokens) = $options.dict() {
tokens.clone()
} else {
Tokens::default()
};
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
{
toks += libafl_targets::autotokens()?;
}
if !toks.is_empty() {
state.add_metadata(toks);
}
}
let i2s =
StdMutationalStage::new(HavocScheduledMutator::new(tuple_list!(I2SRandReplace::new())));
let i2s = IfStage::new(|_, _, _, _| Ok((!mutator_status.custom_mutation).into()), (i2s, ()));
let cm_i2s = StdMutationalStage::new(unsafe {
LLVMCustomMutator::mutate_unchecked(HavocScheduledMutator::new(tuple_list!(
I2SRandReplace::new()
)))
});
let cm_i2s = IfStage::new(|_, _, _, _| Ok(mutator_status.custom_mutation.into()), (cm_i2s, ()));
let std_mutator = HavocScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
let std_power: StdPowerMutationalStage<_, _, BytesInput, _, _, _> = StdPowerMutationalStage::new(std_mutator);
let std_power = IfStage::new(|_, _, _, _| Ok(mutator_status.std_mutational.into()), (std_power, ()));
let custom_mutator = unsafe {
LLVMCustomMutator::mutate_unchecked(HavocScheduledMutator::new(havoc_mutations_no_crossover().merge(tokens_mutations())))
};
let std_mutator_no_mutate = HavocScheduledMutator::with_max_stack_pow(havoc_crossover(),3);
let cm_power: StdPowerMutationalStage<_, _, BytesInput, _, _, _> = StdPowerMutationalStage::new(custom_mutator);
let cm_power = IfStage::new(|_, _, _, _| Ok(mutator_status.custom_mutation.into()), (cm_power, ()));
let cm_std_power = StdMutationalStage::new(std_mutator_no_mutate);
let cm_std_power =
IfStage::new(|_, _, _, _| Ok(mutator_status.std_no_mutate.into()), (cm_std_power, ()));
let custom_crossover = unsafe {
LLVMCustomMutator::crossover_unchecked(HavocScheduledMutator::with_max_stack_pow(
havoc_mutations_no_crossover().merge(tokens_mutations()),
3,
))
};
let std_mutator_no_crossover = HavocScheduledMutator::new(havoc_mutations_no_crossover().merge(tokens_mutations()));
let cc_power = StdMutationalStage::new(custom_crossover);
let cc_power = IfStage::new(|_, _, _, _| Ok(mutator_status.custom_crossover.into()), (cc_power, ()));
let cc_std_power: StdPowerMutationalStage<_, _, BytesInput, _, _, _> = StdPowerMutationalStage::new(std_mutator_no_crossover);
let cc_std_power =
IfStage::new(|_, _, _, _| Ok(mutator_status.std_no_crossover.into()), (cc_std_power, ()));
let grimoire_mutator = HavocScheduledMutator::with_max_stack_pow(
tuple_list!(
GrimoireExtensionMutator::new(),
GrimoireRecursiveReplacementMutator::new(),
GrimoireStringReplacementMutator::new(),
GrimoireRandomDeleteMutator::new(),
GrimoireRandomDeleteMutator::new(),
),
3,
);
let grimoire = IfStage::new(|_, _, _, _| Ok(grimoire.into()), (StdMutationalStage::<_, _, GeneralizedInputMetadata, BytesInput, _, _, _>::transforming(grimoire_mutator), ()));
let scheduler = IndexesLenTimeMinimizerScheduler::new(&edges_observer, PowerQueueScheduler::new(&mut state, &edges_observer, PowerSchedule::fast()));
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
let mut harness = |input: &BytesInput| {
let target = input.target_bytes();
let buf = target.as_slice();
let result = unsafe { crate::libafl_libfuzzer_test_one_input(Some(*$harness), buf.as_ptr(), buf.len()) };
match result {
-2 => ExitKind::Crash,
_ => {
*keep.borrow_mut() = result == 0;
ExitKind::Ok
}
}
};
let add_extra_observer = $extra_obsv;
let observers = add_extra_observer(
tuple_list!(edges_observer, size_edges_observer, time_observer, backtrace_observer, oom_observer),
value_profile_observer
);
let mut executor = InProcessExecutor::builder()
.timeout($options.timeout())
.crashdump(false)
.harness(&mut harness)
.observers(observers)
.fuzzer(&mut fuzzer)
.state(&mut state)
.event_mgr(&mut mgr)
.build()?;
if state.must_load_initial_inputs() {
if !$options.dirs().is_empty() {
state
.load_initial_inputs_forced(&mut fuzzer, &mut executor, &mut mgr, $options.dirs())
.unwrap_or_else(|e| {
panic!("Failed to load initial corpus at {:?}: {}", $options.dirs(), e)
});
println!("We imported {} inputs from disk.", state.corpus().count());
}
if state.corpus().count() < 1 {
let mut generator = RandBytesGenerator::from(RandBytesGenerator::new(nonzero!(64)));
state
.generate_initial_inputs(
&mut fuzzer,
&mut executor,
&mut generator,
&mut mgr,
1 << 10,
)
.expect("Failed to generate the initial corpus");
println!(
"We imported {} inputs from the generator.",
state.corpus().count()
);
}
}
let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer));
let tracing = IfStage::new(|_, _, _, _| Ok(!$options.skip_tracing()), (ShadowTracingStage::new(), ()));
let mut stages = tuple_list!(
calibration,
generalization,
tracing,
unicode_analysis,
i2s,
cm_i2s,
std_power,
cm_power,
cm_std_power,
cc_std_power,
cc_power,
grimoire,
);
$operation(&$options, &mut fuzzer, &mut stages, &mut executor, &mut state, &mut mgr)
};
$and_then(closure)
}};
($options:ident, $harness:ident, $operation:expr, $and_then:expr, $edge_maker:expr) => {{
if $options.use_value_profile() {
fuzz_with!($options, $harness, $operation, $and_then, $edge_maker,
|feedback, value_profile_feedback| {
feedback_or!(feedback, value_profile_feedback)
},
|observers, value_profile_observer| {
(value_profile_observer, observers) }
)
} else {
fuzz_with!($options, $harness, $operation, $and_then, $edge_maker, |feedback, _| feedback, |observers, _| observers)
}
}};
($options:ident, $harness:ident, $operation:expr, $and_then:expr) => {{
use libafl::observers::{
HitcountsIterableMapObserver, HitcountsMapObserver, MultiMapObserver, StdMapObserver,
};
use libafl_targets::{counters_maps_ptr, extra_counters};
let counters_len = unsafe { &*counters_maps_ptr() }.len();
if counters_len == 1 {
fuzz_with!($options, $harness, $operation, $and_then, || {
let edges = unsafe { extra_counters() };
let edges_observer =
HitcountsMapObserver::new(StdMapObserver::from_mut_slice("edges", edges.into_iter().next().unwrap()));
edges_observer
})
} else if counters_len > 1 {
fuzz_with!($options, $harness, $operation, $and_then, || {
let edges = unsafe { extra_counters() };
let edges_observer =
HitcountsIterableMapObserver::new(MultiMapObserver::new("edges", edges));
edges_observer
})
} else {
panic!("No maps available; cannot fuzz!")
}
}};
}
pub(crate) use fuzz_with;
pub fn start_fuzzing_single<F, S, EM>(
mut fuzz_single: F,
initial_state: Option<S>,
mgr: EM,
) -> Result<(), Error>
where
F: FnMut(Option<S>, EM, usize) -> Result<(), Error>,
{
fuzz_single(initial_state, mgr, 0)
}
unsafe extern "C" {
fn libafl_targets_libfuzzer_init(argc: *mut c_int, argv: *mut *mut *const c_char) -> i32;
}
pub const STDERR_FD_VAR: &str = "_LIBAFL_LIBFUZZER_STDERR_FD";
#[expect(clippy::similar_names)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn LLVMFuzzerRunDriver(
argc: *mut c_int,
argv: *mut *mut *const c_char,
harness_fn: Option<extern "C" fn(*const u8, usize) -> c_int>,
) -> c_int {
let harness = harness_fn
.as_ref()
.expect("Illegal harness provided to libafl.");
#[cfg(unix)]
{
use std::{
os::fd::{AsRawFd, FromRawFd},
str::FromStr,
};
let stderr_fd = std::env::var(STDERR_FD_VAR)
.map_err(Error::from)
.and_then(|s| RawFd::from_str(&s).map_err(Error::from))
.unwrap_or_else(|_| {
let stderr = unsafe { libc::dup(stderr().as_raw_fd()) };
unsafe {
std::env::set_var(STDERR_FD_VAR, stderr.to_string());
}
stderr
});
let stderr = unsafe { File::from_raw_fd(stderr_fd) };
env_logger::builder()
.parse_default_env()
.target(Target::Pipe(Box::new(stderr)))
.init();
}
unsafe {
libafl_targets_libfuzzer_init(argc, argv);
}
let argc = unsafe { *argc } as isize;
let argv = unsafe { *argv };
let options = LibfuzzerOptions::new(
(0..argc)
.map(|i| unsafe { *argv.offset(i) })
.map(|cstr| unsafe { CStr::from_ptr(cstr) })
.map(|cstr| cstr.to_str().unwrap()),
)
.unwrap();
if !options.unknown().is_empty() {
eprintln!("Unrecognised options: {:?}", options.unknown());
}
for folder in options
.dirs()
.iter()
.chain(std::iter::once(options.artifact_prefix().dir()))
{
if !folder.try_exists().unwrap_or(false) {
eprintln!(
"Required folder {} did not exist; failing fast.",
folder.to_string_lossy()
);
unsafe {
_exit(1);
}
}
}
if *options.mode() != LibfuzzerMode::Tmin
&& !options.dirs().is_empty()
&& options.dirs().iter().all(|maybe_dir| maybe_dir.is_file())
{
for input in options.dirs() {
let input = BytesInput::from_file(input).unwrap_or_else(|err| {
panic!(
"Couldn't load input {}: {err:?}",
input.to_string_lossy().as_ref()
)
});
unsafe {
libafl_targets::libfuzzer::libfuzzer_test_one_input(
input.target_bytes().as_slice(),
);
}
}
return 0;
}
let res = match options.mode() {
LibfuzzerMode::Fuzz => fuzz::fuzz(&options, harness),
LibfuzzerMode::Merge => merge::merge(&options, harness),
LibfuzzerMode::Tmin => tmin::minimize_crash(&options, *harness),
LibfuzzerMode::Report => report::report(&options, harness),
};
match res {
Ok(()) | Err(Error::ShuttingDown) => 0,
Err(err) => {
eprintln!("Encountered error while performing libfuzzer shimming: {err}");
1
}
}
}