diff --git a/fuzzers/binary_only/qemu_launcher/src/client.rs b/fuzzers/binary_only/qemu_launcher/src/client.rs index 56c71bbc0d..b702ab2ddd 100644 --- a/fuzzers/binary_only/qemu_launcher/src/client.rs +++ b/fuzzers/binary_only/qemu_launcher/src/client.rs @@ -15,6 +15,7 @@ use libafl_qemu::{ asan::{init_qemu_with_asan, AsanModule}, asan_guest::{init_qemu_with_asan_guest, AsanGuestModule}, cmplog::CmpLogModule, + DrCovModule, }, Qemu, }; @@ -38,7 +39,7 @@ impl<'a> Client<'a> { Client { options } } - fn args(&self) -> Result, Error> { + pub fn args(&self) -> Result, Error> { let program = env::args() .next() .ok_or_else(|| Error::empty_optional("Failed to read program name"))?; @@ -49,7 +50,7 @@ impl<'a> Client<'a> { } #[allow(clippy::unused_self)] // Api should look the same as args above - fn env(&self) -> Vec<(String, String)> { + pub fn env(&self) -> Vec<(String, String)> { env::vars() .filter(|(k, _v)| k != "LD_LIBRARY_PATH") .collect::>() @@ -125,7 +126,16 @@ impl<'a> Client<'a> { .core_id(core_id) .extra_tokens(extra_tokens); - if is_asan && is_cmplog { + if self.options.rerun_input.is_some() && self.options.drcov.is_some() { + // Special code path for re-running inputs with DrCov. + // TODO: Add ASan support, injection support + let drcov = self.options.drcov.as_ref().unwrap(); + let drcov = DrCovModule::builder() + .filename(drcov.clone()) + .full_trace(true) + .build(); + instance_builder.build().run(tuple_list!(drcov), state) + } else if is_asan && is_cmplog { if let Some(injection_module) = injection_module { instance_builder.build().run( tuple_list!( diff --git a/fuzzers/binary_only/qemu_launcher/src/fuzzer.rs b/fuzzers/binary_only/qemu_launcher/src/fuzzer.rs index c414400a1d..61f622c78f 100644 --- a/fuzzers/binary_only/qemu_launcher/src/fuzzer.rs +++ b/fuzzers/binary_only/qemu_launcher/src/fuzzer.rs @@ -10,14 +10,20 @@ use libafl::events::SimpleEventManager; #[cfg(not(feature = "simplemgr"))] use libafl::events::{EventConfig, Launcher, MonitorTypedEventManager}; use libafl::{ + events::{LlmpEventManager, LlmpRestartingEventManager}, monitors::{tui::TuiMonitor, Monitor, MultiMonitor}, Error, }; #[cfg(feature = "simplemgr")] use libafl_bolts::core_affinity::CoreId; -use libafl_bolts::current_time; #[cfg(not(feature = "simplemgr"))] use libafl_bolts::shmem::{ShMemProvider, StdShMemProvider}; +use libafl_bolts::{ + current_time, + llmp::LlmpBroker, + prelude::{CoreId, StateRestorer}, + tuples::tuple_list, +}; #[cfg(unix)] use { nix::unistd::dup, @@ -82,7 +88,7 @@ impl Fuzzer { { // The shared memory allocator #[cfg(not(feature = "simplemgr"))] - let shmem_provider = StdShMemProvider::new()?; + let mut shmem_provider = StdShMemProvider::new()?; /* If we are running in verbose, don't provide a replacement stdout, otherwise, use /dev/null */ #[cfg(not(feature = "simplemgr"))] @@ -94,10 +100,42 @@ impl Fuzzer { let client = Client::new(&self.options); + #[cfg(not(feature = "simplemgr"))] + if self.options.rerun_input.is_some() { + // If we want to rerun a single input but we use a restarting mgr, we'll have to create a fake restarting mgr that doesn't actually restart. + // It's not pretty but better than recompiling with simplemgr. + + // Just a random number, let's hope it's free :) + let broker_port = 13120; + let _fake_broker = LlmpBroker::create_attach_to_tcp( + shmem_provider.clone(), + tuple_list!(), + broker_port, + ) + .unwrap(); + + // To rerun an input, instead of using a launcher, we create dummy parameters and run the client directly. + return client.run( + None, + MonitorTypedEventManager::<_, M>::new(LlmpRestartingEventManager::new( + LlmpEventManager::builder() + .build_on_port( + shmem_provider.clone(), + broker_port, + EventConfig::AlwaysUnique, + None, + ) + .unwrap(), + StateRestorer::new(shmem_provider.new_shmem(0x1000).unwrap()), + )), + CoreId(0), + ); + } + #[cfg(feature = "simplemgr")] return client.run(None, SimpleEventManager::new(monitor), CoreId(0)); - // Build and run a Launcher + // Build and run the Launcher / fuzzer. #[cfg(not(feature = "simplemgr"))] match Launcher::builder() .shmem_provider(shmem_provider) diff --git a/fuzzers/binary_only/qemu_launcher/src/instance.rs b/fuzzers/binary_only/qemu_launcher/src/instance.rs index f6b7734272..438dadcb1a 100644 --- a/fuzzers/binary_only/qemu_launcher/src/instance.rs +++ b/fuzzers/binary_only/qemu_launcher/src/instance.rs @@ -1,5 +1,5 @@ use core::{fmt::Debug, ptr::addr_of_mut}; -use std::{marker::PhantomData, ops::Range, process}; +use std::{fs, marker::PhantomData, ops::Range, process}; #[cfg(feature = "simplemgr")] use libafl::events::SimpleEventManager; @@ -7,8 +7,8 @@ use libafl::events::SimpleEventManager; use libafl::events::{LlmpRestartingEventManager, MonitorTypedEventManager}; use libafl::{ corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus}, - events::EventRestarter, - executors::ShadowExecutor, + events::{EventRestarter, NopEventManager}, + executors::{Executor, ShadowExecutor}, feedback_or, feedback_or_fast, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, fuzzer::{Evaluator, Fuzzer, StdFuzzer}, @@ -27,7 +27,7 @@ use libafl::{ StagesTuple, StdMutationalStage, }, state::{HasCorpus, StdState, UsesState}, - Error, HasMetadata, + Error, HasMetadata, NopFuzzer, }; #[cfg(not(feature = "simplemgr"))] use libafl_bolts::shmem::StdShMemProvider; @@ -204,6 +204,34 @@ impl<'a, M: Monitor> Instance<'a, M> { let emulator = Emulator::empty().qemu(self.qemu).modules(modules).build()?; + if let Some(rerun_input) = &self.options.rerun_input { + // TODO: We might want to support non-bytes inputs at some point? + let bytes = fs::read(rerun_input) + .unwrap_or_else(|_| panic!("Could not load file {rerun_input:?}")); + let input = BytesInput::new(bytes); + + let mut executor = QemuExecutor::new( + emulator, + &mut harness, + observers, + &mut fuzzer, + &mut state, + &mut self.mgr, + self.options.timeout, + )?; + + executor + .run_target( + &mut NopFuzzer::new(), + &mut state, + &mut NopEventManager::new(), + &input, + ) + .expect("Error running target"); + // We're done :) + process::exit(0); + } + if self.options.is_cmplog_core(self.core_id) { // Create a QEMU in-process executor let executor = QemuExecutor::new( diff --git a/fuzzers/binary_only/qemu_launcher/src/options.rs b/fuzzers/binary_only/qemu_launcher/src/options.rs index 20e9f598de..32bc8a6294 100644 --- a/fuzzers/binary_only/qemu_launcher/src/options.rs +++ b/fuzzers/binary_only/qemu_launcher/src/options.rs @@ -39,7 +39,7 @@ pub struct FuzzerOptions { #[arg(long, help = "Log file")] pub log: Option, - #[arg(long, help = "Timeout in milli-seconds", default_value = "1000", value_parser = FuzzerOptions::parse_timeout)] + #[arg(long, help = "Timeout in milliseconds", default_value = "1000", value_parser = FuzzerOptions::parse_timeout)] pub timeout: Duration, #[arg(long = "port", help = "Broker port", default_value_t = 1337_u16)] @@ -48,10 +48,10 @@ pub struct FuzzerOptions { #[arg(long, help = "Cpu cores to use", default_value = "all", value_parser = Cores::from_cmdline)] pub cores: Cores, - #[arg(long, help = "Cpu cores to use for ASAN", value_parser = Cores::from_cmdline)] + #[arg(long, help = "Cpu cores to use for ASan", value_parser = Cores::from_cmdline)] pub asan_cores: Option, - #[arg(long, help = "Cpu cores to use for ASAN", value_parser = Cores::from_cmdline)] + #[arg(long, help = "Cpu cores to use for ASan", value_parser = Cores::from_cmdline)] pub asan_guest_cores: Option, #[arg(long, help = "Cpu cores to use for CmpLog", value_parser = Cores::from_cmdline)] @@ -63,7 +63,7 @@ pub struct FuzzerOptions { #[clap(long, help = "Enable AFL++ style output", conflicts_with = "verbose")] pub tui: bool, - #[arg(long = "iterations", help = "Maximum numer of iterations")] + #[arg(long = "iterations", help = "Maximum number of iterations")] pub iterations: Option, #[arg(long = "include", help="Include address ranges", value_parser = FuzzerOptions::parse_ranges)] @@ -72,6 +72,18 @@ pub struct FuzzerOptions { #[arg(long = "exclude", help="Exclude address ranges", value_parser = FuzzerOptions::parse_ranges, conflicts_with="include")] pub exclude: Option>>, + #[arg( + short = 'd', + help = "Write a DrCov Trace for the current input. Requires -r." + )] + pub drcov: Option, + + #[arg( + short = 'r', + help = "An input to rerun, instead of starting to fuzz. Will ignore all other settings apart from -d." + )] + pub rerun_input: Option, + #[arg(last = true, help = "Arguments passed to the target")] pub args: Vec, } @@ -182,5 +194,14 @@ impl FuzzerOptions { } } } + + if self.drcov.is_some() && self.rerun_input.is_none() { + let mut cmd = FuzzerOptions::command(); + cmd.error( + ErrorKind::ValueValidation, + format!("The `drcov` option is only supported with `rerun_input`."), + ) + .exit(); + } } } diff --git a/libafl/src/events/llmp/mgr.rs b/libafl/src/events/llmp/mgr.rs index 6e232ad919..1153c30610 100644 --- a/libafl/src/events/llmp/mgr.rs +++ b/libafl/src/events/llmp/mgr.rs @@ -167,10 +167,8 @@ impl LlmpEventManagerBuilder { }) } - /// Create an LLMP event manager on a port - /// - /// If the port is not yet bound, it will act as a broker; otherwise, it - /// will act as a client. + /// Create an LLMP event manager on a port. + /// It expects a broker to exist on this port. #[cfg(feature = "std")] pub fn build_on_port( self,