
* rands: use splitmix64 for seeding Seeding with splitmix64 is a good way to avoid starting with low-entropy PRNG states, and is explicitly recommended by the authors of both xoshiro256++ and Romu. While at it, give the xoshiro256++ PRNG its proper name. * rands: use fast_bound() to generate number in range * rands: add top-level choose() * rands: add Rand::next_float() * rands: add Rand::coinflip() helper * libafl: unbreak tests that relied on direct seeding * rands: add SFC64 PRNG SFC64 is a well-established and well-understood PRNG designed by Chris Doty-Humphrey, the author of PractRand. It has been tested quite a lot over the years, and to date has no known weaknesses. Compared to xoshiro256++, it is slightly faster and is likely to be a more future-proof design (xoshiro/xoroshiro family of generators come with quite long history of [flaws][1] found over the years). Compared to Romu, it is slightly slower, but guarantees absense of bias, minimum period of at least 2^64 for any seed, and non-overlapping streams for different seeds. [1]: https://tom-kaitchuck.medium.com/designing-a-new-prng-1c4ffd27124d
566 lines
16 KiB
Rust
566 lines
16 KiB
Rust
//! The `ScheduledMutator` schedules multiple mutations internally.
|
|
|
|
use alloc::{string::String, vec::Vec};
|
|
use core::{
|
|
fmt::{self, Debug},
|
|
marker::PhantomData,
|
|
};
|
|
|
|
use libafl_bolts::{
|
|
rands::Rand,
|
|
tuples::{tuple_list, tuple_list_type, Merge, NamedTuple},
|
|
AsMutSlice, AsSlice, Named,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use super::MutationId;
|
|
use crate::{
|
|
corpus::{Corpus, CorpusId},
|
|
mutators::{
|
|
mutations::{
|
|
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::{TokenInsert, TokenReplace},
|
|
MutationResult, Mutator, MutatorsTuple,
|
|
},
|
|
state::{HasCorpus, HasRand},
|
|
Error, HasMetadata,
|
|
};
|
|
|
|
/// The metadata placed in a [`crate::corpus::Testcase`] by a [`LoggerScheduledMutator`].
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(
|
|
any(not(feature = "serdeany_autoreg"), miri),
|
|
allow(clippy::unsafe_derive_deserialize)
|
|
)] // for SerdeAny
|
|
pub struct LogMutationMetadata {
|
|
/// A list of logs
|
|
pub list: Vec<String>,
|
|
}
|
|
|
|
libafl_bolts::impl_serdeany!(LogMutationMetadata);
|
|
|
|
impl AsSlice for LogMutationMetadata {
|
|
type Entry = String;
|
|
#[must_use]
|
|
fn as_slice(&self) -> &[String] {
|
|
self.list.as_slice()
|
|
}
|
|
}
|
|
impl AsMutSlice for LogMutationMetadata {
|
|
type Entry = String;
|
|
#[must_use]
|
|
fn as_mut_slice(&mut self) -> &mut [String] {
|
|
self.list.as_mut_slice()
|
|
}
|
|
}
|
|
|
|
impl LogMutationMetadata {
|
|
/// Creates new [`struct@LogMutationMetadata`].
|
|
#[must_use]
|
|
pub fn new(list: Vec<String>) -> Self {
|
|
Self { list }
|
|
}
|
|
}
|
|
|
|
/// A [`Mutator`] that composes multiple mutations into one.
|
|
pub trait ComposedByMutations<I, MT, S>
|
|
where
|
|
MT: MutatorsTuple<I, S>,
|
|
{
|
|
/// Get the mutations
|
|
fn mutations(&self) -> &MT;
|
|
|
|
/// Get the mutations (mutable)
|
|
fn mutations_mut(&mut self) -> &mut MT;
|
|
}
|
|
|
|
/// A [`Mutator`] scheduling multiple [`Mutator`]s for an input.
|
|
pub trait ScheduledMutator<I, MT, S>: ComposedByMutations<I, MT, S> + Mutator<I, S>
|
|
where
|
|
MT: MutatorsTuple<I, S>,
|
|
{
|
|
/// Compute the number of iterations used to apply stacked mutations
|
|
fn iterations(&self, state: &mut S, input: &I) -> u64;
|
|
|
|
/// Get the next mutation to apply
|
|
fn schedule(&self, state: &mut S, input: &I) -> MutationId;
|
|
|
|
/// New default implementation for mutate.
|
|
/// Implementations must forward `mutate()` to this method
|
|
fn scheduled_mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
|
|
let mut r = MutationResult::Skipped;
|
|
let num = self.iterations(state, input);
|
|
for _ in 0..num {
|
|
let idx = self.schedule(state, input);
|
|
let outcome = self.mutations_mut().get_and_mutate(idx, state, input)?;
|
|
if outcome == MutationResult::Mutated {
|
|
r = MutationResult::Mutated;
|
|
}
|
|
}
|
|
Ok(r)
|
|
}
|
|
}
|
|
|
|
/// A [`Mutator`] that schedules one of the embedded mutations on each call.
|
|
pub struct StdScheduledMutator<I, MT, S>
|
|
where
|
|
MT: MutatorsTuple<I, S>,
|
|
S: HasRand,
|
|
{
|
|
name: String,
|
|
mutations: MT,
|
|
max_stack_pow: u64,
|
|
phantom: PhantomData<(I, S)>,
|
|
}
|
|
|
|
impl<I, MT, S> Debug for StdScheduledMutator<I, MT, S>
|
|
where
|
|
MT: MutatorsTuple<I, S>,
|
|
S: HasRand,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"StdScheduledMutator with {} mutations for Input type {}",
|
|
self.mutations.len(),
|
|
core::any::type_name::<I>()
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<I, MT, S> Named for StdScheduledMutator<I, MT, S>
|
|
where
|
|
MT: MutatorsTuple<I, S>,
|
|
S: HasRand,
|
|
{
|
|
fn name(&self) -> &str {
|
|
&self.name
|
|
}
|
|
}
|
|
|
|
impl<I, MT, S> Mutator<I, S> for StdScheduledMutator<I, MT, S>
|
|
where
|
|
MT: MutatorsTuple<I, S>,
|
|
S: HasRand,
|
|
{
|
|
#[inline]
|
|
fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
|
|
self.scheduled_mutate(state, input)
|
|
}
|
|
}
|
|
|
|
impl<I, MT, S> ComposedByMutations<I, MT, S> for StdScheduledMutator<I, MT, S>
|
|
where
|
|
MT: MutatorsTuple<I, S>,
|
|
S: HasRand,
|
|
{
|
|
/// Get the mutations
|
|
#[inline]
|
|
fn mutations(&self) -> &MT {
|
|
&self.mutations
|
|
}
|
|
|
|
// Get the mutations (mutable)
|
|
#[inline]
|
|
fn mutations_mut(&mut self) -> &mut MT {
|
|
&mut self.mutations
|
|
}
|
|
}
|
|
|
|
impl<I, MT, S> ScheduledMutator<I, MT, S> for StdScheduledMutator<I, MT, S>
|
|
where
|
|
MT: MutatorsTuple<I, S>,
|
|
S: HasRand,
|
|
{
|
|
/// Compute the number of iterations used to apply stacked mutations
|
|
fn iterations(&self, state: &mut S, _: &I) -> u64 {
|
|
1 << (1 + state.rand_mut().below(self.max_stack_pow))
|
|
}
|
|
|
|
/// Get the next mutation to apply
|
|
fn schedule(&self, state: &mut S, _: &I) -> MutationId {
|
|
debug_assert!(self.mutations.len() != 0);
|
|
state.rand_mut().below(self.mutations.len() as u64).into()
|
|
}
|
|
}
|
|
|
|
impl<I, MT, S> StdScheduledMutator<I, MT, S>
|
|
where
|
|
MT: MutatorsTuple<I, S>,
|
|
S: HasRand,
|
|
{
|
|
/// Create a new [`StdScheduledMutator`] instance specifying mutations
|
|
pub fn new(mutations: MT) -> Self {
|
|
StdScheduledMutator {
|
|
name: format!("StdScheduledMutator[{}]", mutations.names().join(", ")),
|
|
mutations,
|
|
max_stack_pow: 7,
|
|
phantom: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Create a new [`StdScheduledMutator`] instance specifying mutations and the maximun number of iterations
|
|
pub fn with_max_stack_pow(mutations: MT, max_stack_pow: u64) -> Self {
|
|
StdScheduledMutator {
|
|
name: format!("StdScheduledMutator[{}]", mutations.names().join(", ")),
|
|
mutations,
|
|
max_stack_pow,
|
|
phantom: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Tuple type of the mutations that compose the Havoc mutator without crossover mutations
|
|
pub type HavocMutationsNoCrossoverType = tuple_list_type!(
|
|
BitFlipMutator,
|
|
ByteFlipMutator,
|
|
ByteIncMutator,
|
|
ByteDecMutator,
|
|
ByteNegMutator,
|
|
ByteRandMutator,
|
|
ByteAddMutator,
|
|
WordAddMutator,
|
|
DwordAddMutator,
|
|
QwordAddMutator,
|
|
ByteInterestingMutator,
|
|
WordInterestingMutator,
|
|
DwordInterestingMutator,
|
|
BytesDeleteMutator,
|
|
BytesDeleteMutator,
|
|
BytesDeleteMutator,
|
|
BytesDeleteMutator,
|
|
BytesExpandMutator,
|
|
BytesInsertMutator,
|
|
BytesRandInsertMutator,
|
|
BytesSetMutator,
|
|
BytesRandSetMutator,
|
|
BytesCopyMutator,
|
|
BytesInsertCopyMutator,
|
|
BytesSwapMutator,
|
|
);
|
|
|
|
/// Tuple type of the mutations that compose the Havoc mutator's crossover mutations
|
|
pub type HavocCrossoverType<I> =
|
|
tuple_list_type!(CrossoverInsertMutator<I>, CrossoverReplaceMutator<I>);
|
|
|
|
/// Tuple type of the mutations that compose the Havoc mutator
|
|
pub type HavocMutationsType<I> = tuple_list_type!(
|
|
BitFlipMutator,
|
|
ByteFlipMutator,
|
|
ByteIncMutator,
|
|
ByteDecMutator,
|
|
ByteNegMutator,
|
|
ByteRandMutator,
|
|
ByteAddMutator,
|
|
WordAddMutator,
|
|
DwordAddMutator,
|
|
QwordAddMutator,
|
|
ByteInterestingMutator,
|
|
WordInterestingMutator,
|
|
DwordInterestingMutator,
|
|
BytesDeleteMutator,
|
|
BytesDeleteMutator,
|
|
BytesDeleteMutator,
|
|
BytesDeleteMutator,
|
|
BytesExpandMutator,
|
|
BytesInsertMutator,
|
|
BytesRandInsertMutator,
|
|
BytesSetMutator,
|
|
BytesRandSetMutator,
|
|
BytesCopyMutator,
|
|
BytesInsertCopyMutator,
|
|
BytesSwapMutator,
|
|
CrossoverInsertMutator<I>,
|
|
CrossoverReplaceMutator<I>,
|
|
);
|
|
|
|
/// Get the mutations that compose the Havoc mutator (only applied to single inputs)
|
|
#[must_use]
|
|
pub fn havoc_mutations_no_crossover() -> HavocMutationsNoCrossoverType {
|
|
tuple_list!(
|
|
BitFlipMutator::new(),
|
|
ByteFlipMutator::new(),
|
|
ByteIncMutator::new(),
|
|
ByteDecMutator::new(),
|
|
ByteNegMutator::new(),
|
|
ByteRandMutator::new(),
|
|
ByteAddMutator::new(),
|
|
WordAddMutator::new(),
|
|
DwordAddMutator::new(),
|
|
QwordAddMutator::new(),
|
|
ByteInterestingMutator::new(),
|
|
WordInterestingMutator::new(),
|
|
DwordInterestingMutator::new(),
|
|
BytesDeleteMutator::new(),
|
|
BytesDeleteMutator::new(),
|
|
BytesDeleteMutator::new(),
|
|
BytesDeleteMutator::new(),
|
|
BytesExpandMutator::new(),
|
|
BytesInsertMutator::new(),
|
|
BytesRandInsertMutator::new(),
|
|
BytesSetMutator::new(),
|
|
BytesRandSetMutator::new(),
|
|
BytesCopyMutator::new(),
|
|
BytesInsertCopyMutator::new(),
|
|
BytesSwapMutator::new(),
|
|
)
|
|
}
|
|
|
|
/// Get the mutations that compose the Havoc mutator's crossover strategy
|
|
#[must_use]
|
|
pub fn havoc_crossover<I>() -> HavocCrossoverType<I> {
|
|
tuple_list!(
|
|
CrossoverInsertMutator::new(),
|
|
CrossoverReplaceMutator::new(),
|
|
)
|
|
}
|
|
|
|
/// Get the mutations that compose the Havoc mutator
|
|
#[must_use]
|
|
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())
|
|
}
|
|
|
|
/// A logging [`Mutator`] that wraps around a [`StdScheduledMutator`].
|
|
pub struct LoggerScheduledMutator<I, MT, S, SM>
|
|
where
|
|
MT: MutatorsTuple<I, S> + NamedTuple,
|
|
S: HasRand + HasCorpus,
|
|
SM: ScheduledMutator<I, MT, S>,
|
|
{
|
|
name: String,
|
|
scheduled: SM,
|
|
mutation_log: Vec<MutationId>,
|
|
phantom: PhantomData<(I, MT, S)>,
|
|
}
|
|
|
|
impl<I, MT, S, SM> Debug for LoggerScheduledMutator<I, MT, S, SM>
|
|
where
|
|
MT: MutatorsTuple<I, S> + NamedTuple,
|
|
S: HasRand + HasCorpus,
|
|
SM: ScheduledMutator<I, MT, S>,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"LoggerScheduledMutator with {} mutations for Input type {}",
|
|
MT::LEN,
|
|
core::any::type_name::<I>()
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<I, MT, S, SM> Named for LoggerScheduledMutator<I, MT, S, SM>
|
|
where
|
|
MT: MutatorsTuple<I, S> + NamedTuple,
|
|
S: HasRand + HasCorpus,
|
|
SM: ScheduledMutator<I, MT, S>,
|
|
{
|
|
fn name(&self) -> &str {
|
|
&self.name
|
|
}
|
|
}
|
|
|
|
impl<I, MT, S, SM> Mutator<I, S> for LoggerScheduledMutator<I, MT, S, SM>
|
|
where
|
|
MT: MutatorsTuple<I, S> + NamedTuple,
|
|
S: HasRand + HasCorpus,
|
|
SM: ScheduledMutator<I, MT, S>,
|
|
{
|
|
fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
|
|
self.scheduled_mutate(state, input)
|
|
}
|
|
|
|
fn post_exec(&mut self, state: &mut S, corpus_idx: Option<CorpusId>) -> Result<(), Error> {
|
|
if let Some(idx) = corpus_idx {
|
|
let mut testcase = (*state.corpus_mut().get(idx)?).borrow_mut();
|
|
let mut log = Vec::<String>::new();
|
|
while let Some(idx) = self.mutation_log.pop() {
|
|
let name = String::from(self.scheduled.mutations().name(idx.0).unwrap()); // TODO maybe return an Error on None
|
|
log.push(name);
|
|
}
|
|
let meta = LogMutationMetadata::new(log);
|
|
testcase.add_metadata(meta);
|
|
};
|
|
// Always reset the log for each run
|
|
self.mutation_log.clear();
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<I, MT, S, SM> ComposedByMutations<I, MT, S> for LoggerScheduledMutator<I, MT, S, SM>
|
|
where
|
|
MT: MutatorsTuple<I, S> + NamedTuple,
|
|
S: HasRand + HasCorpus,
|
|
SM: ScheduledMutator<I, MT, S>,
|
|
{
|
|
#[inline]
|
|
fn mutations(&self) -> &MT {
|
|
self.scheduled.mutations()
|
|
}
|
|
|
|
#[inline]
|
|
fn mutations_mut(&mut self) -> &mut MT {
|
|
self.scheduled.mutations_mut()
|
|
}
|
|
}
|
|
|
|
impl<I, MT, S, SM> ScheduledMutator<I, MT, S> for LoggerScheduledMutator<I, MT, S, SM>
|
|
where
|
|
MT: MutatorsTuple<I, S> + NamedTuple,
|
|
S: HasRand + HasCorpus,
|
|
SM: ScheduledMutator<I, MT, S>,
|
|
{
|
|
/// Compute the number of iterations used to apply stacked mutations
|
|
fn iterations(&self, state: &mut S, _: &I) -> u64 {
|
|
1 << (1 + state.rand_mut().below(6))
|
|
}
|
|
|
|
/// Get the next mutation to apply
|
|
fn schedule(&self, state: &mut S, _: &I) -> MutationId {
|
|
debug_assert!(MT::LEN != 0);
|
|
state.rand_mut().below(MT::LEN as u64).into()
|
|
}
|
|
|
|
fn scheduled_mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
|
|
let mut r = MutationResult::Skipped;
|
|
let num = self.iterations(state, input);
|
|
self.mutation_log.clear();
|
|
for _ in 0..num {
|
|
let idx = self.schedule(state, input);
|
|
self.mutation_log.push(idx);
|
|
let outcome = self.mutations_mut().get_and_mutate(idx, state, input)?;
|
|
if outcome == MutationResult::Mutated {
|
|
r = MutationResult::Mutated;
|
|
}
|
|
}
|
|
Ok(r)
|
|
}
|
|
}
|
|
|
|
impl<I, MT, S, SM> LoggerScheduledMutator<I, MT, S, SM>
|
|
where
|
|
MT: MutatorsTuple<I, S> + NamedTuple,
|
|
S: HasRand + HasCorpus,
|
|
SM: ScheduledMutator<I, MT, S>,
|
|
{
|
|
/// Create a new [`LoggerScheduledMutator`] instance without mutations and corpus
|
|
/// This mutator logs all mutators.
|
|
pub fn new(scheduled: SM) -> Self {
|
|
Self {
|
|
name: format!("LoggerScheduledMutator[{}]", scheduled.name()),
|
|
scheduled,
|
|
mutation_log: vec![],
|
|
phantom: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use libafl_bolts::rands::{StdRand, XkcdRand};
|
|
|
|
use crate::{
|
|
corpus::{Corpus, InMemoryCorpus, Testcase},
|
|
feedbacks::ConstFeedback,
|
|
inputs::{BytesInput, HasBytesVec},
|
|
mutators::{
|
|
mutations::SpliceMutator,
|
|
scheduled::{havoc_mutations, StdScheduledMutator},
|
|
Mutator,
|
|
},
|
|
state::StdState,
|
|
};
|
|
|
|
#[test]
|
|
fn test_mut_scheduled() {
|
|
let rand = XkcdRand::with_seed(0);
|
|
let mut corpus: InMemoryCorpus<BytesInput> = InMemoryCorpus::new();
|
|
corpus
|
|
.add(Testcase::new(vec![b'a', b'b', b'c'].into()))
|
|
.unwrap();
|
|
corpus
|
|
.add(Testcase::new(vec![b'd', b'e', b'f'].into()))
|
|
.unwrap();
|
|
|
|
let mut input = corpus.cloned_input_for_id(corpus.first().unwrap()).unwrap();
|
|
|
|
let mut feedback = ConstFeedback::new(false);
|
|
let mut objective = ConstFeedback::new(false);
|
|
|
|
let mut state = StdState::new(
|
|
rand,
|
|
corpus,
|
|
InMemoryCorpus::new(),
|
|
&mut feedback,
|
|
&mut objective,
|
|
)
|
|
.unwrap();
|
|
|
|
let mut splice = SpliceMutator::new();
|
|
splice.mutate(&mut state, &mut input).unwrap();
|
|
|
|
log::trace!("{:?}", input.bytes());
|
|
|
|
// The pre-seeded rand should have spliced at position 2.
|
|
assert_eq!(input.bytes(), &[b'a', b'b', b'f']);
|
|
}
|
|
|
|
#[test]
|
|
fn test_havoc() {
|
|
let rand = StdRand::with_seed(0x1337);
|
|
let mut corpus: InMemoryCorpus<BytesInput> = InMemoryCorpus::new();
|
|
corpus
|
|
.add(Testcase::new(vec![b'a', b'b', b'c'].into()))
|
|
.unwrap();
|
|
corpus
|
|
.add(Testcase::new(vec![b'd', b'e', b'f'].into()))
|
|
.unwrap();
|
|
|
|
let mut input = corpus.cloned_input_for_id(corpus.first().unwrap()).unwrap();
|
|
let input_prior = input.clone();
|
|
|
|
let mut feedback = ConstFeedback::new(false);
|
|
let mut objective = ConstFeedback::new(false);
|
|
|
|
let mut state = StdState::new(
|
|
rand,
|
|
corpus,
|
|
InMemoryCorpus::new(),
|
|
&mut feedback,
|
|
&mut objective,
|
|
)
|
|
.unwrap();
|
|
|
|
let mut havoc = StdScheduledMutator::new(havoc_mutations());
|
|
|
|
assert_eq!(input, input_prior);
|
|
|
|
let mut equal_in_a_row = 0;
|
|
|
|
for _ in 0..42 {
|
|
havoc.mutate(&mut state, &mut input).unwrap();
|
|
|
|
// Make sure we actually mutate something, at least sometimes
|
|
equal_in_a_row = if input == input_prior {
|
|
equal_in_a_row + 1
|
|
} else {
|
|
0
|
|
};
|
|
assert_ne!(equal_in_a_row, 5);
|
|
}
|
|
}
|
|
}
|