Merge branch 'main' of github.com:AFLplusplus/LibAFL into main

This commit is contained in:
Andrea Fioraldi 2021-11-15 14:10:36 +01:00
commit 56e05d0ff0
16 changed files with 226 additions and 21 deletions

View File

@ -182,8 +182,8 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re
let iters = 1_000_000; let iters = 1_000_000;
fuzzer.fuzz_loop_for( fuzzer.fuzz_loop_for(
&mut stages, &mut stages,
&mut state,
&mut executor, &mut executor,
&mut state,
&mut restarting_mgr, &mut restarting_mgr,
iters, iters,
)?; )?;

View File

@ -15,12 +15,13 @@ To build this example, run
```bash ```bash
cargo build --release cargo build --release
clang -c weak.c -o weak.o
``` ```
This will build the library with the fuzzer (src/lib.rs) with the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback. This will build the library with the fuzzer (src/lib.rs) with the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback.
In addition, it will also build two C and C++ compiler wrappers (bin/libafl_c(libafl_c/xx).rs) that you must use to compile the target. In addition, it will also build two C and C++ compiler wrappers (bin/libafl_c(libafl_c/xx).rs) that you must use to compile the target.
The compiler wrappers, `libafl_cc` and libafl_cxx`, will end up in `./target/release/` (or `./target/debug`, in case you did not build with the `--release` flag). The compiler wrappers, `libafl_cc` and `libafl_cxx`, will end up in `./target/release/` (or `./target/debug`, in case you did not build with the `--release` flag).
Then download libpng, and unpack the archive: Then download libpng, and unpack the archive:
```bash ```bash
@ -33,7 +34,7 @@ Now compile libpng, using the libafl_cc compiler wrapper:
```bash ```bash
cd libpng-1.6.37 cd libpng-1.6.37
./configure ./configure
make CC="$(pwd)/../target/release/libafl_cc" CXX="$(pwd)/../target/release/libafl_cxx" -j `nproc` LIBAFL_WEAK=../weak.o make CC="$(pwd)/../target/release/libafl_cc" CXX="$(pwd)/../target/release/libafl_cxx" -j `nproc`
``` ```
You can find the static lib at `libpng-1.6.37/.libs/libpng16.a`. You can find the static lib at `libpng-1.6.37/.libs/libpng16.a`.
@ -42,13 +43,17 @@ Now, we have to build the libfuzzer harness and link all together to create our
``` ```
cd .. cd ..
./target/release/libafl_cxx ./harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer_libpng -lz -lm LIBAFL_WEAK=./weak.o ./target/release/libafl_cxx ./harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer_libpng -lz -lm
``` ```
Afterward, the fuzzer will be ready to run. Afterward, the fuzzer will be ready to run.
Note that, unless you use the `launcher`, you will have to run the binary multiple times to actually start the fuzz process, see `Run` in the following. Note that, unless you use the `launcher`, you will have to run the binary multiple times to actually start the fuzz process, see `Run` in the following.
This allows you to run multiple different builds of the same fuzzer alongside, for example, with and without ASAN (`-fsanitize=address`) or with different mutators. This allows you to run multiple different builds of the same fuzzer alongside, for example, with and without ASAN (`-fsanitize=address`) or with different mutators.
This example also shows you how to use a user-defined variable from LibAFL.
`diff.patch` adds an array `__libafl_target_list` to `png.c`. In order to read from this variable from LibAFL, you need to weakly define __libafl_target_list as in `weak.c`.
For building, you have to set `LIBAFL_WEAK` to point to the compiled `weak.o`, so that the compiler can find this `weak.o` file and link successfully.
## Run ## Run
The first time you run the binary, the broker will open a tcp port (currently on port `1337`), waiting for fuzzer clients to connect. This port is local and only used for the initial handshake. All further communication happens via shared map, to be independent of the kernel. Currently you must run the clients from the libfuzzer_libpng directory for them to be able to access the PNG corpus. The first time you run the binary, the broker will open a tcp port (currently on port `1337`), waiting for fuzzer clients to connect. This port is local and only used for the initial handshake. All further communication happens via shared map, to be independent of the kernel. Currently you must run the clients from the libfuzzer_libpng directory for them to be able to access the PNG corpus.

View File

@ -5,6 +5,7 @@ pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
if args.len() > 1 { if args.len() > 1 {
let mut dir = env::current_exe().unwrap(); let mut dir = env::current_exe().unwrap();
let weak = env::var("LIBAFL_WEAK").unwrap();
let wrapper_name = dir.file_name().unwrap().to_str().unwrap(); let wrapper_name = dir.file_name().unwrap().to_str().unwrap();
let is_cpp = match wrapper_name[wrapper_name.len()-2..].to_lowercase().as_str() { let is_cpp = match wrapper_name[wrapper_name.len()-2..].to_lowercase().as_str() {
@ -22,6 +23,7 @@ pub fn main() {
.silence(true) .silence(true)
.from_args(&args) .from_args(&args)
.expect("Failed to parse the command line") .expect("Failed to parse the command line")
.add_link_arg(weak)
.link_staticlib(&dir, "libfuzzer_libpng") .link_staticlib(&dir, "libfuzzer_libpng")
.add_arg("-fsanitize-coverage=trace-pc-guard") .add_arg("-fsanitize-coverage=trace-pc-guard")
.run() .run()

View File

@ -147,8 +147,8 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re
let iters = 1_000_000; let iters = 1_000_000;
fuzzer.fuzz_loop_for( fuzzer.fuzz_loop_for(
&mut stages, &mut stages,
&mut state,
&mut executor, &mut executor,
&mut state,
&mut restarting_mgr, &mut restarting_mgr,
iters, iters,
)?; )?;

View File

@ -0,0 +1,3 @@
#include<stddef.h>
__attribute__((weak, visibility("default"))) size_t *__libafl_target_list;

View File

@ -173,8 +173,8 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re
let iters = 1_000_000; let iters = 1_000_000;
fuzzer.fuzz_loop_for( fuzzer.fuzz_loop_for(
&mut stages, &mut stages,
&mut state,
&mut executor, &mut executor,
&mut state,
&mut restarting_mgr, &mut restarting_mgr,
iters, iters,
)?; )?;

View File

@ -20,6 +20,7 @@ pub mod shmem;
pub mod staterestore; pub mod staterestore;
pub mod tuples; pub mod tuples;
use alloc::string::String;
use core::time; use core::time;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
@ -85,3 +86,10 @@ pub fn current_nanos() -> u64 {
pub fn current_milliseconds() -> u64 { pub fn current_milliseconds() -> u64 {
current_time().as_millis() as u64 current_time().as_millis() as u64
} }
/// Format a `Duration` into a HMS string
#[must_use]
pub fn format_duration_hms(duration: &time::Duration) -> String {
let secs = duration.as_secs();
format!("{}h-{}m-{}s", (secs / 60) / 60, (secs / 60) % 60, secs % 60)
}

View File

@ -866,7 +866,7 @@ where
} }
// Storing state in the last round did not work // Storing state in the last round did not work
panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client! (Child exited with: {})", child_status); panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client! This can happen if the child calls `exit()`, in that case make sure it uses `abort()`, if it got killed unrecoverable (OOM), or if there is a bug in the fuzzer itself. (Child exited with: {})", child_status);
} }
ctr = ctr.wrapping_add(1); ctr = ctr.wrapping_add(1);

View File

@ -376,7 +376,7 @@ where
} }
// Storing state in the last round did not work // Storing state in the last round did not work
panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client! (Child exited with: {})", child_status); panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client! This can happen if the child calls `exit()`, in that case make sure it uses `abort()`, if it got killed unrecoverable (OOM), or if there is a bug in the fuzzer itself. (Child exited with: {})", child_status);
} }
ctr = ctr.wrapping_add(1); ctr = ctr.wrapping_add(1);

View File

@ -188,8 +188,8 @@ pub trait Fuzzer<E, EM, I, S, ST> {
fn fuzz_loop_for( fn fuzz_loop_for(
&mut self, &mut self,
stages: &mut ST, stages: &mut ST,
state: &mut S,
executor: &mut E, executor: &mut E,
state: &mut S,
manager: &mut EM, manager: &mut EM,
iters: u64, iters: u64,
) -> Result<usize, Error> { ) -> Result<usize, Error> {

View File

@ -42,20 +42,20 @@ pub use fuzzer::*;
/// The `stats` module got renamed to [`monitors`]. /// The `stats` module got renamed to [`monitors`].
/// It monitors and displays the statistics of the fuzzing process. /// It monitors and displays the statistics of the fuzzing process.
#[deprecated(since = "0.6.0", note = "The `stats` module got renamed to `monitors`")] #[deprecated(since = "0.7.0", note = "The `stats` module got renamed to `monitors`")]
pub mod stats { pub mod stats {
#[deprecated( #[deprecated(
since = "0.6.0", since = "0.7.0",
note = "Use monitors::MultiMonitor instead of stats::MultiStats!" note = "Use monitors::MultiMonitor instead of stats::MultiStats!"
)] )]
pub use crate::monitors::MultiMonitor as MultiStats; pub use crate::monitors::MultiMonitor as MultiStats;
#[deprecated( #[deprecated(
since = "0.6.0", since = "0.7.0",
note = "Use monitors::SimpleMonitor instead of stats::SimpleStats!" note = "Use monitors::SimpleMonitor instead of stats::SimpleStats!"
)] )]
pub use crate::monitors::SimpleMonitor as SimpleStats; pub use crate::monitors::SimpleMonitor as SimpleStats;
#[deprecated( #[deprecated(
since = "0.6.0", since = "0.7.0",
note = "Use monitors::UserMonitor instead of stats::SimpleStats!" note = "Use monitors::UserMonitor instead of stats::SimpleStats!"
)] )]
pub use crate::monitors::UserStats; pub use crate::monitors::UserStats;

View File

@ -13,7 +13,7 @@ use hashbrown::HashMap;
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
use alloc::string::ToString; use alloc::string::ToString;
use crate::bolts::current_time; use crate::bolts::{current_time, format_duration_hms};
const CLIENT_STATS_TIME_WINDOW_SECS: u64 = 5; // 5 seconds const CLIENT_STATS_TIME_WINDOW_SECS: u64 = 5; // 5 seconds
@ -269,10 +269,10 @@ where
fn display(&mut self, event_msg: String, sender_id: u32) { fn display(&mut self, event_msg: String, sender_id: u32) {
let fmt = format!( let fmt = format!(
"{}: [{} #{}] clients: {}, corpus: {}, objectives: {}, executions: {}, exec/sec: {}", "[{} #{}] run time: {}, clients: {}, corpus: {}, objectives: {}, executions: {}, exec/sec: {}",
(current_time() - self.start_time).as_millis(),
event_msg, event_msg,
sender_id, sender_id,
format_duration_hms(&(current_time() - self.start_time)),
self.client_stats().len(), self.client_stats().len(),
self.corpus_size(), self.corpus_size(),
self.objective_size(), self.objective_size(),

View File

@ -7,7 +7,7 @@ use core::{time, time::Duration};
use alloc::string::ToString; use alloc::string::ToString;
use crate::{ use crate::{
bolts::current_time, bolts::{current_time, format_duration_hms},
monitors::{ClientStats, Monitor}, monitors::{ClientStats, Monitor},
}; };
@ -50,8 +50,9 @@ where
}; };
let head = format!("{}{} {}", event_msg, pad, sender); let head = format!("{}{} {}", event_msg, pad, sender);
let global_fmt = format!( let global_fmt = format!(
"[{}] (GLOBAL) clients: {}, corpus: {}, objectives: {}, executions: {}, exec/sec: {}", "[{}] (GLOBAL) run time: {}, clients: {}, corpus: {}, objectives: {}, executions: {}, exec/sec: {}",
head, head,
format_duration_hms(&(current_time() - self.start_time)),
self.client_stats().len(), self.client_stats().len(),
self.corpus_size(), self.corpus_size(),
self.objective_size(), self.objective_size(),

View File

@ -26,6 +26,11 @@ pub use concolic::ConcolicTracingStage;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub use concolic::SimpleConcolicMutationalStage; pub use concolic::SimpleConcolicMutationalStage;
#[cfg(feature = "std")]
pub mod sync;
#[cfg(feature = "std")]
pub use sync::*;
use crate::Error; use crate::Error;
use core::{convert::From, marker::PhantomData}; use core::{convert::From, marker::PhantomData};

View File

@ -58,7 +58,6 @@ where
{ {
initialized: bool, initialized: bool,
state: Rc<RefCell<S>>, state: Rc<RefCell<S>>,
current_iter: Option<usize>,
current_corpus_idx: Option<usize>, current_corpus_idx: Option<usize>,
testcases_to_do: usize, testcases_to_do: usize,
testcases_done: usize, testcases_done: usize,
@ -201,7 +200,7 @@ where
// We already ran once // We already ran once
self.post_exec() self.post_exec()
} else { } else {
self.init() // TODO: Corpus idx self.init()
}; };
if let Err(err) = step_success { if let Err(err) = step_success {
//let errored = true; //let errored = true;
@ -264,7 +263,6 @@ where
phantom: PhantomData, phantom: PhantomData,
initialized: false, initialized: false,
state, state,
current_iter: None,
current_corpus_idx: None, // todo current_corpus_idx: None, // todo
testcases_to_do: 0, testcases_to_do: 0,
testcases_done: 0, testcases_done: 0,

183
libafl/src/stages/sync.rs Normal file
View File

@ -0,0 +1,183 @@
//| The [`MutationalStage`] is the default stage used during fuzzing.
//! For the current input, it will perform a range of random mutations, and then run them in the executor.
use core::marker::PhantomData;
use serde::{Deserialize, Serialize};
use std::{
fs,
path::{Path, PathBuf},
time::SystemTime,
};
use crate::{
bolts::rands::Rand,
corpus::Corpus,
fuzzer::Evaluator,
inputs::Input,
stages::Stage,
state::{HasClientPerfMonitor, HasCorpus, HasMetadata, HasRand},
Error,
};
#[derive(Serialize, Deserialize)]
pub struct SyncFromDiskMetadata {
pub last_time: SystemTime,
}
crate::impl_serdeany!(SyncFromDiskMetadata);
impl SyncFromDiskMetadata {
#[must_use]
pub fn new(last_time: SystemTime) -> Self {
Self { last_time }
}
}
/// A stage that loads testcases from disk to sync with other fuzzers such as AFL++
pub struct SyncFromDiskStage<C, CB, E, EM, I, R, S, Z>
where
C: Corpus<I>,
CB: FnMut(&mut Z, &mut S, &Path) -> Result<I, Error>,
I: Input,
R: Rand,
S: HasClientPerfMonitor + HasCorpus<C, I> + HasRand<R> + HasMetadata,
Z: Evaluator<E, EM, I, S>,
{
sync_dir: PathBuf,
load_callback: CB,
#[allow(clippy::type_complexity)]
phantom: PhantomData<(C, E, EM, I, R, S, Z)>,
}
impl<C, CB, E, EM, I, R, S, Z> Stage<E, EM, S, Z> for SyncFromDiskStage<C, CB, E, EM, I, R, S, Z>
where
C: Corpus<I>,
CB: FnMut(&mut Z, &mut S, &Path) -> Result<I, Error>,
I: Input,
R: Rand,
S: HasClientPerfMonitor + HasCorpus<C, I> + HasRand<R> + HasMetadata,
Z: Evaluator<E, EM, I, S>,
{
#[inline]
fn perform(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut S,
manager: &mut EM,
_corpus_idx: usize,
) -> Result<(), Error> {
let last = state
.metadata()
.get::<SyncFromDiskMetadata>()
.map(|m| m.last_time);
let path = self.sync_dir.clone();
if let Some(max_time) =
self.load_from_directory(&path, &last, fuzzer, executor, state, manager)?
{
if last.is_none() {
state
.metadata_mut()
.insert(SyncFromDiskMetadata::new(max_time));
} else {
state
.metadata_mut()
.get_mut::<SyncFromDiskMetadata>()
.unwrap()
.last_time = max_time;
}
}
#[cfg(feature = "introspection")]
state.introspection_monitor_mut().finish_stage();
Ok(())
}
}
impl<C, CB, E, EM, I, R, S, Z> SyncFromDiskStage<C, CB, E, EM, I, R, S, Z>
where
C: Corpus<I>,
CB: FnMut(&mut Z, &mut S, &Path) -> Result<I, Error>,
I: Input,
R: Rand,
S: HasClientPerfMonitor + HasCorpus<C, I> + HasRand<R> + HasMetadata,
Z: Evaluator<E, EM, I, S>,
{
/// Creates a new [`SyncFromDiskStage`]
#[must_use]
pub fn new(sync_dir: PathBuf, load_callback: CB) -> Self {
Self {
sync_dir,
load_callback,
phantom: PhantomData,
}
}
fn load_from_directory(
&mut self,
in_dir: &Path,
last: &Option<SystemTime>,
fuzzer: &mut Z,
executor: &mut E,
state: &mut S,
manager: &mut EM,
) -> Result<Option<SystemTime>, Error> {
let mut max_time = None;
for entry in fs::read_dir(in_dir)? {
let entry = entry?;
let path = entry.path();
let attributes = fs::metadata(&path);
if attributes.is_err() {
continue;
}
let attr = attributes?;
if attr.is_file() && attr.len() > 0 {
if let Ok(time) = attr.modified() {
if let Some(l) = last {
if time.duration_since(*l).is_err() {
continue;
}
}
max_time = Some(max_time.map_or(time, |t: SystemTime| t.max(time)));
let input = (self.load_callback)(fuzzer, state, &path)?;
drop(fuzzer.evaluate_input(state, executor, manager, input)?);
}
} else if attr.is_dir() {
let dir_max_time =
self.load_from_directory(&path, last, fuzzer, executor, state, manager)?;
if let Some(time) = dir_max_time {
max_time = Some(max_time.map_or(time, |t: SystemTime| t.max(time)));
}
}
}
Ok(max_time)
}
}
impl<C, E, EM, I, R, S, Z>
SyncFromDiskStage<C, fn(&mut Z, &mut S, &Path) -> Result<I, Error>, E, EM, I, R, S, Z>
where
C: Corpus<I>,
I: Input,
R: Rand,
S: HasClientPerfMonitor + HasCorpus<C, I> + HasRand<R> + HasMetadata,
Z: Evaluator<E, EM, I, S>,
{
/// Creates a new [`SyncFromDiskStage`] invoking `Input::from_file` to load inputs
#[must_use]
pub fn with_from_file(sync_dir: PathBuf) -> Self {
fn load_callback<Z, S, I: Input>(_: &mut Z, _: &mut S, p: &Path) -> Result<I, Error> {
I::from_file(p)
}
Self {
sync_dir,
load_callback: load_callback::<_, _, I>,
phantom: PhantomData,
}
}
}