Multipart Input support (#1617)
* initial commit: multipart * document + wrap up baby fuzzer * oops * core * add from method, option to iter * improve example; use minmap; fix initial_mut * bindings * clippy, again * moar clippy * fmt * drop rand dep because we don't need it, actually * docfix * ok actually fix docs pls
This commit is contained in:
parent
75fcd47044
commit
99fd69acdc
1
fuzzers/baby_fuzzer_multi/.gitignore
vendored
Normal file
1
fuzzers/baby_fuzzer_multi/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
libpng-*
|
24
fuzzers/baby_fuzzer_multi/Cargo.toml
Normal file
24
fuzzers/baby_fuzzer_multi/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "baby_fuzzer_multi"
|
||||
version = "0.10.0"
|
||||
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>", "Addison Crump <me@addisoncrump.info>"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
tui = []
|
||||
std = []
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../../libafl/", features = ["multipart_inputs"] }
|
||||
libafl_bolts = { path = "../../libafl_bolts/" }
|
10
fuzzers/baby_fuzzer_multi/README.md
Normal file
10
fuzzers/baby_fuzzer_multi/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Baby fuzzer multi
|
||||
|
||||
This is a minimalistic example about how to create a libafl based fuzzer for targets with multipart inputs.
|
||||
|
||||
It runs on a single core until a crash occurs and then exits.
|
||||
|
||||
The tested program is a simple Rust function without any instrumentation.
|
||||
For real fuzzing, you will want to add some sort to add coverage or other feedback.
|
||||
|
||||
You can run this example using `cargo run`, and you can enable the TUI feature by running `cargo run --features tui`.
|
164
fuzzers/baby_fuzzer_multi/src/main.rs
Normal file
164
fuzzers/baby_fuzzer_multi/src/main.rs
Normal file
@ -0,0 +1,164 @@
|
||||
#[cfg(windows)]
|
||||
use std::ptr::write_volatile;
|
||||
use std::{path::PathBuf, ptr::write};
|
||||
|
||||
#[cfg(feature = "tui")]
|
||||
use libafl::monitors::tui::{ui::TuiUI, TuiMonitor};
|
||||
#[cfg(not(feature = "tui"))]
|
||||
use libafl::monitors::SimpleMonitor;
|
||||
use libafl::{
|
||||
corpus::{InMemoryCorpus, OnDiskCorpus},
|
||||
events::SimpleEventManager,
|
||||
executors::{inprocess::InProcessExecutor, ExitKind},
|
||||
feedback_or_fast,
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback, MinMapFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
inputs::{BytesInput, HasTargetBytes, MultipartInput},
|
||||
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
||||
observers::StdMapObserver,
|
||||
schedulers::QueueScheduler,
|
||||
stages::mutational::StdMutationalStage,
|
||||
state::StdState,
|
||||
Evaluator,
|
||||
};
|
||||
use libafl_bolts::{current_nanos, rands::StdRand, tuples::tuple_list, AsSlice};
|
||||
|
||||
/// Coverage map with explicit assignments due to the lack of instrumentation
|
||||
static mut SIGNALS: [u8; 128] = [0; 128];
|
||||
static mut SIGNALS_PTR: *mut u8 = unsafe { SIGNALS.as_mut_ptr() };
|
||||
|
||||
/// "Coverage" map for count, just to help things along
|
||||
static mut LAST_COUNT: [usize; 1] = [usize::MAX];
|
||||
static mut LAST_COUNT_PTR: *mut usize = unsafe { LAST_COUNT.as_mut_ptr() };
|
||||
|
||||
/// Assign a signal to the signals map
|
||||
fn signals_set(idx: usize) {
|
||||
unsafe { write(SIGNALS_PTR.add(idx), 1) };
|
||||
}
|
||||
|
||||
/// Assign a count to the count "map"
|
||||
fn count_set(count: usize) {
|
||||
unsafe { LAST_COUNT[0] = count };
|
||||
}
|
||||
|
||||
#[allow(clippy::similar_names, clippy::manual_assert)]
|
||||
pub fn main() {
|
||||
// The closure that we want to fuzz
|
||||
let mut harness = |input: &MultipartInput<BytesInput>| {
|
||||
let mut count = input.parts().len();
|
||||
for (i, input) in input.parts().iter().enumerate() {
|
||||
let target = input.target_bytes();
|
||||
let buf = target.as_slice();
|
||||
signals_set(i * 8);
|
||||
if !buf.is_empty() && buf[0] == b'a' {
|
||||
signals_set(1 + i * 8);
|
||||
if buf.len() > 1 && buf[1] == b'b' {
|
||||
signals_set(2 + i * 8);
|
||||
if buf.len() > 2 && buf[2] == b'c' {
|
||||
count -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if count == 0 {
|
||||
#[cfg(unix)]
|
||||
panic!("Artificial bug triggered =)");
|
||||
|
||||
// panic!() raises a STATUS_STACK_BUFFER_OVERRUN exception which cannot be caught by the exception handler.
|
||||
// Here we make it raise STATUS_ACCESS_VIOLATION instead.
|
||||
// Extending the windows exception handler is a TODO. Maybe we can refer to what winafl code does.
|
||||
// https://github.com/googleprojectzero/winafl/blob/ea5f6b85572980bb2cf636910f622f36906940aa/winafl.c#L728
|
||||
#[cfg(windows)]
|
||||
unsafe {
|
||||
write_volatile(0 as *mut u32, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// without this, artificial bug is not found
|
||||
// maybe interesting to try to auto-derive this, researchers! :)
|
||||
count_set(count);
|
||||
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
// Create an observation channel using the signals map
|
||||
let signals_observer =
|
||||
unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS.len()) };
|
||||
let mut count_observer =
|
||||
unsafe { StdMapObserver::from_mut_ptr("count", LAST_COUNT_PTR, LAST_COUNT.len()) };
|
||||
*count_observer.initial_mut() = usize::MAX; // we are minimising!
|
||||
|
||||
// Feedback to rate the interestingness of an input
|
||||
let signals_feedback = MaxMapFeedback::new(&signals_observer);
|
||||
let count_feedback = MinMapFeedback::new(&count_observer);
|
||||
|
||||
let mut feedback = feedback_or_fast!(count_feedback, signals_feedback);
|
||||
|
||||
// A feedback to choose if an input is a solution or not
|
||||
let mut objective = CrashFeedback::new();
|
||||
|
||||
// create a State from scratch
|
||||
let mut state = StdState::new(
|
||||
// RNG
|
||||
StdRand::with_seed(current_nanos()),
|
||||
// Corpus that will be evolved, we keep it in memory for performance
|
||||
InMemoryCorpus::new(),
|
||||
// Corpus in which we store solutions (crashes in this example),
|
||||
// on disk so the user can get them after stopping the fuzzer
|
||||
OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(),
|
||||
// States of the feedbacks.
|
||||
// The feedbacks can report the data that should persist in the State.
|
||||
&mut feedback,
|
||||
// Same for objective feedbacks
|
||||
&mut objective,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// The Monitor trait define how the fuzzer stats are displayed to the user
|
||||
#[cfg(not(feature = "tui"))]
|
||||
let mon = SimpleMonitor::new(|s| println!("{s}"));
|
||||
#[cfg(feature = "tui")]
|
||||
let ui = TuiUI::with_version(String::from("Baby Fuzzer"), String::from("0.0.1"), false);
|
||||
#[cfg(feature = "tui")]
|
||||
let mon = TuiMonitor::new(ui);
|
||||
|
||||
// The event manager handle the various events generated during the fuzzing loop
|
||||
// such as the notification of the addition of a new item to the corpus
|
||||
let mut mgr = SimpleEventManager::new(mon);
|
||||
|
||||
// A queue policy to get testcasess from the corpus
|
||||
let scheduler = QueueScheduler::new();
|
||||
|
||||
// A fuzzer with feedbacks and a corpus scheduler
|
||||
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||
|
||||
// Create the executor for an in-process function with just one observer
|
||||
let mut executor = InProcessExecutor::new(
|
||||
&mut harness,
|
||||
tuple_list!(signals_observer, count_observer),
|
||||
&mut fuzzer,
|
||||
&mut state,
|
||||
&mut mgr,
|
||||
)
|
||||
.expect("Failed to create the Executor");
|
||||
|
||||
// a generator here is not generalisable
|
||||
let initial = MultipartInput::from([
|
||||
("part", BytesInput::new(vec![b'h', b'e', b'l', b'l', b'o'])),
|
||||
("part", BytesInput::new(vec![b'h', b'e', b'l', b'l', b'o'])),
|
||||
("part", BytesInput::new(vec![b'h', b'e', b'l', b'l', b'o'])),
|
||||
("part", BytesInput::new(vec![b'h', b'e', b'l', b'l', b'o'])),
|
||||
]);
|
||||
|
||||
fuzzer
|
||||
.evaluate_input(&mut state, &mut executor, &mut mgr, initial)
|
||||
.unwrap();
|
||||
|
||||
// Setup a mutational stage with a basic bytes mutator
|
||||
let mutator = StdScheduledMutator::new(havoc_mutations());
|
||||
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
|
||||
|
||||
fuzzer
|
||||
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
|
||||
.expect("Error in the fuzzing loop");
|
||||
}
|
@ -85,6 +85,8 @@ tui_monitor = ["ratatui", "crossterm"]
|
||||
## Enables `StringClassificationStage` and associated mutators, which allow for mutations which preserve the Unicode property data
|
||||
unicode = ["libafl_bolts/alloc", "ahash/std", "serde/rc", "bitvec", "reqwest", "zip"]
|
||||
|
||||
## Enable multi-part input formats and mutators
|
||||
multipart_inputs = ["arrayvec", "rand_trait"]
|
||||
|
||||
#! ## LibAFL-Bolts Features
|
||||
|
||||
@ -186,6 +188,8 @@ libcasr = { version = "2.7", optional = true }
|
||||
|
||||
bitvec = { version = "1.0", optional = true, features = ["serde"] } # used for string range storage
|
||||
|
||||
arrayvec = { version = "0.7.4", optional = true, default-features = false } # used for fixed-len collects
|
||||
|
||||
# optional-dev deps (change when target.'cfg(accessible(::std))'.test-dependencies will be stable)
|
||||
serial_test = { version = "2", optional = true, default-features = false, features = ["logging"] }
|
||||
|
||||
|
@ -12,8 +12,14 @@ pub use gramatron::*;
|
||||
pub mod generalized;
|
||||
pub use generalized::*;
|
||||
|
||||
#[cfg(feature = "multipart_inputs")]
|
||||
pub mod multi;
|
||||
#[cfg(feature = "multipart_inputs")]
|
||||
pub use multi::*;
|
||||
|
||||
#[cfg(feature = "nautilus")]
|
||||
pub mod nautilus;
|
||||
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
string::{String, ToString},
|
||||
|
165
libafl/src/inputs/multi.rs
Normal file
165
libafl/src/inputs/multi.rs
Normal file
@ -0,0 +1,165 @@
|
||||
//! Definitions for inputs which have multiple distinct subcomponents.
|
||||
//!
|
||||
//! Unfortunately, since both [`serde::de::Deserialize`] and [`Clone`] require [`Sized`], it is not
|
||||
//! possible to dynamically define a single input with dynamic typing. As such, [`MultipartInput`]
|
||||
//! requires that each subcomponent be the same subtype.
|
||||
|
||||
use alloc::{
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::inputs::Input;
|
||||
|
||||
/// An input composed of multiple parts. Use in situations where subcomponents are not necessarily
|
||||
/// related, or represent distinct parts of the input.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct MultipartInput<I> {
|
||||
parts: Vec<I>,
|
||||
names: Vec<String>,
|
||||
}
|
||||
|
||||
impl<I> Default for MultipartInput<I> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> MultipartInput<I> {
|
||||
/// Create a new multipart input.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
parts: Vec::new(),
|
||||
names: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn idxs_to_skips(idxs: &mut [usize]) {
|
||||
for following in (1..idxs.len()).rev() {
|
||||
let first = idxs[following - 1];
|
||||
let second = idxs[following];
|
||||
|
||||
idxs[following] = second
|
||||
.checked_sub(first)
|
||||
.expect("idxs was not sorted")
|
||||
.checked_sub(1)
|
||||
.expect("idxs had duplicate elements");
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the individual parts of this input.
|
||||
#[must_use]
|
||||
pub fn parts(&self) -> &[I] {
|
||||
&self.parts
|
||||
}
|
||||
|
||||
/// Access multiple parts mutably.
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// Panics if idxs is not sorted, has duplicate elements, or any entry is out of bounds.
|
||||
#[must_use]
|
||||
pub fn parts_mut<const N: usize>(&mut self, mut idxs: [usize; N]) -> [&mut I; N] {
|
||||
Self::idxs_to_skips(&mut idxs);
|
||||
|
||||
let mut parts = self.parts.iter_mut();
|
||||
if let Ok(arr) = idxs
|
||||
.into_iter()
|
||||
.map(|i| parts.nth(i).expect("idx had an out of bounds entry"))
|
||||
.collect::<ArrayVec<_, N>>()
|
||||
.into_inner()
|
||||
{
|
||||
arr
|
||||
} else {
|
||||
// avoid Debug trait requirement for expect/unwrap
|
||||
panic!("arrayvec collection failed somehow")
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a specific part of this input by index.
|
||||
pub fn part_mut(&mut self, idx: usize) -> Option<&mut I> {
|
||||
self.parts.get_mut(idx)
|
||||
}
|
||||
|
||||
/// Get the names associated with the subparts of this input. Used to distinguish between the
|
||||
/// input components in the case where some parts may or may not be present, or in different
|
||||
/// orders.
|
||||
#[must_use]
|
||||
pub fn names(&self) -> &[String] {
|
||||
&self.names
|
||||
}
|
||||
|
||||
/// Gets a reference to each part with the provided name.
|
||||
pub fn parts_by_name<'a, 'b>(
|
||||
&'b self,
|
||||
name: &'a str,
|
||||
) -> impl Iterator<Item = (usize, &'b I)> + 'a
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
self.names()
|
||||
.iter()
|
||||
.zip(&self.parts)
|
||||
.enumerate()
|
||||
.filter_map(move |(i, (s, item))| (s == name).then_some((i, item)))
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to each part with the provided name.
|
||||
pub fn parts_by_name_mut<'a, 'b>(
|
||||
&'b mut self,
|
||||
name: &'a str,
|
||||
) -> impl Iterator<Item = (usize, &'b mut I)> + 'a
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
self.names
|
||||
.iter()
|
||||
.zip(&mut self.parts)
|
||||
.enumerate()
|
||||
.filter_map(move |(i, (s, item))| (s == name).then_some((i, item)))
|
||||
}
|
||||
|
||||
/// Adds a part to this input, potentially with the same name as an existing part.
|
||||
pub fn add_part(&mut self, name: String, part: I) {
|
||||
self.parts.push(part);
|
||||
self.names.push(name);
|
||||
}
|
||||
|
||||
/// Iterate over the parts of this input; no order is specified.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &I)> {
|
||||
self.names.iter().map(String::as_ref).zip(self.parts())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, It, S> From<It> for MultipartInput<I>
|
||||
where
|
||||
It: IntoIterator<Item = (S, I)>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
fn from(parts: It) -> Self {
|
||||
let mut input = MultipartInput::new();
|
||||
for (name, part) in parts {
|
||||
input.add_part(name.as_ref().to_string(), part);
|
||||
}
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Input for MultipartInput<I>
|
||||
where
|
||||
I: Input,
|
||||
{
|
||||
fn generate_name(&self, idx: usize) -> String {
|
||||
self.names
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(self.parts.iter().map(|i| i.generate_name(idx)))
|
||||
.map(|(name, generated)| format!("{name}-{generated}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",")
|
||||
}
|
||||
}
|
@ -25,8 +25,14 @@ pub mod string;
|
||||
#[cfg(feature = "unicode")]
|
||||
pub use string::*;
|
||||
|
||||
#[cfg(feature = "multipart_inputs")]
|
||||
pub mod multi;
|
||||
#[cfg(feature = "multipart_inputs")]
|
||||
pub use multi::*;
|
||||
|
||||
#[cfg(feature = "nautilus")]
|
||||
pub mod nautilus;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use libafl_bolts::{tuples::HasConstLen, Named};
|
||||
|
330
libafl/src/mutators/multi.rs
Normal file
330
libafl/src/mutators/multi.rs
Normal file
@ -0,0 +1,330 @@
|
||||
//! Mutator definitions for [`MultipartInput`]s. See [`crate::inputs::multi`] for details.
|
||||
|
||||
use core::cmp::{min, Ordering};
|
||||
|
||||
use libafl_bolts::{rands::Rand, Error};
|
||||
|
||||
use crate::{
|
||||
corpus::{Corpus, CorpusId},
|
||||
impl_default_multipart,
|
||||
inputs::{multi::MultipartInput, HasBytesVec, Input},
|
||||
mutators::{
|
||||
mutations::{
|
||||
rand_range, BitFlipMutator, ByteAddMutator, ByteDecMutator, ByteFlipMutator,
|
||||
ByteIncMutator, ByteInterestingMutator, ByteNegMutator, ByteRandMutator,
|
||||
BytesCopyMutator, BytesDeleteMutator, BytesExpandMutator, BytesInsertCopyMutator,
|
||||
BytesInsertMutator, BytesRandInsertMutator, BytesRandSetMutator, BytesSetMutator,
|
||||
BytesSwapMutator, CrossoverInsertMutator, CrossoverReplaceMutator, DwordAddMutator,
|
||||
DwordInterestingMutator, QwordAddMutator, WordAddMutator, WordInterestingMutator,
|
||||
},
|
||||
token_mutations::{I2SRandReplace, TokenInsert, TokenReplace},
|
||||
MutationResult, Mutator,
|
||||
},
|
||||
random_corpus_id,
|
||||
state::{HasCorpus, HasMaxSize, HasRand},
|
||||
};
|
||||
|
||||
/// Marker trait for if the default multipart input mutator implementation is appropriate.
|
||||
///
|
||||
/// You should implement this type for your mutator if you just want a random part of the input to
|
||||
/// be selected and mutated. Use [`impl_default_multipart`] to implement this marker trait for many
|
||||
/// at once.
|
||||
pub trait DefaultMultipartMutator {}
|
||||
|
||||
impl<I, M, S> Mutator<MultipartInput<I>, S> for M
|
||||
where
|
||||
M: DefaultMultipartMutator + Mutator<I, S>,
|
||||
S: HasRand,
|
||||
{
|
||||
fn mutate(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
input: &mut MultipartInput<I>,
|
||||
stage_idx: i32,
|
||||
) -> Result<MutationResult, Error> {
|
||||
if input.parts().is_empty() {
|
||||
Ok(MutationResult::Skipped)
|
||||
} else {
|
||||
let selected = state.rand_mut().below(input.parts().len() as u64) as usize;
|
||||
let mutated = input.part_mut(selected).unwrap();
|
||||
self.mutate(state, mutated, stage_idx)
|
||||
}
|
||||
}
|
||||
|
||||
fn post_exec(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
stage_idx: i32,
|
||||
corpus_idx: Option<CorpusId>,
|
||||
) -> Result<(), Error> {
|
||||
M::post_exec(self, state, stage_idx, corpus_idx)
|
||||
}
|
||||
}
|
||||
|
||||
mod macros {
|
||||
/// Implements the marker trait [`super::DefaultMultipartMutator`] for one to many types, e.g.:
|
||||
///
|
||||
/// ```rs
|
||||
/// impl_default_multipart!(
|
||||
/// // --- havoc ---
|
||||
/// BitFlipMutator,
|
||||
/// ByteAddMutator,
|
||||
/// ByteDecMutator,
|
||||
/// ByteFlipMutator,
|
||||
/// ByteIncMutator,
|
||||
/// ...
|
||||
/// );
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! impl_default_multipart {
|
||||
($mutator: ty, $($mutators: ty),+$(,)?) => {
|
||||
impl $crate::mutators::multi::DefaultMultipartMutator for $mutator {}
|
||||
impl_default_multipart!($($mutators),+);
|
||||
};
|
||||
|
||||
($mutator: ty) => {
|
||||
impl $crate::mutators::multi::DefaultMultipartMutator for $mutator {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl_default_multipart!(
|
||||
// --- havoc ---
|
||||
BitFlipMutator,
|
||||
ByteAddMutator,
|
||||
ByteDecMutator,
|
||||
ByteFlipMutator,
|
||||
ByteIncMutator,
|
||||
ByteInterestingMutator,
|
||||
ByteNegMutator,
|
||||
ByteRandMutator,
|
||||
BytesCopyMutator,
|
||||
BytesDeleteMutator,
|
||||
BytesExpandMutator,
|
||||
BytesInsertCopyMutator,
|
||||
BytesInsertMutator,
|
||||
BytesRandInsertMutator,
|
||||
BytesRandSetMutator,
|
||||
BytesSetMutator,
|
||||
BytesSwapMutator,
|
||||
// crossover has a custom implementation below
|
||||
DwordAddMutator,
|
||||
DwordInterestingMutator,
|
||||
QwordAddMutator,
|
||||
WordAddMutator,
|
||||
WordInterestingMutator,
|
||||
// --- token ---
|
||||
TokenInsert,
|
||||
TokenReplace,
|
||||
// --- i2s ---
|
||||
I2SRandReplace,
|
||||
);
|
||||
|
||||
impl<I, S> Mutator<MultipartInput<I>, S> for CrossoverInsertMutator<I>
|
||||
where
|
||||
S: HasCorpus<Input = MultipartInput<I>> + HasMaxSize + HasRand,
|
||||
I: Input + HasBytesVec,
|
||||
{
|
||||
fn mutate(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
input: &mut MultipartInput<I>,
|
||||
_stage_idx: i32,
|
||||
) -> Result<MutationResult, Error> {
|
||||
// we can eat the slight bias; number of parts will be small
|
||||
let name_choice = state.rand_mut().next() as usize;
|
||||
let part_choice = state.rand_mut().next() as usize;
|
||||
|
||||
// We special-case crossover with self
|
||||
let idx = random_corpus_id!(state.corpus(), state.rand_mut());
|
||||
if let Some(cur) = state.corpus().current() {
|
||||
if idx == *cur {
|
||||
let choice = name_choice % input.names().len();
|
||||
let name = input.names()[choice].clone();
|
||||
|
||||
let other_size = input.parts()[choice].bytes().len();
|
||||
if other_size < 2 {
|
||||
return Ok(MutationResult::Skipped);
|
||||
}
|
||||
|
||||
let parts = input.parts_by_name(&name).count() - 1;
|
||||
|
||||
if parts == 0 {
|
||||
return Ok(MutationResult::Skipped);
|
||||
}
|
||||
|
||||
let maybe_size = input
|
||||
.parts_by_name(&name)
|
||||
.filter(|&(p, _)| p != choice)
|
||||
.nth(part_choice % parts)
|
||||
.map(|(idx, part)| (idx, part.bytes().len()));
|
||||
|
||||
if let Some((part_idx, size)) = maybe_size {
|
||||
let target = state.rand_mut().below(size as u64) as usize;
|
||||
let range = rand_range(state, other_size, min(other_size, size - target));
|
||||
|
||||
let [part, chosen] = match part_idx.cmp(&choice) {
|
||||
Ordering::Less => input.parts_mut([part_idx, choice]),
|
||||
Ordering::Equal => {
|
||||
unreachable!("choice should never equal the part idx!")
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let [chosen, part] = input.parts_mut([choice, part_idx]);
|
||||
[part, chosen]
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(Self::crossover_insert(part, size, target, range, chosen));
|
||||
}
|
||||
|
||||
return Ok(MutationResult::Skipped);
|
||||
}
|
||||
}
|
||||
|
||||
let mut other_testcase = state.corpus().get(idx)?.borrow_mut();
|
||||
let other = other_testcase.load_input(state.corpus())?;
|
||||
|
||||
let choice = name_choice % other.names().len();
|
||||
let name = &other.names()[choice];
|
||||
|
||||
let other_size = other.parts()[choice].bytes().len();
|
||||
if other_size < 2 {
|
||||
return Ok(MutationResult::Skipped);
|
||||
}
|
||||
|
||||
let parts = input.parts_by_name(name).count();
|
||||
|
||||
if parts > 0 {
|
||||
let (_, part) = input
|
||||
.parts_by_name_mut(name)
|
||||
.nth(part_choice % parts)
|
||||
.unwrap();
|
||||
drop(other_testcase);
|
||||
let size = part.bytes().len();
|
||||
|
||||
let target = state.rand_mut().below(size as u64) as usize;
|
||||
let range = rand_range(state, other_size, min(other_size, size - target));
|
||||
|
||||
let other_testcase = state.corpus().get(idx)?.borrow_mut();
|
||||
// No need to load the input again, it'll still be cached.
|
||||
let other = other_testcase.input().as_ref().unwrap();
|
||||
|
||||
Ok(Self::crossover_insert(
|
||||
part,
|
||||
size,
|
||||
target,
|
||||
range,
|
||||
&other.parts()[choice],
|
||||
))
|
||||
} else {
|
||||
// just add it!
|
||||
input.add_part(name.clone(), other.parts()[choice].clone());
|
||||
|
||||
Ok(MutationResult::Mutated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S> Mutator<MultipartInput<I>, S> for CrossoverReplaceMutator<I>
|
||||
where
|
||||
S: HasCorpus<Input = MultipartInput<I>> + HasMaxSize + HasRand,
|
||||
I: Input + HasBytesVec,
|
||||
{
|
||||
fn mutate(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
input: &mut MultipartInput<I>,
|
||||
_stage_idx: i32,
|
||||
) -> Result<MutationResult, Error> {
|
||||
// we can eat the slight bias; number of parts will be small
|
||||
let name_choice = state.rand_mut().next() as usize;
|
||||
let part_choice = state.rand_mut().next() as usize;
|
||||
|
||||
// We special-case crossover with self
|
||||
let idx = random_corpus_id!(state.corpus(), state.rand_mut());
|
||||
if let Some(cur) = state.corpus().current() {
|
||||
if idx == *cur {
|
||||
let choice = name_choice % input.names().len();
|
||||
let name = input.names()[choice].clone();
|
||||
|
||||
let other_size = input.parts()[choice].bytes().len();
|
||||
if other_size < 2 {
|
||||
return Ok(MutationResult::Skipped);
|
||||
}
|
||||
|
||||
let parts = input.parts_by_name(&name).count() - 1;
|
||||
|
||||
if parts == 0 {
|
||||
return Ok(MutationResult::Skipped);
|
||||
}
|
||||
|
||||
let maybe_size = input
|
||||
.parts_by_name(&name)
|
||||
.filter(|&(p, _)| p != choice)
|
||||
.nth(part_choice % parts)
|
||||
.map(|(idx, part)| (idx, part.bytes().len()));
|
||||
|
||||
if let Some((part_idx, size)) = maybe_size {
|
||||
let target = state.rand_mut().below(size as u64) as usize;
|
||||
let range = rand_range(state, other_size, min(other_size, size - target));
|
||||
|
||||
let [part, chosen] = match part_idx.cmp(&choice) {
|
||||
Ordering::Less => input.parts_mut([part_idx, choice]),
|
||||
Ordering::Equal => {
|
||||
unreachable!("choice should never equal the part idx!")
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let [chosen, part] = input.parts_mut([choice, part_idx]);
|
||||
[part, chosen]
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(Self::crossover_replace(part, target, range, chosen));
|
||||
}
|
||||
|
||||
return Ok(MutationResult::Skipped);
|
||||
}
|
||||
}
|
||||
|
||||
let mut other_testcase = state.corpus().get(idx)?.borrow_mut();
|
||||
let other = other_testcase.load_input(state.corpus())?;
|
||||
|
||||
let choice = name_choice % other.names().len();
|
||||
let name = &other.names()[choice];
|
||||
|
||||
let other_size = other.parts()[choice].bytes().len();
|
||||
if other_size < 2 {
|
||||
return Ok(MutationResult::Skipped);
|
||||
}
|
||||
|
||||
let parts = input.parts_by_name(name).count();
|
||||
|
||||
if parts > 0 {
|
||||
let (_, part) = input
|
||||
.parts_by_name_mut(name)
|
||||
.nth(part_choice % parts)
|
||||
.unwrap();
|
||||
drop(other_testcase);
|
||||
let size = part.bytes().len();
|
||||
|
||||
let target = state.rand_mut().below(size as u64) as usize;
|
||||
let range = rand_range(state, other_size, min(other_size, size - target));
|
||||
|
||||
let other_testcase = state.corpus().get(idx)?.borrow_mut();
|
||||
// No need to load the input again, it'll still be cached.
|
||||
let other = other_testcase.input().as_ref().unwrap();
|
||||
|
||||
Ok(Self::crossover_replace(
|
||||
part,
|
||||
target,
|
||||
range,
|
||||
&other.parts()[choice],
|
||||
))
|
||||
} else {
|
||||
// just add it!
|
||||
input.add_part(name.clone(), other.parts()[choice].clone());
|
||||
|
||||
Ok(MutationResult::Mutated)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
//! A wide variety of mutations used during fuzzing.
|
||||
|
||||
use alloc::{borrow::ToOwned, vec::Vec};
|
||||
use core::{cmp::min, mem::size_of, ops::Range};
|
||||
use core::{cmp::min, marker::PhantomData, mem::size_of, ops::Range};
|
||||
|
||||
use libafl_bolts::{rands::Rand, Named};
|
||||
|
||||
use crate::{
|
||||
corpus::Corpus,
|
||||
inputs::HasBytesVec,
|
||||
inputs::{HasBytesVec, Input},
|
||||
mutators::{MutationResult, Mutator},
|
||||
random_corpus_id,
|
||||
state::{HasCorpus, HasMaxSize, HasRand},
|
||||
@ -1085,12 +1085,45 @@ impl BytesSwapMutator {
|
||||
|
||||
/// Crossover insert mutation for inputs with a bytes vector
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CrossoverInsertMutator;
|
||||
pub struct CrossoverInsertMutator<I> {
|
||||
phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<S> Mutator<S::Input, S> for CrossoverInsertMutator
|
||||
impl<I: HasBytesVec> CrossoverInsertMutator<I> {
|
||||
pub(crate) fn crossover_insert(
|
||||
input: &mut I,
|
||||
size: usize,
|
||||
target: usize,
|
||||
range: Range<usize>,
|
||||
other: &I,
|
||||
) -> MutationResult {
|
||||
input.bytes_mut().resize(size + range.len(), 0);
|
||||
unsafe {
|
||||
buffer_self_copy(
|
||||
input.bytes_mut(),
|
||||
target,
|
||||
target + range.len(),
|
||||
size - target,
|
||||
);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
buffer_copy(
|
||||
input.bytes_mut(),
|
||||
other.bytes(),
|
||||
range.start,
|
||||
target,
|
||||
range.len(),
|
||||
);
|
||||
}
|
||||
MutationResult::Mutated
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S> Mutator<I, S> for CrossoverInsertMutator<I>
|
||||
where
|
||||
S: HasCorpus + HasRand + HasMaxSize,
|
||||
S::Input: HasBytesVec,
|
||||
S: HasCorpus<Input = I> + HasRand + HasMaxSize,
|
||||
I: Input + HasBytesVec,
|
||||
{
|
||||
fn mutate(
|
||||
&mut self,
|
||||
@ -1125,20 +1158,43 @@ where
|
||||
let range = rand_range(state, other_size, min(other_size, max_size - size));
|
||||
let target = state.rand_mut().below(size as u64) as usize;
|
||||
|
||||
input.bytes_mut().resize(size + range.len(), 0);
|
||||
unsafe {
|
||||
buffer_self_copy(
|
||||
input.bytes_mut(),
|
||||
target,
|
||||
target + range.len(),
|
||||
size - target,
|
||||
);
|
||||
}
|
||||
|
||||
let other_testcase = state.corpus().get(idx)?.borrow_mut();
|
||||
// No need to load the input again, it'll still be cached.
|
||||
let other = other_testcase.input().as_ref().unwrap();
|
||||
|
||||
Ok(Self::crossover_insert(input, size, target, range, other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Named for CrossoverInsertMutator<I> {
|
||||
fn name(&self) -> &str {
|
||||
"CrossoverInsertMutator"
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> CrossoverInsertMutator<I> {
|
||||
/// Creates a new [`CrossoverInsertMutator`].
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Crossover replace mutation for inputs with a bytes vector
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CrossoverReplaceMutator<I> {
|
||||
phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<I: HasBytesVec> CrossoverReplaceMutator<I> {
|
||||
pub(crate) fn crossover_replace(
|
||||
input: &mut I,
|
||||
target: usize,
|
||||
range: Range<usize>,
|
||||
other: &I,
|
||||
) -> MutationResult {
|
||||
unsafe {
|
||||
buffer_copy(
|
||||
input.bytes_mut(),
|
||||
@ -1148,32 +1204,14 @@ where
|
||||
range.len(),
|
||||
);
|
||||
}
|
||||
Ok(MutationResult::Mutated)
|
||||
MutationResult::Mutated
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for CrossoverInsertMutator {
|
||||
fn name(&self) -> &str {
|
||||
"CrossoverInsertMutator"
|
||||
}
|
||||
}
|
||||
|
||||
impl CrossoverInsertMutator {
|
||||
/// Creates a new [`CrossoverInsertMutator`].
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
/// Crossover replace mutation for inputs with a bytes vector
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CrossoverReplaceMutator;
|
||||
|
||||
impl<S> Mutator<S::Input, S> for CrossoverReplaceMutator
|
||||
impl<I, S> Mutator<I, S> for CrossoverReplaceMutator<I>
|
||||
where
|
||||
S: HasCorpus + HasRand,
|
||||
S::Input: HasBytesVec,
|
||||
S: HasCorpus<Input = I> + HasRand,
|
||||
I: Input + HasBytesVec,
|
||||
{
|
||||
fn mutate(
|
||||
&mut self,
|
||||
@ -1210,30 +1248,23 @@ where
|
||||
// No need to load the input again, it'll still be cached.
|
||||
let other = other_testcase.input().as_ref().unwrap();
|
||||
|
||||
unsafe {
|
||||
buffer_copy(
|
||||
input.bytes_mut(),
|
||||
other.bytes(),
|
||||
range.start,
|
||||
target,
|
||||
range.len(),
|
||||
);
|
||||
}
|
||||
Ok(MutationResult::Mutated)
|
||||
Ok(Self::crossover_replace(input, target, range, other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for CrossoverReplaceMutator {
|
||||
impl<I> Named for CrossoverReplaceMutator<I> {
|
||||
fn name(&self) -> &str {
|
||||
"CrossoverReplaceMutator"
|
||||
}
|
||||
}
|
||||
|
||||
impl CrossoverReplaceMutator {
|
||||
impl<I> CrossoverReplaceMutator<I> {
|
||||
/// Creates a new [`CrossoverReplaceMutator`].
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
Self {
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,10 +258,11 @@ pub type HavocMutationsNoCrossoverType = tuple_list_type!(
|
||||
);
|
||||
|
||||
/// Tuple type of the mutations that compose the Havoc mutator's crossover mutations
|
||||
pub type HavocCrossoverType = tuple_list_type!(CrossoverInsertMutator, CrossoverReplaceMutator);
|
||||
pub type HavocCrossoverType<I> =
|
||||
tuple_list_type!(CrossoverInsertMutator<I>, CrossoverReplaceMutator<I>);
|
||||
|
||||
/// Tuple type of the mutations that compose the Havoc mutator
|
||||
pub type HavocMutationsType = tuple_list_type!(
|
||||
pub type HavocMutationsType<I> = tuple_list_type!(
|
||||
BitFlipMutator,
|
||||
ByteFlipMutator,
|
||||
ByteIncMutator,
|
||||
@ -287,8 +288,8 @@ pub type HavocMutationsType = tuple_list_type!(
|
||||
BytesCopyMutator,
|
||||
BytesInsertCopyMutator,
|
||||
BytesSwapMutator,
|
||||
CrossoverInsertMutator,
|
||||
CrossoverReplaceMutator,
|
||||
CrossoverInsertMutator<I>,
|
||||
CrossoverReplaceMutator<I>,
|
||||
);
|
||||
|
||||
/// Get the mutations that compose the Havoc mutator (only applied to single inputs)
|
||||
@ -325,7 +326,7 @@ pub fn havoc_mutations_no_crossover() -> HavocMutationsNoCrossoverType {
|
||||
|
||||
/// Get the mutations that compose the Havoc mutator's crossover strategy
|
||||
#[must_use]
|
||||
pub fn havoc_crossover() -> HavocCrossoverType {
|
||||
pub fn havoc_crossover<I>() -> HavocCrossoverType<I> {
|
||||
tuple_list!(
|
||||
CrossoverInsertMutator::new(),
|
||||
CrossoverReplaceMutator::new(),
|
||||
@ -334,14 +335,14 @@ pub fn havoc_crossover() -> HavocCrossoverType {
|
||||
|
||||
/// Get the mutations that compose the Havoc mutator
|
||||
#[must_use]
|
||||
pub fn havoc_mutations() -> HavocMutationsType {
|
||||
pub fn havoc_mutations<I>() -> HavocMutationsType<I> {
|
||||
havoc_mutations_no_crossover().merge(havoc_crossover())
|
||||
}
|
||||
|
||||
/// Get the mutations that uses the Tokens metadata
|
||||
#[must_use]
|
||||
pub fn tokens_mutations() -> tuple_list_type!(TokenInsert, TokenReplace) {
|
||||
tuple_list!(TokenInsert::new(), TokenReplace::new(),)
|
||||
tuple_list!(TokenInsert::new(), TokenReplace::new())
|
||||
}
|
||||
|
||||
/// A logging [`Mutator`] that wraps around a [`StdScheduledMutator`].
|
||||
@ -617,7 +618,7 @@ pub mod pybind {
|
||||
/// Python class for StdHavocMutator
|
||||
pub struct PythonStdHavocMutator {
|
||||
/// Rust wrapped StdHavocMutator object
|
||||
pub inner: StdScheduledMutator<BytesInput, HavocMutationsType, PythonStdState>,
|
||||
pub inner: StdScheduledMutator<BytesInput, HavocMutationsType<BytesInput>, PythonStdState>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
|
@ -542,6 +542,11 @@ where
|
||||
)
|
||||
}
|
||||
|
||||
/// Gets the initial value for this map, mutably
|
||||
pub fn initial_mut(&mut self) -> &mut T {
|
||||
&mut self.initial
|
||||
}
|
||||
|
||||
/// Gets the backing for this map
|
||||
pub fn map(&self) -> &OwnedMutSlice<'a, T> {
|
||||
&self.map
|
||||
|
Loading…
x
Reference in New Issue
Block a user