Rowan Hart 0b25d723c0
Windows Support for LibAFL-LibFuzzer (#3130)
* Add windows build script and additional changes to support windows for libafl-libfuzzer

* Update build scripts and harness wrapping directives

* Resolve issue with corpus edge count calculation

* Add help message and make fork do nothing on Windows

* Format harness_wrap.cpp

* Clippy happiness pass

* Clippy happiness pass

* Clippy happiness pass

* Correct logic

* Correct logic

* Update help output and make runs argument work

* Add test for libafl_libfuzzer on windows

* Add workflow for libafl_libfuzzer test

* Fix copy without dependent task

* Add libafl_libfuzzer_windows to preflight list

* Format harness

* Explicitly ignore windows fuzzer

* Remove windows-specific copy from unix instructions

* Ensure using nightly

* Fix job name

* Update build to use libFuzzer.lib on Windows to keep consistent with Linux

* Remove nightly requirement

---------

Co-authored-by: Rowan Hart <rowanhart@microsoft.com>
2025-05-21 01:35:48 +02:00

195 lines
5.8 KiB
Rust

use std::{
ffi::c_int,
fs::{read, write},
};
#[cfg(windows)]
use libafl::executors::inprocess::InProcessExecutor;
#[cfg(unix)]
use libafl::executors::inprocess_fork::InProcessForkExecutor;
use libafl::{
Error, ExecutesInput, Fuzzer, StdFuzzer,
corpus::{Corpus, HasTestcase, InMemoryCorpus, Testcase},
events::SimpleEventManager,
executors::ExitKind,
feedbacks::{CrashFeedback, TimeoutFeedback},
inputs::{BytesInput, HasMutatorBytes, HasTargetBytes},
mutators::{HavocScheduledMutator, Mutator, havoc_mutations_no_crossover},
schedulers::QueueScheduler,
stages::StdTMinMutationalStage,
state::{HasCorpus, StdState},
};
#[cfg(unix)]
use libafl_bolts::shmem::{ShMemProvider, StdShMemProvider};
use libafl_bolts::{
AsSlice, HasLen,
rands::{RomuDuoJrRand, StdRand},
tuples::tuple_list,
};
use libafl_targets::LLVMCustomMutator;
use crate::{CustomMutationStatus, options::LibfuzzerOptions};
type TMinState =
StdState<InMemoryCorpus<BytesInput>, BytesInput, RomuDuoJrRand, InMemoryCorpus<BytesInput>>;
fn minimize_crash_with_mutator<M: Mutator<BytesInput, TMinState>>(
options: &LibfuzzerOptions,
harness: extern "C" fn(*const u8, usize) -> c_int,
mutator: M,
mut state: TMinState,
) -> Result<(), Error> {
let mut mgr = SimpleEventManager::printing();
assert_eq!(
options.dirs().len(),
1,
"Must provide exactly one input to minimise"
);
assert!(options.dirs()[0].exists(), "Input specified does not exist");
assert!(options.dirs()[0].is_file(), "Input specified is not a file");
let input = BytesInput::new(read(&options.dirs()[0])?);
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,
_ => ExitKind::Ok,
}
};
let mut fuzzer = StdFuzzer::new(QueueScheduler::new(), (), ());
#[cfg(unix)]
let mut executor = {
let shmem_provider = StdShMemProvider::new()?;
InProcessForkExecutor::new(
&mut harness,
(),
&mut fuzzer,
&mut state,
&mut mgr,
options.timeout(),
shmem_provider,
)?
};
#[cfg(windows)]
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
(),
&mut fuzzer,
&mut state,
&mut mgr,
options.timeout(),
)?;
let exit_kind = fuzzer.execute_input(&mut state, &mut executor, &mut mgr, &input)?;
let size = input.len();
let id = state.corpus_mut().add(Testcase::new(input))?;
match exit_kind {
ExitKind::Crash => {
let factory = CrashFeedback::new();
let tmin = StdTMinMutationalStage::new(
mutator,
factory,
if options.runs() == 0 {
128
} else {
options.runs()
},
);
let mut stages = tuple_list!(tmin);
fuzzer.fuzz_one(&mut stages, &mut executor, &mut state, &mut mgr)?;
}
ExitKind::Timeout => {
let factory = TimeoutFeedback::new();
let tmin = StdTMinMutationalStage::new(
mutator,
factory,
if options.runs() == 0 {
128
} else {
options.runs()
},
);
let mut stages = tuple_list!(tmin);
fuzzer.fuzz_one(&mut stages, &mut executor, &mut state, &mut mgr)?;
}
kind => unimplemented!("Unsupported exit kind for test minification: {:?}", kind),
}
let mut testcase = state.testcase_mut(id)?;
let input = testcase
.load_input(state.corpus())?
.mutator_bytes()
.to_vec();
drop(testcase);
if input.len() >= size {
eprintln!(
"Unable to reduce {}",
options.dirs()[0].as_path().as_os_str().to_str().unwrap()
);
} else {
let mut dest = options.artifact_prefix().dir().clone();
dest.push(format!(
"{}minimized-from-{}",
options.artifact_prefix().filename_prefix(),
options.dirs()[0].file_name().unwrap().to_str().unwrap()
));
write(&dest, input)?;
println!(
"Wrote minimised input to {}",
dest.file_name().unwrap().to_str().unwrap()
);
}
Ok(())
}
pub fn minimize_crash(
options: &LibfuzzerOptions,
harness: extern "C" fn(*const u8, usize) -> c_int,
) -> Result<(), Error> {
println!(
"Attempting to minimise a crash: {}",
options
.dirs()
.iter()
.map(|p| p
.to_str()
.expect("Couldn't render the filename as a string!"))
.collect::<Vec<_>>()
.join(", ")
);
let mutator_status = CustomMutationStatus::new();
let state = StdState::new(
StdRand::new(),
InMemoryCorpus::<BytesInput>::new(),
InMemoryCorpus::new(),
&mut (),
&mut (),
)?;
// TODO configure with mutation stacking options from libfuzzer
if mutator_status.custom_mutation {
let custom_mutator = unsafe {
LLVMCustomMutator::mutate_unchecked(HavocScheduledMutator::new(
havoc_mutations_no_crossover(),
))
};
minimize_crash_with_mutator(options, harness, custom_mutator, state)
} else {
let std_mutator = HavocScheduledMutator::new(havoc_mutations_no_crossover());
minimize_crash_with_mutator(options, harness, std_mutator, state)
}
}