Add bloom filter for duplicate execution of the same inputs (#2771)
* fixing empty multipart name * fixing clippy * New rules for the contributing (#2752) * Rules * more * aa * Improve Flexibility of DumpToDiskStage (#2753) * fixing empty multipart name * fixing clippy * improve flexibility of DumpToDiskStage * adding note to MIGRATION.md * Update bindgen requirement from 0.70.1 to 0.71.1 (#2756) Updates the requirements on [bindgen](https://github.com/rust-lang/rust-bindgen) to permit the latest version. - [Release notes](https://github.com/rust-lang/rust-bindgen/releases) - [Changelog](https://github.com/rust-lang/rust-bindgen/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/rust-bindgen/compare/v0.70.1...v0.71.1) --- updated-dependencies: - dependency-name: bindgen dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * No Use* from stages (#2745) * no from stage * fixer * doc fix * how was this working???? * more fixes * delete more * rq * cargo-fuzz * m * aa * Update CONTRIBUTING.md MIGRATION.md (#2762) * No Uses* from `fuzzer` (#2761) * go * fixing stuf * hello from windows * more * lolg * lolf * fix * a --------- Co-authored-by: Your Name <you@example.com> * Remove useless cfgs (#2764) * Link libresolv on all Apple OSs (#2767) * Somewhat ugly CI fix... (#2768) * Maybe fix CI * does this help? * Very dirty 'fix' * Add Input Types and Mutators for Numeric Types (#2760) * fixing empty multipart name * fixing clippy * New rules for the contributing (#2752) * Rules * more * aa * Improve Flexibility of DumpToDiskStage (#2753) * fixing empty multipart name * fixing clippy * improve flexibility of DumpToDiskStage * adding note to MIGRATION.md * Introduce WrappingMutator * introducing mutators for int types * fixing no_std * random fixes * Add hash derivation for WrappingInput * Revert fixes that broke things * Derive Default on WrappingInput * Add unit tests * Fixes according to code review * introduce mappable ValueInputs * remove unnecessary comments * Elide more lifetimes * remove dead code * simplify hashing * improve docs * improve randomization * rename method to align with standard library * add typedefs for int types for ValueMutRefInput * rename test * add safety notice to trait function * improve randomize performance for i128/u128 * rename macro * improve comment * actually check return values in test * make 128 bit int randomize even more efficient * shifting signed values --------- Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com> Co-authored-by: Dominik Maier <domenukk@gmail.com> * Add HashMutator * Fix docs * Fix docs again * introducing bloom filter * fix tests * Implement evaluate_filtered * Add macros to libafl_bolts tuples for mapping and merging types (#2788) * Add macros * Use the macros for havoc_mutations * Fix docs * improve merge_tuple_list_type to accept n items * libafl_cc: Automatically find llvm_ar path (#2790) * imemory_ondisk: Don't fail write under any circumstances if locking is disabled (#2791) * imemory_ondisk: Don't fail write under any circumstances if locking is disabled * fmt * inmemory_ondisk: Add a log message on failure * clippy' * micro optimization * Revert changes to global Cargo.toml * Hide std-dependent dependency behind std feature * Fix example fuzzer * Rename constructor for filtered fuzzer * Reorder generics alphabetically * Rename HashingMutator, add note to MutationResult about filtered fuzzers * Improve StdFuzzer according to feedback * rename hashing mutator * Fix english in comment * Cleanup of old PRs that break the CI * Fix more CI bugs * Code cleanup * Remove unnecessary comments --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Your Name <you@example.com> Co-authored-by: Sharad Khanna <sharad@mineo333.dev> Co-authored-by: Dominik Maier <domenukk@gmail.com> Co-authored-by: s1341 <s1341@users.noreply.github.com>
This commit is contained in:
parent
2a79ee5b4f
commit
d8ec991b48
@ -8,8 +8,9 @@ authors = [
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std", "bloom_input_filter"]
|
||||||
tui = ["libafl/tui_monitor"]
|
tui = ["libafl/tui_monitor"]
|
||||||
|
bloom_input_filter = ["std"]
|
||||||
std = []
|
std = []
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
@ -134,7 +134,11 @@ pub fn main() {
|
|||||||
let scheduler = QueueScheduler::new();
|
let scheduler = QueueScheduler::new();
|
||||||
|
|
||||||
// A fuzzer with feedbacks and a corpus scheduler
|
// A fuzzer with feedbacks and a corpus scheduler
|
||||||
|
#[cfg(not(feature = "bloom_input_filter"))]
|
||||||
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||||
|
#[cfg(feature = "bloom_input_filter")]
|
||||||
|
let mut fuzzer =
|
||||||
|
StdFuzzer::with_bloom_input_filter(scheduler, feedback, objective, 10_000_000, 0.001);
|
||||||
|
|
||||||
// Create the executor for an in-process function with just one observer
|
// Create the executor for an in-process function with just one observer
|
||||||
let executor = CustomExecutor::new(&state);
|
let executor = CustomExecutor::new(&state);
|
||||||
|
@ -58,6 +58,7 @@ std = [
|
|||||||
"serial_test",
|
"serial_test",
|
||||||
"libafl_bolts/std",
|
"libafl_bolts/std",
|
||||||
"typed-builder",
|
"typed-builder",
|
||||||
|
"fastbloom",
|
||||||
]
|
]
|
||||||
|
|
||||||
## Tracks the Feedbacks and the Objectives that were interesting for a Testcase
|
## Tracks the Feedbacks and the Objectives that were interesting for a Testcase
|
||||||
@ -291,6 +292,7 @@ document-features = { workspace = true, optional = true }
|
|||||||
clap = { workspace = true, optional = true }
|
clap = { workspace = true, optional = true }
|
||||||
num_enum = { workspace = true, optional = true }
|
num_enum = { workspace = true, optional = true }
|
||||||
libipt = { workspace = true, optional = true }
|
libipt = { workspace = true, optional = true }
|
||||||
|
fastbloom = { version = "0.8.0", optional = true }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
@ -557,7 +557,7 @@ mod tests {
|
|||||||
let mut mgr = NopEventManager::new();
|
let mut mgr = NopEventManager::new();
|
||||||
let mut state =
|
let mut state =
|
||||||
StdState::new(rand, corpus, solutions, &mut feedback, &mut objective).unwrap();
|
StdState::new(rand, corpus, solutions, &mut feedback, &mut objective).unwrap();
|
||||||
let mut fuzzer = StdFuzzer::<_, _, _>::new(sche, feedback, objective);
|
let mut fuzzer = StdFuzzer::new(sche, feedback, objective);
|
||||||
|
|
||||||
let mut in_process_executor = InProcessExecutor::new(
|
let mut in_process_executor = InProcessExecutor::new(
|
||||||
&mut harness,
|
&mut harness,
|
||||||
|
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
use alloc::{string::ToString, vec::Vec};
|
use alloc::{string::ToString, vec::Vec};
|
||||||
use core::{fmt::Debug, time::Duration};
|
use core::{fmt::Debug, time::Duration};
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use fastbloom::BloomFilter;
|
||||||
use libafl_bolts::{current_time, tuples::MatchName};
|
use libafl_bolts::{current_time, tuples::MatchName};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
@ -138,6 +142,16 @@ pub trait EvaluatorObservers<E, EM, I, S> {
|
|||||||
|
|
||||||
/// Evaluate an input modifying the state of the fuzzer
|
/// Evaluate an input modifying the state of the fuzzer
|
||||||
pub trait Evaluator<E, EM, I, S> {
|
pub trait Evaluator<E, EM, I, S> {
|
||||||
|
/// Runs the input if it was (likely) not previously run and triggers observers and feedback and adds the input to the previously executed list
|
||||||
|
/// returns if is interesting an (option) the index of the new [`crate::corpus::Testcase`] in the corpus
|
||||||
|
fn evaluate_filtered(
|
||||||
|
&mut self,
|
||||||
|
state: &mut S,
|
||||||
|
executor: &mut E,
|
||||||
|
manager: &mut EM,
|
||||||
|
input: I,
|
||||||
|
) -> Result<(ExecuteInputResult, Option<CorpusId>), Error>;
|
||||||
|
|
||||||
/// Runs the input and triggers observers and feedback,
|
/// Runs the input and triggers observers and feedback,
|
||||||
/// returns if is interesting an (option) the index of the new [`crate::corpus::Testcase`] in the corpus
|
/// returns if is interesting an (option) the index of the new [`crate::corpus::Testcase`] in the corpus
|
||||||
fn evaluate_input(
|
fn evaluate_input(
|
||||||
@ -242,13 +256,14 @@ pub enum ExecuteInputResult {
|
|||||||
|
|
||||||
/// Your default fuzzer instance, for everyday use.
|
/// Your default fuzzer instance, for everyday use.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StdFuzzer<CS, F, OF> {
|
pub struct StdFuzzer<CS, F, IF, OF> {
|
||||||
scheduler: CS,
|
scheduler: CS,
|
||||||
feedback: F,
|
feedback: F,
|
||||||
objective: OF,
|
objective: OF,
|
||||||
|
input_filter: IF,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS, F, OF, S> HasScheduler<<S::Corpus as Corpus>::Input, S> for StdFuzzer<CS, F, OF>
|
impl<CS, F, IF, OF, S> HasScheduler<<S::Corpus as Corpus>::Input, S> for StdFuzzer<CS, F, IF, OF>
|
||||||
where
|
where
|
||||||
S: HasCorpus,
|
S: HasCorpus,
|
||||||
CS: Scheduler<<S::Corpus as Corpus>::Input, S>,
|
CS: Scheduler<<S::Corpus as Corpus>::Input, S>,
|
||||||
@ -264,7 +279,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS, F, OF> HasFeedback for StdFuzzer<CS, F, OF> {
|
impl<CS, F, IF, OF> HasFeedback for StdFuzzer<CS, F, IF, OF> {
|
||||||
type Feedback = F;
|
type Feedback = F;
|
||||||
|
|
||||||
fn feedback(&self) -> &Self::Feedback {
|
fn feedback(&self) -> &Self::Feedback {
|
||||||
@ -276,7 +291,7 @@ impl<CS, F, OF> HasFeedback for StdFuzzer<CS, F, OF> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS, F, OF> HasObjective for StdFuzzer<CS, F, OF> {
|
impl<CS, F, IF, OF> HasObjective for StdFuzzer<CS, F, IF, OF> {
|
||||||
type Objective = OF;
|
type Objective = OF;
|
||||||
|
|
||||||
fn objective(&self) -> &OF {
|
fn objective(&self) -> &OF {
|
||||||
@ -288,8 +303,8 @@ impl<CS, F, OF> HasObjective for StdFuzzer<CS, F, OF> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS, EM, F, OF, OT, S> ExecutionProcessor<EM, <S::Corpus as Corpus>::Input, OT, S>
|
impl<CS, EM, F, IF, OF, OT, S> ExecutionProcessor<EM, <S::Corpus as Corpus>::Input, OT, S>
|
||||||
for StdFuzzer<CS, F, OF>
|
for StdFuzzer<CS, F, IF, OF>
|
||||||
where
|
where
|
||||||
CS: Scheduler<<S::Corpus as Corpus>::Input, S>,
|
CS: Scheduler<<S::Corpus as Corpus>::Input, S>,
|
||||||
EM: EventFirer<State = S>,
|
EM: EventFirer<State = S>,
|
||||||
@ -494,8 +509,8 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS, E, EM, F, OF, S> EvaluatorObservers<E, EM, <S::Corpus as Corpus>::Input, S>
|
impl<CS, E, EM, F, IF, OF, S> EvaluatorObservers<E, EM, <S::Corpus as Corpus>::Input, S>
|
||||||
for StdFuzzer<CS, F, OF>
|
for StdFuzzer<CS, F, IF, OF>
|
||||||
where
|
where
|
||||||
CS: Scheduler<<S::Corpus as Corpus>::Input, S>,
|
CS: Scheduler<<S::Corpus as Corpus>::Input, S>,
|
||||||
E: HasObservers + Executor<EM, Self, State = S>,
|
E: HasObservers + Executor<EM, Self, State = S>,
|
||||||
@ -532,7 +547,48 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS, E, EM, F, OF, S> Evaluator<E, EM, <S::Corpus as Corpus>::Input, S> for StdFuzzer<CS, F, OF>
|
trait InputFilter<I> {
|
||||||
|
fn should_execute(&mut self, input: &I) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A pseudo-filter that will execute each input.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NopInputFilter;
|
||||||
|
impl<I> InputFilter<I> for NopInputFilter {
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
fn should_execute(&mut self, _input: &I) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A filter that probabilistically prevents duplicate execution of the same input based on a bloom filter.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BloomInputFilter {
|
||||||
|
bloom: BloomFilter,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl BloomInputFilter {
|
||||||
|
#[must_use]
|
||||||
|
fn new(items_count: usize, fp_p: f64) -> Self {
|
||||||
|
let bloom = BloomFilter::with_false_pos(fp_p).expected_items(items_count);
|
||||||
|
Self { bloom }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl<I: Hash> InputFilter<I> for BloomInputFilter {
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
fn should_execute(&mut self, input: &I) -> bool {
|
||||||
|
!self.bloom.insert(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CS, E, EM, F, IF, OF, S> Evaluator<E, EM, <S::Corpus as Corpus>::Input, S>
|
||||||
|
for StdFuzzer<CS, F, IF, OF>
|
||||||
where
|
where
|
||||||
CS: Scheduler<<S::Corpus as Corpus>::Input, S>,
|
CS: Scheduler<<S::Corpus as Corpus>::Input, S>,
|
||||||
E: HasObservers + Executor<EM, Self, State = S>,
|
E: HasObservers + Executor<EM, Self, State = S>,
|
||||||
@ -549,7 +605,22 @@ where
|
|||||||
+ UsesInput<Input = <S::Corpus as Corpus>::Input>,
|
+ UsesInput<Input = <S::Corpus as Corpus>::Input>,
|
||||||
<S::Corpus as Corpus>::Input: Input,
|
<S::Corpus as Corpus>::Input: Input,
|
||||||
S::Solutions: Corpus<Input = <S::Corpus as Corpus>::Input>,
|
S::Solutions: Corpus<Input = <S::Corpus as Corpus>::Input>,
|
||||||
|
IF: InputFilter<<S::Corpus as Corpus>::Input>,
|
||||||
{
|
{
|
||||||
|
fn evaluate_filtered(
|
||||||
|
&mut self,
|
||||||
|
state: &mut S,
|
||||||
|
executor: &mut E,
|
||||||
|
manager: &mut EM,
|
||||||
|
input: <S::Corpus as Corpus>::Input,
|
||||||
|
) -> Result<(ExecuteInputResult, Option<CorpusId>), Error> {
|
||||||
|
if self.input_filter.should_execute(&input) {
|
||||||
|
self.evaluate_input(state, executor, manager, input)
|
||||||
|
} else {
|
||||||
|
Ok((ExecuteInputResult::None, None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Process one input, adding to the respective corpora if needed and firing the right events
|
/// Process one input, adding to the respective corpora if needed and firing the right events
|
||||||
#[inline]
|
#[inline]
|
||||||
fn evaluate_input_events(
|
fn evaluate_input_events(
|
||||||
@ -562,6 +633,7 @@ where
|
|||||||
) -> Result<(ExecuteInputResult, Option<CorpusId>), Error> {
|
) -> Result<(ExecuteInputResult, Option<CorpusId>), Error> {
|
||||||
self.evaluate_input_with_observers(state, executor, manager, input, send_events)
|
self.evaluate_input_with_observers(state, executor, manager, input, send_events)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_disabled_input(
|
fn add_disabled_input(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut S,
|
state: &mut S,
|
||||||
@ -573,6 +645,7 @@ where
|
|||||||
let id = state.corpus_mut().add_disabled(testcase)?;
|
let id = state.corpus_mut().add_disabled(testcase)?;
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds an input, even if it's not considered `interesting` by any of the executors
|
/// Adds an input, even if it's not considered `interesting` by any of the executors
|
||||||
fn add_input(
|
fn add_input(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -672,7 +745,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS, E, EM, F, OF, S, ST> Fuzzer<E, EM, S, ST> for StdFuzzer<CS, F, OF>
|
impl<CS, E, EM, F, IF, OF, S, ST> Fuzzer<E, EM, S, ST> for StdFuzzer<CS, F, IF, OF>
|
||||||
where
|
where
|
||||||
CS: Scheduler<S::Input, S>,
|
CS: Scheduler<S::Input, S>,
|
||||||
E: UsesState<State = S>,
|
E: UsesState<State = S>,
|
||||||
@ -796,17 +869,44 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS, F, OF> StdFuzzer<CS, F, OF> {
|
impl<CS, F, IF, OF> StdFuzzer<CS, F, IF, OF> {
|
||||||
/// Create a new `StdFuzzer` with standard behavior.
|
/// Create a new [`StdFuzzer`] with standard behavior and the provided duplicate input execution filter.
|
||||||
pub fn new(scheduler: CS, feedback: F, objective: OF) -> Self {
|
pub fn with_input_filter(scheduler: CS, feedback: F, objective: OF, input_filter: IF) -> Self {
|
||||||
Self {
|
Self {
|
||||||
scheduler,
|
scheduler,
|
||||||
feedback,
|
feedback,
|
||||||
objective,
|
objective,
|
||||||
|
input_filter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<CS, F, OF> StdFuzzer<CS, F, NopInputFilter, OF> {
|
||||||
|
/// Create a new [`StdFuzzer`] with standard behavior and no duplicate input execution filtering.
|
||||||
|
pub fn new(scheduler: CS, feedback: F, objective: OF) -> Self {
|
||||||
|
Self::with_input_filter(scheduler, feedback, objective, NopInputFilter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")] // hashing requires std
|
||||||
|
impl<CS, F, OF> StdFuzzer<CS, F, BloomInputFilter, OF> {
|
||||||
|
/// Create a new [`StdFuzzer`], which, with a certain certainty, executes each input only once.
|
||||||
|
///
|
||||||
|
/// This is achieved by hashing each input and using a bloom filter to differentiate inputs.
|
||||||
|
///
|
||||||
|
/// Use this implementation if hashing each input is very fast compared to executing potential duplicate inputs.
|
||||||
|
pub fn with_bloom_input_filter(
|
||||||
|
scheduler: CS,
|
||||||
|
feedback: F,
|
||||||
|
objective: OF,
|
||||||
|
items_count: usize,
|
||||||
|
fp_p: f64,
|
||||||
|
) -> Self {
|
||||||
|
let input_filter = BloomInputFilter::new(items_count, fp_p);
|
||||||
|
Self::with_input_filter(scheduler, feedback, objective, input_filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Structs with this trait will execute an input
|
/// Structs with this trait will execute an input
|
||||||
pub trait ExecutesInput<E, EM, I, S> {
|
pub trait ExecutesInput<E, EM, I, S> {
|
||||||
/// Runs the input and triggers observers and feedback
|
/// Runs the input and triggers observers and feedback
|
||||||
@ -819,8 +919,8 @@ pub trait ExecutesInput<E, EM, I, S> {
|
|||||||
) -> Result<ExitKind, Error>;
|
) -> Result<ExitKind, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS, E, EM, F, OF, S> ExecutesInput<E, EM, <S::Corpus as Corpus>::Input, S>
|
impl<CS, E, EM, F, IF, OF, S> ExecutesInput<E, EM, <S::Corpus as Corpus>::Input, S>
|
||||||
for StdFuzzer<CS, F, OF>
|
for StdFuzzer<CS, F, IF, OF>
|
||||||
where
|
where
|
||||||
CS: Scheduler<<S::Corpus as Corpus>::Input, S>,
|
CS: Scheduler<<S::Corpus as Corpus>::Input, S>,
|
||||||
E: Executor<EM, Self, State = S> + HasObservers,
|
E: Executor<EM, Self, State = S> + HasObservers,
|
||||||
@ -913,3 +1013,63 @@ where
|
|||||||
unimplemented!("NopFuzzer cannot fuzz");
|
unimplemented!("NopFuzzer cannot fuzz");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, feature = "std"))]
|
||||||
|
mod tests {
|
||||||
|
use core::cell::RefCell;
|
||||||
|
|
||||||
|
use libafl_bolts::rands::StdRand;
|
||||||
|
|
||||||
|
use super::{Evaluator, StdFuzzer};
|
||||||
|
use crate::{
|
||||||
|
corpus::InMemoryCorpus,
|
||||||
|
events::NopEventManager,
|
||||||
|
executors::{ExitKind, InProcessExecutor},
|
||||||
|
inputs::BytesInput,
|
||||||
|
schedulers::StdScheduler,
|
||||||
|
state::StdState,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filtered_execution() {
|
||||||
|
let execution_count = RefCell::new(0);
|
||||||
|
let scheduler = StdScheduler::new();
|
||||||
|
let mut fuzzer = StdFuzzer::with_bloom_input_filter(scheduler, (), (), 100, 1e-4);
|
||||||
|
let mut state = StdState::new(
|
||||||
|
StdRand::new(),
|
||||||
|
InMemoryCorpus::new(),
|
||||||
|
InMemoryCorpus::new(),
|
||||||
|
&mut (),
|
||||||
|
&mut (),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut manager = NopEventManager::new();
|
||||||
|
let mut harness = |_input: &BytesInput| {
|
||||||
|
*execution_count.borrow_mut() += 1;
|
||||||
|
ExitKind::Ok
|
||||||
|
};
|
||||||
|
let mut executor =
|
||||||
|
InProcessExecutor::new(&mut harness, (), &mut fuzzer, &mut state, &mut manager)
|
||||||
|
.unwrap();
|
||||||
|
let input = BytesInput::new(vec![1, 2, 3]);
|
||||||
|
assert!(fuzzer
|
||||||
|
.evaluate_input(&mut state, &mut executor, &mut manager, input.clone())
|
||||||
|
.is_ok());
|
||||||
|
assert_eq!(1, *execution_count.borrow()); // evaluate_input does not add it to the filter
|
||||||
|
|
||||||
|
assert!(fuzzer
|
||||||
|
.evaluate_filtered(&mut state, &mut executor, &mut manager, input.clone())
|
||||||
|
.is_ok());
|
||||||
|
assert_eq!(2, *execution_count.borrow()); // at to the filter
|
||||||
|
|
||||||
|
assert!(fuzzer
|
||||||
|
.evaluate_filtered(&mut state, &mut executor, &mut manager, input.clone())
|
||||||
|
.is_ok());
|
||||||
|
assert_eq!(2, *execution_count.borrow()); // the harness is not called
|
||||||
|
|
||||||
|
assert!(fuzzer
|
||||||
|
.evaluate_input(&mut state, &mut executor, &mut manager, input.clone())
|
||||||
|
.is_ok());
|
||||||
|
assert_eq!(3, *execution_count.borrow()); // evaluate_input ignores filters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
80
libafl/src/mutators/hash.rs
Normal file
80
libafl/src/mutators/hash.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
//! A wrapper around a [`Mutator`] that ensures an input really changed [`MutationResult::Mutated`]
|
||||||
|
//! by hashing pre- and post-mutation
|
||||||
|
use std::{borrow::Cow, hash::Hash};
|
||||||
|
|
||||||
|
use libafl_bolts::{generic_hash_std, Error, Named};
|
||||||
|
|
||||||
|
use super::{MutationResult, Mutator};
|
||||||
|
|
||||||
|
/// A wrapper around a [`Mutator`] that ensures an input really changed [`MutationResult::Mutated`]
|
||||||
|
/// by hashing pre- and post-mutation and comparing the values
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MutationChecker<M> {
|
||||||
|
inner: M,
|
||||||
|
name: Cow<'static, str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> MutationChecker<M>
|
||||||
|
where
|
||||||
|
M: Named,
|
||||||
|
{
|
||||||
|
/// Create a new [`MutationChecker`]
|
||||||
|
pub fn new(inner: M) -> Self {
|
||||||
|
let name = Cow::Owned(format!("MutationChecker<{}>", inner.name().clone()));
|
||||||
|
Self { inner, name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, I, S> Mutator<I, S> for MutationChecker<M>
|
||||||
|
where
|
||||||
|
I: Hash,
|
||||||
|
M: Mutator<I, S>,
|
||||||
|
{
|
||||||
|
fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
|
||||||
|
let before = generic_hash_std(input);
|
||||||
|
self.inner.mutate(state, input)?;
|
||||||
|
if before == generic_hash_std(input) {
|
||||||
|
Ok(MutationResult::Skipped)
|
||||||
|
} else {
|
||||||
|
Ok(MutationResult::Mutated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Named for MutationChecker<M> {
|
||||||
|
fn name(&self) -> &Cow<'static, str> {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
inputs::BytesInput,
|
||||||
|
mutators::{BytesSetMutator, MutationChecker, MutationResult, Mutator},
|
||||||
|
state::NopState,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_mutated() {
|
||||||
|
let mut state: NopState<BytesInput> = NopState::new();
|
||||||
|
let mut inner = BytesSetMutator::new();
|
||||||
|
|
||||||
|
let mut input = BytesInput::new(vec![0; 5]);
|
||||||
|
|
||||||
|
// nothing changed, yet `MutationResult::Mutated` was reported
|
||||||
|
assert_eq!(
|
||||||
|
MutationResult::Mutated,
|
||||||
|
inner.mutate(&mut state, &mut input).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(BytesInput::new(vec![0; 5]), input);
|
||||||
|
|
||||||
|
// now it is correctly reported as `MutationResult::Skipped`
|
||||||
|
let mut hash_mutator = MutationChecker::new(inner);
|
||||||
|
assert_eq!(
|
||||||
|
MutationResult::Skipped,
|
||||||
|
hash_mutator.mutate(&mut state, &mut input).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(BytesInput::new(vec![0; 5]), input);
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,11 @@ pub use mapping::*;
|
|||||||
pub mod tuneable;
|
pub mod tuneable;
|
||||||
pub use tuneable::*;
|
pub use tuneable::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub mod hash;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub use hash::*;
|
||||||
|
|
||||||
#[cfg(feature = "unicode")]
|
#[cfg(feature = "unicode")]
|
||||||
pub mod unicode;
|
pub mod unicode;
|
||||||
#[cfg(feature = "unicode")]
|
#[cfg(feature = "unicode")]
|
||||||
@ -84,12 +89,15 @@ impl From<i32> for MutationId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of a mutation.
|
/// Result of the mutation.
|
||||||
/// If the mutation got skipped, the target
|
///
|
||||||
/// will not be executed with the returned input.
|
/// [`MutationResult::Skipped`] does not necessarily mean that the input changed,
|
||||||
|
/// just that the mutator did something. For slow targets, consider using
|
||||||
|
/// a filtered fuzzer (see [`crate::fuzzer::StdFuzzer::with_input_filter`])
|
||||||
|
/// or wrapping your mutator in a [`hash::MutationChecker`].
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum MutationResult {
|
pub enum MutationResult {
|
||||||
/// The [`Mutator`] mutated this `Input`.
|
/// The [`Mutator`] executed on this `Input`. It may not guarantee that the input has actually been changed.
|
||||||
Mutated,
|
Mutated,
|
||||||
/// The [`Mutator`] did not mutate this `Input`. It was `Skipped`.
|
/// The [`Mutator`] did not mutate this `Input`. It was `Skipped`.
|
||||||
Skipped,
|
Skipped,
|
||||||
|
@ -417,8 +417,7 @@ where
|
|||||||
for (index, new_byte) in mutation {
|
for (index, new_byte) in mutation {
|
||||||
input_copy.bytes_mut()[index] = new_byte;
|
input_copy.bytes_mut()[index] = new_byte;
|
||||||
}
|
}
|
||||||
// Time is measured directly the `evaluate_input` function
|
fuzzer.evaluate_filtered(state, executor, manager, input_copy)?;
|
||||||
fuzzer.evaluate_input(state, executor, manager, input_copy)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -44,7 +44,7 @@ where
|
|||||||
manager: &mut EM,
|
manager: &mut EM,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let input = self.0.generate(state)?;
|
let input = self.0.generate(state)?;
|
||||||
fuzzer.evaluate_input(state, executor, manager, input)?;
|
fuzzer.evaluate_filtered(state, executor, manager, input)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,9 +277,9 @@ where
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time is measured directly the `evaluate_input` function
|
|
||||||
let (untransformed, post) = input.try_transform_into(state)?;
|
let (untransformed, post) = input.try_transform_into(state)?;
|
||||||
let (_, corpus_id) = fuzzer.evaluate_input(state, executor, manager, untransformed)?;
|
let (_, corpus_id) =
|
||||||
|
fuzzer.evaluate_filtered(state, executor, manager, untransformed)?;
|
||||||
|
|
||||||
start_timer!(state);
|
start_timer!(state);
|
||||||
self.mutator_mut().post_exec(state, corpus_id)?;
|
self.mutator_mut().post_exec(state, corpus_id)?;
|
||||||
@ -345,9 +345,9 @@ where
|
|||||||
|
|
||||||
let generated = self.mutator.multi_mutate(state, &input, None)?;
|
let generated = self.mutator.multi_mutate(state, &input, None)?;
|
||||||
for new_input in generated {
|
for new_input in generated {
|
||||||
// Time is measured directly the `evaluate_input` function
|
|
||||||
let (untransformed, post) = new_input.try_transform_into(state)?;
|
let (untransformed, post) = new_input.try_transform_into(state)?;
|
||||||
let (_, corpus_id) = fuzzer.evaluate_input(state, executor, manager, untransformed)?;
|
let (_, corpus_id) =
|
||||||
|
fuzzer.evaluate_filtered(state, executor, manager, untransformed)?;
|
||||||
self.mutator.multi_post_exec(state, corpus_id)?;
|
self.mutator.multi_post_exec(state, corpus_id)?;
|
||||||
post.post_exec(state, corpus_id)?;
|
post.post_exec(state, corpus_id)?;
|
||||||
}
|
}
|
||||||
|
@ -189,9 +189,9 @@ where
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time is measured directly the `evaluate_input` function
|
|
||||||
let (untransformed, post) = input.try_transform_into(state)?;
|
let (untransformed, post) = input.try_transform_into(state)?;
|
||||||
let (_, corpus_id) = fuzzer.evaluate_input(state, executor, manager, untransformed)?;
|
let (_, corpus_id) =
|
||||||
|
fuzzer.evaluate_filtered(state, executor, manager, untransformed)?;
|
||||||
|
|
||||||
start_timer!(state);
|
start_timer!(state);
|
||||||
self.mutator_mut().post_exec(state, corpus_id)?;
|
self.mutator_mut().post_exec(state, corpus_id)?;
|
||||||
|
@ -453,9 +453,8 @@ where
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time is measured directly the `evaluate_input` function
|
|
||||||
let (untransformed, post) = input.try_transform_into(state)?;
|
let (untransformed, post) = input.try_transform_into(state)?;
|
||||||
let (_, corpus_id) = fuzzer.evaluate_input(state, executor, manager, untransformed)?;
|
let (_, corpus_id) = fuzzer.evaluate_filtered(state, executor, manager, untransformed)?;
|
||||||
|
|
||||||
start_timer!(state);
|
start_timer!(state);
|
||||||
self.mutator_mut().post_exec(state, corpus_id)?;
|
self.mutator_mut().post_exec(state, corpus_id)?;
|
||||||
|
@ -2669,7 +2669,11 @@ where
|
|||||||
self.inner
|
self.inner
|
||||||
.llmp_clients
|
.llmp_clients
|
||||||
.binary_search_by_key(&client_id, |x| x.id)
|
.binary_search_by_key(&client_id, |x| x.id)
|
||||||
.expect("Fatal error, client ID {client_id} not found in llmp_clients.")
|
.unwrap_or_else(|_| {
|
||||||
|
panic!(
|
||||||
|
"Fatal error, client ID {client_id:?} not found in llmp_clients."
|
||||||
|
)
|
||||||
|
})
|
||||||
};
|
};
|
||||||
let client = &mut self.inner.llmp_clients[pos];
|
let client = &mut self.inner.llmp_clients[pos];
|
||||||
match client.recv()? {
|
match client.recv()? {
|
||||||
@ -2768,7 +2772,7 @@ where
|
|||||||
self.inner
|
self.inner
|
||||||
.llmp_clients
|
.llmp_clients
|
||||||
.binary_search_by_key(&client_id, |x| x.id)
|
.binary_search_by_key(&client_id, |x| x.id)
|
||||||
.expect("Fatal error, client ID {client_id} not found in llmp_clients.")
|
.unwrap_or_else(|_| panic!("Fatal error, client ID {client_id:?} not found in llmp_clients."))
|
||||||
};
|
};
|
||||||
|
|
||||||
let map = &mut self.inner.llmp_clients[pos].current_recv_shmem;
|
let map = &mut self.inner.llmp_clients[pos].current_recv_shmem;
|
||||||
|
@ -18,7 +18,7 @@ static SEED_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
|||||||
|
|
||||||
/// Return a pseudo-random seed. For `no_std` environments, a single deterministic sequence is used.
|
/// Return a pseudo-random seed. For `no_std` environments, a single deterministic sequence is used.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[expect(unreachable_code)]
|
#[allow(unreachable_code)] // cfg dependent
|
||||||
pub fn random_seed() -> u64 {
|
pub fn random_seed() -> u64 {
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
return random_seed_from_random_state();
|
return random_seed_from_random_state();
|
||||||
@ -365,7 +365,7 @@ impl Rand for Lehmer64Rand {
|
|||||||
fn set_seed(&mut self, mut seed: u64) {
|
fn set_seed(&mut self, mut seed: u64) {
|
||||||
let hi = splitmix64(&mut seed);
|
let hi = splitmix64(&mut seed);
|
||||||
let lo = splitmix64(&mut seed) | 1;
|
let lo = splitmix64(&mut seed) | 1;
|
||||||
self.s = u128::from(hi) << 64 | u128::from(lo);
|
self.s = (u128::from(hi) << 64) | u128::from(lo);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -624,12 +624,9 @@ where
|
|||||||
/// Is needed on top.
|
/// Is needed on top.
|
||||||
#[cfg(all(unix, feature = "std", not(target_os = "haiku")))]
|
#[cfg(all(unix, feature = "std", not(target_os = "haiku")))]
|
||||||
pub mod unix_shmem {
|
pub mod unix_shmem {
|
||||||
/// Mmap [`ShMemProvider`] for Unix
|
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
pub use default::MmapShMemProvider;
|
|
||||||
/// Mmap [`ShMem`] for Unix
|
/// Mmap [`ShMem`] for Unix
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
pub use default::{MmapShMem, MAX_MMAP_FILENAME_LEN};
|
pub use default::{MmapShMem, MmapShMemProvider, MAX_MMAP_FILENAME_LEN};
|
||||||
|
|
||||||
#[cfg(doc)]
|
#[cfg(doc)]
|
||||||
use crate::shmem::{ShMem, ShMemProvider};
|
use crate::shmem::{ShMem, ShMemProvider};
|
||||||
@ -669,7 +666,7 @@ pub mod unix_shmem {
|
|||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The size of the buffer of the filename of mmap mapped memory regions
|
/// The max number of bytes used when generating names for [`MmapShMem`]s.
|
||||||
pub const MAX_MMAP_FILENAME_LEN: usize = 20;
|
pub const MAX_MMAP_FILENAME_LEN: usize = 20;
|
||||||
|
|
||||||
/// Mmap-based The sharedmap impl for unix using [`shm_open`] and [`mmap`].
|
/// Mmap-based The sharedmap impl for unix using [`shm_open`] and [`mmap`].
|
||||||
|
@ -1532,8 +1532,7 @@ mod addr2line_legacy {
|
|||||||
/// # Safety
|
/// # Safety
|
||||||
/// Will access the global [`FullBacktraceCollector`].
|
/// Will access the global [`FullBacktraceCollector`].
|
||||||
/// Calling this function concurrently might be racey.
|
/// Calling this function concurrently might be racey.
|
||||||
#[expect(clippy::unnecessary_cast)]
|
#[expect(clippy::too_many_lines, clippy::unnecessary_cast)]
|
||||||
#[expect(clippy::too_many_lines)]
|
|
||||||
pub unsafe fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: &AsanError) {
|
pub unsafe fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: &AsanError) {
|
||||||
let mut regions = HashMap::new();
|
let mut regions = HashMap::new();
|
||||||
for region in qemu.mappings() {
|
for region in qemu.mappings() {
|
||||||
|
@ -780,8 +780,7 @@ where
|
|||||||
SyscallHookResult::new(None)
|
SyscallHookResult::new(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::too_many_arguments)]
|
#[expect(non_upper_case_globals, clippy::too_many_arguments)]
|
||||||
#[expect(non_upper_case_globals)]
|
|
||||||
pub fn trace_mmap_snapshot<ET, S>(
|
pub fn trace_mmap_snapshot<ET, S>(
|
||||||
emulator_modules: &mut EmulatorModules<ET, S>,
|
emulator_modules: &mut EmulatorModules<ET, S>,
|
||||||
_state: Option<&mut S>,
|
_state: Option<&mut S>,
|
||||||
|
@ -26,8 +26,7 @@ extern "C" {
|
|||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// Calls the libfuzzer-style init function which is native code.
|
/// Calls the libfuzzer-style init function which is native code.
|
||||||
#[expect(clippy::similar_names)]
|
#[expect(clippy::must_use_candidate, clippy::similar_names)] // nobody uses that return code...
|
||||||
#[expect(clippy::must_use_candidate)] // nobody uses that return code...
|
|
||||||
pub unsafe fn libfuzzer_initialize(args: &[String]) -> i32 {
|
pub unsafe fn libfuzzer_initialize(args: &[String]) -> i32 {
|
||||||
let args: Vec<String> = args.iter().map(|x| x.clone() + "\0").collect();
|
let args: Vec<String> = args.iter().map(|x| x.clone() + "\0").collect();
|
||||||
let argv: Vec<*const u8> = args.iter().map(|x| x.as_bytes().as_ptr()).collect();
|
let argv: Vec<*const u8> = args.iter().map(|x| x.as_bytes().as_ptr()).collect();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user