use std::{env::temp_dir, ffi::c_int, fs::rename};
#[cfg(unix)]
use std::{
fs::File,
io::Write,
os::fd::{AsRawFd, FromRawFd},
};
use libafl::{
Error, HasScheduler, StdFuzzer,
corpus::Corpus,
events::{SendExiting, SimpleRestartingEventManager},
executors::{ExitKind, InProcessExecutor},
feedback_and_fast, feedback_or_fast,
feedbacks::{CrashFeedback, MinMapFeedback, TimeoutFeedback},
inputs::{BytesInput, HasTargetBytes},
monitors::MultiMonitor,
observers::{MultiMapObserver, TimeObserver},
schedulers::RemovableScheduler,
state::{HasCorpus, HasRand, StdState},
};
use libafl_bolts::{
rands::{Rand, StdRand},
shmem::{ShMemProvider, StdShMemProvider},
tuples::tuple_list,
};
use libafl_targets::{OomFeedback, OomObserver, counters_maps_ptr_mut};
use crate::{
corpus::{ArtifactCorpus, LibfuzzerCorpus},
feedbacks::{LibfuzzerCrashCauseFeedback, LibfuzzerKeepFeedback},
observers::{MappedEdgeMapObserver, SizeTimeValueObserver},
options::LibfuzzerOptions,
schedulers::MergeScheduler,
};
#[expect(clippy::too_many_lines)]
pub fn merge(
options: &LibfuzzerOptions,
harness: &extern "C" fn(*const u8, usize) -> c_int,
) -> Result<(), Error> {
if options.dirs().is_empty() {
return Err(Error::illegal_argument(
"Missing corpora to minimize; you should provide one directory to minimize into and one-to-many from which the inputs are loaded.",
));
}
let crash_corpus = ArtifactCorpus::new();
let keep_observer = LibfuzzerKeepFeedback::new();
let keep = keep_observer.keep();
let mut shmem_provider = StdShMemProvider::new().unwrap();
#[cfg(unix)]
let mut stderr = unsafe {
let new_fd = libc::dup(std::io::stderr().as_raw_fd());
File::from_raw_fd(new_fd)
};
let monitor = MultiMonitor::new(move |s| {
#[cfg(unix)]
writeln!(stderr, "{s}").expect("Could not write to stderr???");
#[cfg(not(unix))]
eprintln!("{s}");
});
let (state, mut mgr): (
Option<StdState<_, _, _, _>>,
SimpleRestartingEventManager<_, _, StdState<_, _, _, _>, _>,
) = match SimpleRestartingEventManager::launch(monitor, &mut shmem_provider, true) {
Ok(res) => res,
Err(err) => match err {
Error::ShuttingDown => {
return Ok(());
}
_ => {
panic!("Failed to setup the restarter: {err}");
}
},
};
#[cfg(unix)]
{
if options.close_fd_mask() != 0 {
let file_null = File::open("/dev/null")?;
unsafe {
if options.close_fd_mask() & 1 != 0 {
libc::dup2(file_null.as_raw_fd(), 1);
}
if options.close_fd_mask() & 2 != 0 {
libc::dup2(file_null.as_raw_fd(), 2);
}
}
}
}
let edges = unsafe { core::mem::take(&mut *(counters_maps_ptr_mut())) };
let edges_observer = MultiMapObserver::new("edges", edges);
let time = TimeObserver::new("time");
let edges_observer =
MappedEdgeMapObserver::new(edges_observer, SizeTimeValueObserver::new(time));
let map_feedback = MinMapFeedback::new(&edges_observer);
let oom_observer = OomObserver::new(options.rss_limit(), options.malloc_limit());
let mut feedback = feedback_and_fast!(keep_observer, map_feedback);
let mut objective = feedback_or_fast!(
LibfuzzerCrashCauseFeedback::new(options.artifact_prefix().clone()),
OomFeedback,
CrashFeedback::new(),
TimeoutFeedback::new()
);
let observers = tuple_list!(edges_observer, oom_observer);
let scheduler = MergeScheduler::new();
let mut state = state.map_or_else(|| {
let mut rand = StdRand::new();
let corpus_dir = if options.dirs().first().unwrap().exists()
&& options
.dirs()
.first()
.unwrap()
.read_dir()?
.any(|entry| entry.map_or(true, |e| !(e.file_name() == "." || e.file_name() == "..")))
{
let temp = temp_dir().join(format!("libafl-merge-{}{}", rand.next(), rand.next()));
eprintln!("Warning: creating an intermediary directory for minimisation at {}. We will move your existing corpus dir to.", temp.to_str().unwrap());
temp
} else {
options.dirs().first().cloned().unwrap()
};
StdState::new(
StdRand::new(),
LibfuzzerCorpus::new(corpus_dir, 4096),
crash_corpus,
&mut feedback,
&mut objective,
)
}, Ok)?;
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); let mut harness = |input: &BytesInput| {
let target = input.target_bytes();
let buf = ⌖
let result = unsafe {
crate::libafl_libfuzzer_test_one_input(Some(*harness), buf.as_ptr(), buf.len())
};
if result == -2 {
ExitKind::Crash
} else {
*keep.borrow_mut() = result == 0;
ExitKind::Ok
}
};
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() && !options.dirs().is_empty() {
let loaded_dirs = options
.dirs()
.iter()
.filter(|&dir| state.corpus().dir_path() != dir)
.cloned()
.collect::<Vec<_>>();
state
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &loaded_dirs)
.unwrap_or_else(|e| {
panic!(
"Failed to load initial corpus at {:?}: {}",
options.dirs(),
e
)
});
}
for id in fuzzer.scheduler().removable() {
let testcase = state.corpus_mut().remove(id)?;
fuzzer
.scheduler_mut()
.on_remove(&mut state, id, &Some(testcase))?;
}
for id in fuzzer.scheduler().current().clone() {
let mut testcase = state.corpus_mut().get(id)?.borrow_mut();
let file_path = testcase
.file_path_mut()
.as_mut()
.expect("No file backing for corpus entry");
if let Some((base, _)) = file_path
.file_name()
.unwrap()
.to_str()
.unwrap()
.rsplit_once('-')
{
let mut new_file_path = file_path.clone();
new_file_path.pop();
new_file_path.push(base);
if new_file_path.exists() {
drop(testcase);
let testcase = state.corpus_mut().remove(id)?;
fuzzer
.scheduler_mut()
.on_remove(&mut state, id, &Some(testcase))?;
} else {
rename(&file_path, &new_file_path)?;
*file_path = new_file_path;
}
}
}
println!(
"Minimization complete; reduced to {} inputs!",
state.corpus().count()
);
let corpus_dir = state.corpus().dir_path().clone();
if corpus_dir != options.dirs()[0] {
let temp = temp_dir().join(format!(
"libafl-merge-orig-{}{}",
state.rand_mut().next(),
state.rand_mut().next()
));
eprintln!(
"Moving original corpus directory {} to {} and replacing it with minimisation result ({}).",
options.dirs()[0].to_str().unwrap(),
temp.to_str().unwrap(),
corpus_dir.to_str().unwrap()
);
rename(&options.dirs()[0], temp)?;
rename(corpus_dir, &options.dirs()[0])?;
}
mgr.send_exiting()
}