Add DrCov &.rerun options to QEMU_Launcher (#2607)

* Move all target-specific code to harness.rs

* Add IfModule to LibAFL_Qemu

* More more

* Added DrCov tracing

* Add DrCov rerun to launcher

* fixes
This commit is contained in:
Dominik Maier 2024-10-11 16:02:01 +02:00 committed by GitHub
parent 2bfed2d488
commit 66a8682c9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 113 additions and 18 deletions

View File

@ -15,6 +15,7 @@ use libafl_qemu::{
asan::{init_qemu_with_asan, AsanModule}, asan::{init_qemu_with_asan, AsanModule},
asan_guest::{init_qemu_with_asan_guest, AsanGuestModule}, asan_guest::{init_qemu_with_asan_guest, AsanGuestModule},
cmplog::CmpLogModule, cmplog::CmpLogModule,
DrCovModule,
}, },
Qemu, Qemu,
}; };
@ -38,7 +39,7 @@ impl<'a> Client<'a> {
Client { options } Client { options }
} }
fn args(&self) -> Result<Vec<String>, Error> { pub fn args(&self) -> Result<Vec<String>, Error> {
let program = env::args() let program = env::args()
.next() .next()
.ok_or_else(|| Error::empty_optional("Failed to read program name"))?; .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 #[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() env::vars()
.filter(|(k, _v)| k != "LD_LIBRARY_PATH") .filter(|(k, _v)| k != "LD_LIBRARY_PATH")
.collect::<Vec<(String, String)>>() .collect::<Vec<(String, String)>>()
@ -125,7 +126,16 @@ impl<'a> Client<'a> {
.core_id(core_id) .core_id(core_id)
.extra_tokens(extra_tokens); .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 { if let Some(injection_module) = injection_module {
instance_builder.build().run( instance_builder.build().run(
tuple_list!( tuple_list!(

View File

@ -10,14 +10,20 @@ use libafl::events::SimpleEventManager;
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
use libafl::events::{EventConfig, Launcher, MonitorTypedEventManager}; use libafl::events::{EventConfig, Launcher, MonitorTypedEventManager};
use libafl::{ use libafl::{
events::{LlmpEventManager, LlmpRestartingEventManager},
monitors::{tui::TuiMonitor, Monitor, MultiMonitor}, monitors::{tui::TuiMonitor, Monitor, MultiMonitor},
Error, Error,
}; };
#[cfg(feature = "simplemgr")] #[cfg(feature = "simplemgr")]
use libafl_bolts::core_affinity::CoreId; use libafl_bolts::core_affinity::CoreId;
use libafl_bolts::current_time;
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
use libafl_bolts::shmem::{ShMemProvider, StdShMemProvider}; use libafl_bolts::shmem::{ShMemProvider, StdShMemProvider};
use libafl_bolts::{
current_time,
llmp::LlmpBroker,
prelude::{CoreId, StateRestorer},
tuples::tuple_list,
};
#[cfg(unix)] #[cfg(unix)]
use { use {
nix::unistd::dup, nix::unistd::dup,
@ -82,7 +88,7 @@ impl Fuzzer {
{ {
// The shared memory allocator // The shared memory allocator
#[cfg(not(feature = "simplemgr"))] #[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 */ /* If we are running in verbose, don't provide a replacement stdout, otherwise, use /dev/null */
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
@ -94,10 +100,42 @@ impl Fuzzer {
let client = Client::new(&self.options); 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")] #[cfg(feature = "simplemgr")]
return client.run(None, SimpleEventManager::new(monitor), CoreId(0)); return client.run(None, SimpleEventManager::new(monitor), CoreId(0));
// Build and run a Launcher // Build and run the Launcher / fuzzer.
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
match Launcher::builder() match Launcher::builder()
.shmem_provider(shmem_provider) .shmem_provider(shmem_provider)

View File

@ -1,5 +1,5 @@
use core::{fmt::Debug, ptr::addr_of_mut}; 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")] #[cfg(feature = "simplemgr")]
use libafl::events::SimpleEventManager; use libafl::events::SimpleEventManager;
@ -7,8 +7,8 @@ use libafl::events::SimpleEventManager;
use libafl::events::{LlmpRestartingEventManager, MonitorTypedEventManager}; use libafl::events::{LlmpRestartingEventManager, MonitorTypedEventManager};
use libafl::{ use libafl::{
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus}, corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
events::EventRestarter, events::{EventRestarter, NopEventManager},
executors::ShadowExecutor, executors::{Executor, ShadowExecutor},
feedback_or, feedback_or_fast, feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Evaluator, Fuzzer, StdFuzzer}, fuzzer::{Evaluator, Fuzzer, StdFuzzer},
@ -27,7 +27,7 @@ use libafl::{
StagesTuple, StdMutationalStage, StagesTuple, StdMutationalStage,
}, },
state::{HasCorpus, StdState, UsesState}, state::{HasCorpus, StdState, UsesState},
Error, HasMetadata, Error, HasMetadata, NopFuzzer,
}; };
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
use libafl_bolts::shmem::StdShMemProvider; 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()?; 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) { if self.options.is_cmplog_core(self.core_id) {
// Create a QEMU in-process executor // Create a QEMU in-process executor
let executor = QemuExecutor::new( let executor = QemuExecutor::new(

View File

@ -39,7 +39,7 @@ pub struct FuzzerOptions {
#[arg(long, help = "Log file")] #[arg(long, help = "Log file")]
pub log: Option<String>, pub log: Option<String>,
#[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, pub timeout: Duration,
#[arg(long = "port", help = "Broker port", default_value_t = 1337_u16)] #[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)] #[arg(long, help = "Cpu cores to use", default_value = "all", value_parser = Cores::from_cmdline)]
pub cores: Cores, 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<Cores>, pub asan_cores: Option<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_guest_cores: Option<Cores>, pub asan_guest_cores: Option<Cores>,
#[arg(long, help = "Cpu cores to use for CmpLog", value_parser = Cores::from_cmdline)] #[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")] #[clap(long, help = "Enable AFL++ style output", conflicts_with = "verbose")]
pub tui: bool, pub tui: bool,
#[arg(long = "iterations", help = "Maximum numer of iterations")] #[arg(long = "iterations", help = "Maximum number of iterations")]
pub iterations: Option<u64>, pub iterations: Option<u64>,
#[arg(long = "include", help="Include address ranges", value_parser = FuzzerOptions::parse_ranges)] #[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")] #[arg(long = "exclude", help="Exclude address ranges", value_parser = FuzzerOptions::parse_ranges, conflicts_with="include")]
pub exclude: Option<Vec<Range<GuestAddr>>>, pub exclude: Option<Vec<Range<GuestAddr>>>,
#[arg(
short = 'd',
help = "Write a DrCov Trace for the current input. Requires -r."
)]
pub drcov: Option<PathBuf>,
#[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<PathBuf>,
#[arg(last = true, help = "Arguments passed to the target")] #[arg(last = true, help = "Arguments passed to the target")]
pub args: Vec<String>, pub args: Vec<String>,
} }
@ -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();
}
} }
} }

View File

@ -167,10 +167,8 @@ impl<EMH> LlmpEventManagerBuilder<EMH> {
}) })
} }
/// Create an LLMP event manager on a port /// Create an LLMP event manager on a port.
/// /// It expects a broker to exist on this port.
/// If the port is not yet bound, it will act as a broker; otherwise, it
/// will act as a client.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn build_on_port<S, SP>( pub fn build_on_port<S, SP>(
self, self,