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>
This commit is contained in:
Valentin Huber 2024-12-15 15:00:41 +01:00 committed by GitHub
parent 65e544a417
commit 61e3f0b3a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 947 additions and 147 deletions

View File

@ -1,18 +1,15 @@
use core::num::NonZeroUsize;
use std::{
borrow::Cow,
hash::{DefaultHasher, Hash, Hasher},
};
use std::{borrow::Cow, hash::Hash};
use libafl::{
corpus::CorpusId,
generators::{Generator, RandBytesGenerator},
inputs::{BytesInput, HasTargetBytes, Input, MutVecInput},
inputs::{value::MutI16Input, BytesInput, HasTargetBytes, Input, MutVecInput},
mutators::{MutationResult, Mutator},
state::HasRand,
Error, SerdeAny,
};
use libafl_bolts::{rands::Rand, Named};
use libafl_bolts::{generic_hash_std, rands::Rand, Named};
use serde::{Deserialize, Serialize};
/// The custom [`Input`] type used in this example, consisting of a byte array part, a byte array that is not always present, and a boolean
@ -20,20 +17,20 @@ use serde::{Deserialize, Serialize};
/// Imagine these could be used to model command line arguments for a bash command, where
/// - `byte_array` is binary data that is always needed like what is passed to stdin,
/// - `optional_byte_array` is binary data passed as a command line arg, and it is only passed if it is not `None` in the input,
/// - `num` is an arbitrary number (`i16` in this case)
/// - `boolean` models the presence or absence of a command line flag that does not require additional data
#[derive(Serialize, Deserialize, Clone, Debug, Hash, SerdeAny)]
pub struct CustomInput {
pub byte_array: Vec<u8>,
pub optional_byte_array: Option<Vec<u8>>,
pub num: i16,
pub boolean: bool,
}
/// Hash-based implementation
impl Input for CustomInput {
fn generate_name(&self, _id: Option<CorpusId>) -> String {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
format!("{:016x}", hasher.finish())
format!("{:016x}", generic_hash_std(self))
}
}
@ -57,6 +54,16 @@ impl CustomInput {
pub fn optional_byte_array(&self) -> Option<&[u8]> {
self.optional_byte_array.as_deref()
}
/// Returns a mutable reference to the number
pub fn num_mut(&mut self) -> MutI16Input<'_> {
(&mut self.num).into()
}
/// Returns an immutable reference to the number
pub fn num(&self) -> &i16 {
&self.num
}
}
/// A generator for [`CustomInput`] used in this example
@ -86,10 +93,12 @@ where
.coinflip(0.5)
.then(|| generator.generate(state).unwrap().target_bytes().into());
let boolean = state.rand_mut().coinflip(0.5);
let num = state.rand_mut().next() as i16;
Ok(CustomInput {
byte_array,
optional_byte_array,
num,
boolean,
})
}

View File

@ -8,7 +8,10 @@ use input::{
CustomInput, CustomInputGenerator, ToggleBooleanMutator, ToggleOptionalByteArrayMutator,
};
#[cfg(feature = "simple_interface")]
use libafl::mutators::havoc_mutations::{mapped_havoc_mutations, optional_mapped_havoc_mutations};
use libafl::mutators::{
havoc_mutations::{mapped_havoc_mutations, optional_mapped_havoc_mutations},
numeric::mapped_int_mutators,
};
use libafl::{
corpus::{InMemoryCorpus, OnDiskCorpus},
events::SimpleEventManager,
@ -32,6 +35,7 @@ use {
libafl::mutators::{
havoc_mutations::{havoc_crossover_with_corpus_mapper, havoc_mutations_no_crossover},
mapping::{ToMappedInputFunctionMappingMutatorMapper, ToOptionMappingMutatorMapper},
numeric::{int_mutators_no_crossover, mapped_int_mutators_crossover},
},
libafl_bolts::tuples::Map,
};
@ -43,7 +47,7 @@ static mut SIGNALS_PTR: *mut u8 = &raw mut SIGNALS as _;
/// Assign a signal to the signals map
fn signals_set(idx: usize) {
if idx > 2 {
if idx > 3 {
println!("Setting signal: {idx}");
}
unsafe { write(SIGNALS_PTR.add(idx), 1) };
@ -60,6 +64,9 @@ pub fn main() {
signals_set(1);
if input.optional_byte_array == Some(vec![b'b']) {
signals_set(2);
// require input.num to be in the top 1% of possible values
if input.num > i16::MAX - i16::MAX / 50 {
signals_set(3);
if input.boolean {
#[cfg(unix)]
panic!("Artificial bug triggered =)");
@ -75,6 +82,7 @@ pub fn main() {
}
}
}
}
ExitKind::Ok
};
@ -136,7 +144,7 @@ pub fn main() {
.expect("Failed to generate the initial corpus");
#[cfg(feature = "simple_interface")]
let (mapped_mutators, optional_mapped_mutators) = {
let (mapped_mutators, optional_mapped_mutators, int_mutators) = {
// Creating mutators that will operate on input.byte_array
let mapped_mutators =
mapped_havoc_mutations(CustomInput::byte_array_mut, CustomInput::byte_array);
@ -146,11 +154,13 @@ pub fn main() {
CustomInput::optional_byte_array_mut,
CustomInput::optional_byte_array,
);
(mapped_mutators, optional_mapped_mutators)
let int_mutators = mapped_int_mutators(CustomInput::num_mut, CustomInput::num);
(mapped_mutators, optional_mapped_mutators, int_mutators)
};
#[cfg(not(feature = "simple_interface"))]
let (mapped_mutators, optional_mapped_mutators) = {
let (mapped_mutators, optional_mapped_mutators, int_mutators) = {
// Creating mutators that will operate on input.byte_array
let mapped_mutators = havoc_mutations_no_crossover()
.merge(havoc_crossover_with_corpus_mapper(CustomInput::byte_array))
@ -168,7 +178,13 @@ pub fn main() {
CustomInput::optional_byte_array_mut,
));
(mapped_mutators, optional_mapped_mutators)
// Creating mutators that will operate on input.num
let int_mutators = int_mutators_no_crossover()
.merge(mapped_int_mutators_crossover(CustomInput::num))
.map(ToMappedInputFunctionMappingMutatorMapper::new(
CustomInput::num_mut,
));
(mapped_mutators, optional_mapped_mutators, int_mutators)
};
// Merging multiple lists of mutators that mutate a sub-part of the custom input
@ -178,6 +194,8 @@ pub fn main() {
.merge(mapped_mutators)
// Then, mutators for the optional byte array, these return MutationResult::Skipped if the part is not present
.merge(optional_mapped_mutators)
// Then, mutators for the number
.merge(int_mutators)
// A custom mutator that sets the optional byte array to None if present, and generates a random byte array of length 1 if it is not
.prepend(ToggleOptionalByteArrayMutator::new(nonzero!(1)))
// Finally, a custom mutator that toggles the boolean part of the input

View File

@ -1,61 +1,20 @@
//! The `BytesInput` is the "normal" input, a map of bytes, that can be sent directly to the client
//! (As opposed to other, more abstract, inputs, like an Grammar-Based AST Input)
use alloc::{borrow::ToOwned, rc::Rc, string::String, vec::Vec};
use core::{
cell::RefCell,
hash::{BuildHasher, Hasher},
use alloc::{
borrow::ToOwned,
rc::Rc,
vec::{self, Vec},
};
#[cfg(feature = "std")]
use std::{fs::File, io::Read, path::Path};
use core::cell::RefCell;
use ahash::RandomState;
#[cfg(feature = "std")]
use libafl_bolts::{fs::write_file_atomic, Error};
use libafl_bolts::{ownedref::OwnedSlice, HasLen};
use serde::{Deserialize, Serialize};
use crate::{
corpus::CorpusId,
inputs::{HasMutatorBytes, HasTargetBytes, Input},
};
use super::ValueInput;
use crate::inputs::{HasMutatorBytes, HasTargetBytes};
/// A bytes input is the basic input
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct BytesInput {
/// The raw input bytes
pub(crate) bytes: Vec<u8>,
}
impl Input for BytesInput {
#[cfg(feature = "std")]
/// Write this input to the file
fn to_file<P>(&self, path: P) -> Result<(), Error>
where
P: AsRef<Path>,
{
write_file_atomic(path, &self.bytes)
}
/// Load the content of this input from a file
#[cfg(feature = "std")]
fn from_file<P>(path: P) -> Result<Self, Error>
where
P: AsRef<Path>,
{
let mut file = File::open(path)?;
let mut bytes: Vec<u8> = vec![];
file.read_to_end(&mut bytes)?;
Ok(BytesInput::new(bytes))
}
/// Generate a name for this input
fn generate_name(&self, _id: Option<CorpusId>) -> String {
let mut hasher = RandomState::with_seeds(0, 0, 0, 0).build_hasher();
hasher.write(self.bytes());
format!("{:016x}", hasher.finish())
}
}
pub type BytesInput = ValueInput<Vec<u8>>;
/// Rc Ref-cell from Input
impl From<BytesInput> for Rc<RefCell<BytesInput>> {
@ -65,57 +24,48 @@ impl From<BytesInput> for Rc<RefCell<BytesInput>> {
}
impl HasMutatorBytes for BytesInput {
#[inline]
fn bytes(&self) -> &[u8] {
&self.bytes
self.as_ref()
}
#[inline]
fn bytes_mut(&mut self) -> &mut [u8] {
&mut self.bytes
self.as_mut()
}
fn resize(&mut self, new_len: usize, value: u8) {
self.bytes.resize(new_len, value);
self.as_mut().resize(new_len, value);
}
fn extend<'a, I: IntoIterator<Item = &'a u8>>(&mut self, iter: I) {
Extend::extend(&mut self.bytes, iter);
self.as_mut().extend(iter);
}
fn splice<R, I>(&mut self, range: R, replace_with: I) -> alloc::vec::Splice<'_, I::IntoIter>
fn splice<R, I>(&mut self, range: R, replace_with: I) -> vec::Splice<'_, I::IntoIter>
where
R: core::ops::RangeBounds<usize>,
I: IntoIterator<Item = u8>,
{
self.bytes.splice(range, replace_with)
self.as_mut().splice(range, replace_with)
}
fn drain<R>(&mut self, range: R) -> alloc::vec::Drain<'_, u8>
fn drain<R>(&mut self, range: R) -> vec::Drain<'_, u8>
where
R: core::ops::RangeBounds<usize>,
{
self.bytes.drain(range)
self.as_mut().drain(range)
}
}
impl HasTargetBytes for BytesInput {
#[inline]
fn target_bytes(&self) -> OwnedSlice<u8> {
OwnedSlice::from(&self.bytes)
OwnedSlice::from(self.as_ref())
}
}
impl HasLen for BytesInput {
#[inline]
fn len(&self) -> usize {
self.bytes.len()
}
}
impl From<Vec<u8>> for BytesInput {
fn from(bytes: Vec<u8>) -> Self {
Self::new(bytes)
self.as_ref().len()
}
}
@ -127,14 +77,6 @@ impl From<&[u8]> for BytesInput {
impl From<BytesInput> for Vec<u8> {
fn from(value: BytesInput) -> Vec<u8> {
value.bytes
}
}
impl BytesInput {
/// Creates a new bytes input using the given bytes
#[must_use]
pub const fn new(bytes: Vec<u8>) -> Self {
Self { bytes }
value.into_inner()
}
}

View File

@ -3,6 +3,9 @@
pub mod bytes;
pub use bytes::BytesInput;
pub mod value;
pub use value::ValueInput;
pub mod encoded;
pub use encoded::*;
@ -28,7 +31,12 @@ use alloc::{
string::{String, ToString},
vec::{Drain, Splice, Vec},
};
use core::{clone::Clone, fmt::Debug, marker::PhantomData, ops::RangeBounds};
use core::{
clone::Clone,
fmt::Debug,
marker::PhantomData,
ops::{Deref, DerefMut, RangeBounds},
};
#[cfg(feature = "std")]
use std::{fs::File, hash::Hash, io::Read, path::Path};
@ -42,6 +50,7 @@ use libafl_bolts::{
#[cfg(feature = "nautilus")]
pub use nautilus::*;
use serde::{Deserialize, Serialize};
use value::ValueMutRefInput;
use crate::corpus::CorpusId;
@ -210,36 +219,29 @@ where
}
/// A wrapper type that allows us to use mutators for Mutators for `&mut `[`Vec`].
#[derive(Debug)]
pub struct MutVecInput<'a>(&'a mut Vec<u8>);
impl<'a> From<&'a mut Vec<u8>> for MutVecInput<'a> {
fn from(value: &'a mut Vec<u8>) -> Self {
Self(value)
}
}
pub type MutVecInput<'a> = ValueMutRefInput<'a, Vec<u8>>;
impl HasLen for MutVecInput<'_> {
fn len(&self) -> usize {
self.0.len()
self.deref().len()
}
}
impl HasMutatorBytes for MutVecInput<'_> {
fn bytes(&self) -> &[u8] {
self.0
self
}
fn bytes_mut(&mut self) -> &mut [u8] {
self.0
self
}
fn resize(&mut self, new_len: usize, value: u8) {
self.0.resize(new_len, value);
self.deref_mut().resize(new_len, value);
}
fn extend<'b, I: IntoIterator<Item = &'b u8>>(&mut self, iter: I) {
self.0.extend(iter);
self.deref_mut().extend(iter);
}
fn splice<R, I>(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter>
@ -247,24 +249,17 @@ impl HasMutatorBytes for MutVecInput<'_> {
R: RangeBounds<usize>,
I: IntoIterator<Item = u8>,
{
self.0.splice::<R, I>(range, replace_with)
self.deref_mut().splice::<R, I>(range, replace_with)
}
fn drain<R>(&mut self, range: R) -> Drain<'_, u8>
where
R: RangeBounds<usize>,
{
self.0.drain(range)
self.deref_mut().drain(range)
}
}
impl MappedInput for MutVecInput<'_> {
type Type<'b>
= MutVecInput<'b>
where
Self: 'b;
}
/// Defines the input type shared across traits of the type.
/// Needed for consistency across HasCorpus/HasSolutions and friends.
pub trait UsesInput {

339
libafl/src/inputs/value.rs Normal file
View File

@ -0,0 +1,339 @@
//! Newtype pattern style wrapper for [`super::Input`]s
use alloc::{string::String, vec::Vec};
use core::{
fmt::Debug,
hash::Hash,
ops::{Deref, DerefMut},
};
use libafl_bolts::{generic_hash_std, rands::Rand};
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use {
libafl_bolts::{fs::write_file_atomic, Error},
std::{fs::File, io::Read, path::Path},
};
use super::{Input, MappedInput};
use crate::{corpus::CorpusId, mutators::numeric::Numeric};
/// Newtype pattern wrapper around an underlying structure to implement inputs
///
/// This does not blanket implement [`super::Input`], because for certain inputs, writing them to disk does not make sense, because they don't own their data (like [`super::MutVecInput`])
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
pub struct ValueInput<I>(I);
impl<I> From<I> for ValueInput<I> {
fn from(value: I) -> Self {
Self(value)
}
}
impl<I> ValueInput<I> {
/// Create a new [`ValueInput`]
pub const fn new(value: I) -> Self {
Self(value)
}
/// Extract the inner value
pub fn into_inner(self) -> I {
self.0
}
}
impl<I> AsRef<I> for ValueInput<I> {
fn as_ref(&self) -> &I {
&self.0
}
}
impl<I> AsMut<I> for ValueInput<I> {
fn as_mut(&mut self) -> &mut I {
&mut self.0
}
}
impl<I: Copy> Copy for ValueInput<I> {}
// Macro to implement the `Input` trait and create type aliases for `WrappingInput<T>`
macro_rules! impl_input_for_value_input {
($($t:ty => $name:ident),+ $(,)?) => {
$(
impl Input for ValueInput<$t> {
fn generate_name(&self, _id: Option<CorpusId>) -> String {
format!("{:016x}", generic_hash_std(self))
}
}
/// Input wrapping a <$t>
pub type $name = ValueInput<$t>;
)*
};
}
// Invoke the macro with type-name pairs
impl_input_for_value_input!(
u8 => U8Input,
u16 => U16Input,
u32 => U32Input,
u64 => U64Input,
u128 => U128Input,
usize => UsizeInput,
i8 => I8Input,
i16 => I16Input,
i32 => I32Input,
i64 => I64Input,
i128 => I128Input,
isize => IsizeInput,
);
/// manually implemented because files can be written more efficiently
impl Input for ValueInput<Vec<u8>> {
fn generate_name(&self, _id: Option<CorpusId>) -> String {
format!("{:016x}", generic_hash_std(self))
}
/// Write this input to the file
#[cfg(feature = "std")]
fn to_file<P>(&self, path: P) -> Result<(), Error>
where
P: AsRef<Path>,
{
write_file_atomic(path, self.as_ref())?;
Ok(())
}
/// Load the content of this input from a file
#[cfg(feature = "std")]
fn from_file<P>(path: P) -> Result<Self, Error>
where
P: AsRef<Path>,
{
let mut file = File::open(path)?;
let mut data = vec![];
file.read_to_end(&mut data)?;
Ok(data.into())
}
}
impl<I> Numeric for ValueInput<I>
where
I: Numeric,
{
fn flip_all_bits(&mut self) {
self.as_mut().flip_all_bits();
}
fn flip_bit_at(&mut self, rhs: usize) {
self.as_mut().flip_bit_at(rhs);
}
fn wrapping_inc(&mut self) {
self.as_mut().wrapping_inc();
}
fn wrapping_dec(&mut self) {
self.as_mut().wrapping_dec();
}
fn twos_complement(&mut self) {
self.as_mut().twos_complement();
}
fn randomize<R: Rand>(&mut self, rand: &mut R) {
self.as_mut().randomize(rand);
}
}
/// Input type that holds a mutable reference to an inner value
#[derive(Debug, PartialEq)]
pub struct ValueMutRefInput<'a, I>(&'a mut I);
// Macro to implement the `Input` trait and create type aliases for `WrappingInput<T>`
macro_rules! impl_input_for_value_mut_ref_input {
($($t:ty => $name:ident),+ $(,)?) => {
$( /// Input wrapping a <$t>
pub type $name<'a> = ValueMutRefInput<'a, $t>;
)*
};
}
// Invoke the macro with type-name pairs
impl_input_for_value_mut_ref_input!(
u8 => MutU8Input,
u16 => MutU16Input,
u32 => MutU32Input,
u64 => MutU64Input,
u128 => MutU128Input,
usize => MutUsizeInput,
i8 => MutI8Input,
i16 => MutI16Input,
i32 => MutI32Input,
i64 => MutI64Input,
i128 => MutI128Input,
isize => MutIsizeInput,
);
impl<I> Deref for ValueMutRefInput<'_, I> {
type Target = I;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<I> DerefMut for ValueMutRefInput<'_, I> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0
}
}
impl<'a, I> From<&'a mut I> for ValueMutRefInput<'a, I> {
fn from(value: &'a mut I) -> Self {
Self(value)
}
}
impl<'a, I> From<&'a mut ValueInput<I>> for ValueMutRefInput<'a, I> {
fn from(value: &'a mut ValueInput<I>) -> Self {
Self(value.as_mut())
}
}
impl<I> MappedInput for ValueMutRefInput<'_, I> {
type Type<'a>
= ValueMutRefInput<'a, I>
where
Self: 'a;
}
impl<I> Numeric for ValueMutRefInput<'_, I>
where
I: Numeric,
{
fn flip_all_bits(&mut self) {
self.deref_mut().flip_all_bits();
}
fn flip_bit_at(&mut self, rhs: usize) {
self.deref_mut().flip_bit_at(rhs);
}
fn wrapping_inc(&mut self) {
self.deref_mut().wrapping_inc();
}
fn wrapping_dec(&mut self) {
self.deref_mut().wrapping_dec();
}
fn twos_complement(&mut self) {
self.deref_mut().twos_complement();
}
fn randomize<R: Rand>(&mut self, rand: &mut R) {
self.deref_mut().randomize(rand);
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "std")]
use {
super::{ValueInput, ValueMutRefInput},
crate::mutators::numeric::Numeric,
alloc::fmt::Debug,
std::any::type_name,
};
#[cfg(feature = "std")]
macro_rules! apply_all_ops {
($prep:stmt, $value:expr, $type:ty, $check_twos_complement:expr) => {{
$prep
let mut j = $value;
j.flip_all_bits();
$prep
assert_ne!(j, $value, "{:?}.flip_all_bits() for {}", j, type_name::<$type>());
$prep
let mut j = $value;
j.wrapping_inc();
$prep
assert_ne!(j, $value, "{:?}.wrapping_inc() for {}", j, type_name::<$type>());
$prep
let mut j = $value;
j.wrapping_dec();
$prep
assert_ne!(j, $value, "{:?}.wrapping_dec() for {}", j, type_name::<$type>());
$prep
let mut j = $value;
j.twos_complement();
if $check_twos_complement {
$prep
assert_ne!(j, $value, "{:?}.twos_complement() for {}", j, type_name::<$type>());
}
$prep
let mut j = $value;
j.flip_bit_at(0);
$prep
assert_ne!(j, $value, "{:?}.flip_bit_at(0) for {}", j, type_name::<$type>());
$prep
let mut j = $value;
j.flip_bit_at(size_of::<I>() * 8 - 1);
$prep
assert_ne!(j, $value, "{:?}.flip_bit_at({}) for {}", j, size_of::<I>() * 8 - 1, type_name::<$type>());
}};
}
#[cfg(feature = "std")]
fn take_numeric<I: Numeric + Clone + PartialEq + Debug>(i: &I, check_twos_complement: bool) {
apply_all_ops!({}, i.clone(), I, check_twos_complement);
apply_all_ops!(
{},
ValueInput::from(i.clone()),
ValueInput<I>,
check_twos_complement
);
apply_all_ops!(
let mut i_clone = i.clone(),
ValueMutRefInput::from(&mut i_clone),
ValueMutRefInput<'_, I>,
check_twos_complement
);
}
#[test]
#[cfg(feature = "std")] // type detection for better error messages, running with std is sufficient
fn compiles() {
// twos complement doesn't change anything on the min value of numeric types
take_numeric(&u8::MIN, false);
take_numeric(&u16::MIN, false);
take_numeric(&u32::MIN, false);
take_numeric(&u64::MIN, false);
take_numeric(&u128::MIN, false);
take_numeric(&usize::MIN, false);
take_numeric(&i8::MIN, false);
take_numeric(&i16::MIN, false);
take_numeric(&i32::MIN, false);
take_numeric(&i64::MIN, false);
take_numeric(&i128::MIN, false);
take_numeric(&isize::MIN, false);
take_numeric(&u8::MAX, true);
take_numeric(&u16::MAX, true);
take_numeric(&u32::MAX, true);
take_numeric(&u64::MAX, true);
take_numeric(&u128::MAX, true);
take_numeric(&usize::MAX, true);
take_numeric(&i8::MAX, true);
take_numeric(&i16::MAX, true);
take_numeric(&i32::MAX, true);
take_numeric(&i64::MAX, true);
take_numeric(&i128::MAX, true);
take_numeric(&isize::MAX, true);
}
}

View File

@ -13,6 +13,8 @@ use serde::{Deserialize, Serialize};
pub use token_mutations::*;
pub mod havoc_mutations;
pub use havoc_mutations::*;
pub mod numeric;
pub use numeric::{int_mutators, mapped_int_mutators};
pub mod encoded_mutations;
pub use encoded_mutations::*;
pub mod mopt_mutator;

View File

@ -1749,7 +1749,7 @@ mod tests {
}
let mut gaps = 0;
let mut range = 0..10;
let mut iter = mutated.bytes.iter().copied();
let mut iter = mutated.as_ref().iter().copied();
while let Some(expected) = range.next() {
if let Some(last) = iter.next() {
if expected != last {
@ -1768,9 +1768,11 @@ mod tests {
}
}
assert_eq!(
gaps, 1,
gaps,
1,
"{:?} should have exactly one gap, found {}",
mutated.bytes, gaps
mutated.as_ref(),
gaps
);
}
@ -1802,20 +1804,20 @@ mod tests {
continue;
}
let mut expansion = 0;
let mut expansion_len = base.bytes.len();
for (i, value) in mutated.bytes.iter().copied().enumerate() {
let mut expansion_len = base.as_ref().len();
for (i, value) in mutated.as_ref().iter().copied().enumerate() {
if i as u8 != value {
expansion = value as usize;
expansion_len = i - expansion;
break;
}
}
assert_eq!(mutated.bytes.len(), base.bytes.len() + expansion_len);
assert_eq!(mutated.as_ref().len(), base.as_ref().len() + expansion_len);
for (expected, value) in (0..(expansion + expansion_len))
.chain(expansion..base.bytes.len())
.zip(mutated.bytes)
.chain(expansion..base.as_ref().len())
.zip(mutated.as_ref())
{
assert_eq!(expected as u8, value);
assert_eq!(expected as u8, *value);
}
for i in (expansion..).take(expansion_len) {
counts[i] += 1;
@ -1851,19 +1853,19 @@ mod tests {
continue;
}
let mut inserted = 0;
for (i, value) in mutated.bytes.iter().copied().enumerate() {
for (i, value) in mutated.as_ref().iter().copied().enumerate() {
if i as u8 != value {
inserted = value;
break;
}
}
assert!(mutated.bytes.len() <= base.bytes.len() + 16);
assert!(mutated.as_ref().len() <= base.as_ref().len() + 16);
assert_eq!(
bytecount::count(&mutated.bytes, inserted),
mutated.bytes.len() - base.bytes.len() + 1
bytecount::count(mutated.as_ref(), inserted),
mutated.as_ref().len() - base.as_ref().len() + 1
);
counts[inserted as usize] += 1;
insertions[mutated.bytes.len() - base.bytes.len() - 1] += 1;
insertions[mutated.as_ref().len() - base.as_ref().len() - 1] += 1;
}
let average = counts.iter().copied().sum::<usize>() / counts.len();
@ -1901,22 +1903,22 @@ mod tests {
continue;
}
let mut inserted = 10;
for (i, value) in mutated.bytes.iter().copied().enumerate() {
for (i, value) in mutated.as_ref().iter().copied().enumerate() {
if i as u8 != value {
inserted = value;
break;
}
}
assert!(mutated.bytes.len() <= base.bytes.len() + 16);
let offset = usize::from((inserted as usize) < base.bytes.len());
assert!(mutated.as_ref().len() <= base.as_ref().len() + 16);
let offset = usize::from((inserted as usize) < base.as_ref().len());
assert_eq!(
bytecount::count(&mutated.bytes, inserted),
mutated.bytes.len() - base.bytes.len() + offset,
bytecount::count(mutated.as_ref(), inserted),
mutated.as_ref().len() - base.as_ref().len() + offset,
"{:?}",
mutated.bytes
mutated.as_ref()
);
counts[inserted as usize] += 1;
insertions[mutated.bytes.len() - base.bytes.len() - 1] += 1;
insertions[mutated.as_ref().len() - base.as_ref().len() - 1] += 1;
}
let average = counts.iter().copied().sum::<usize>() / counts.len();

View File

@ -0,0 +1,478 @@
//! Mutators for integer-style inputs
use alloc::borrow::Cow;
use libafl_bolts::{
rands::Rand,
tuples::{Map as _, Merge},
Error, Named,
};
use tuple_list::{tuple_list, tuple_list_type};
use super::{
MappedInputFunctionMappingMutator, MutationResult, Mutator,
ToMappedInputFunctionMappingMutatorMapper,
};
use crate::{
corpus::Corpus,
inputs::value::ValueMutRefInput,
random_corpus_id_with_disabled,
state::{HasCorpus, HasRand},
};
/// All mutators for integer-like inputs
pub type IntMutatorsType = tuple_list_type!(
BitFlipMutator,
NegateMutator,
IncMutator,
DecMutator,
TwosComplementMutator,
RandMutator,
CrossoverMutator
);
type IntMutatorsCrossoverType = tuple_list_type!(CrossoverMutator);
type MappedIntMutatorsCrossoverType<F> = tuple_list_type!(MappedCrossoverMutator<F>);
type IntMutatorsNoCrossoverType = tuple_list_type!(
BitFlipMutator,
NegateMutator,
IncMutator,
DecMutator,
TwosComplementMutator,
RandMutator,
);
/// Mutators for integer-like inputs without crossover mutations
#[must_use]
pub fn int_mutators_no_crossover() -> IntMutatorsNoCrossoverType {
tuple_list!(
BitFlipMutator,
NegateMutator,
IncMutator,
DecMutator,
TwosComplementMutator,
RandMutator,
)
}
/// Mutators for integer-like inputs that implement some form of crossover
#[must_use]
pub fn int_mutators_crossover() -> IntMutatorsCrossoverType {
tuple_list!(CrossoverMutator)
}
/// Mutators for integer-like inputs that implement some form of crossover with a mapper to extract the crossed over information.
#[must_use]
pub fn mapped_int_mutators_crossover<F>(input_mapper: F) -> MappedIntMutatorsCrossoverType<F> {
tuple_list!(MappedCrossoverMutator::new(input_mapper))
}
/// Mutators for integer-like inputs
///
/// Modelled after the applicable mutators from [`super::havoc_mutations::havoc_mutations`]
#[must_use]
pub fn int_mutators() -> IntMutatorsType {
int_mutators_no_crossover().merge(int_mutators_crossover())
}
/// Mapped mutators for integer-like inputs
pub type MappedIntMutatorsType<F1, F2, I> = tuple_list_type!(
MappedInputFunctionMappingMutator<BitFlipMutator,F1,I>,
MappedInputFunctionMappingMutator<NegateMutator,F1,I>,
MappedInputFunctionMappingMutator<IncMutator,F1,I>,
MappedInputFunctionMappingMutator<DecMutator,F1,I>,
MappedInputFunctionMappingMutator<TwosComplementMutator,F1,I>,
MappedInputFunctionMappingMutator<RandMutator,F1,I>,
MappedInputFunctionMappingMutator<MappedCrossoverMutator<F2>,F1,I>
);
/// Mapped mutators for integer-like inputs
///
/// Modelled after the applicable mutators from [`super::havoc_mutations::havoc_mutations`]
pub fn mapped_int_mutators<F1, F2, IO, II>(
current_input_mapper: F1,
input_from_corpus_mapper: F2,
) -> MappedIntMutatorsType<F1, F2, II>
where
F1: Clone + FnMut(IO) -> II,
{
int_mutators_no_crossover()
.merge(mapped_int_mutators_crossover(input_from_corpus_mapper))
.map(ToMappedInputFunctionMappingMutatorMapper::new(
current_input_mapper,
))
}
/// Functionality required for Numeric Mutators (see [`int_mutators`])
pub trait Numeric {
/// Flip all bits of the number.
fn flip_all_bits(&mut self);
/// Flip the bit at the specified offset.
///
/// # Safety
///
/// Panics if the `offset` is out of bounds for the type
fn flip_bit_at(&mut self, offset: usize);
/// Increment the number by one, wrapping around on overflow.
fn wrapping_inc(&mut self);
/// Decrement the number by one, wrapping around on underflow.
fn wrapping_dec(&mut self);
/// Compute the two's complement of the number.
fn twos_complement(&mut self);
/// Randomizes the value using the provided random number generator.
fn randomize<R: Rand>(&mut self, rand: &mut R);
}
// Macro to implement the Numeric trait for multiple integer types a u64 can be cast to
macro_rules! impl_numeric_cast_randomize {
($($t:ty)*) => ($(
impl Numeric for $t {
#[inline]
fn flip_all_bits(&mut self) {
*self = !*self;
}
#[inline]
fn flip_bit_at(&mut self, offset: usize) {
*self ^= 1 << offset;
}
#[inline]
fn wrapping_inc(&mut self) {
*self = self.wrapping_add(1);
}
#[inline]
fn wrapping_dec(&mut self) {
*self = self.wrapping_sub(1);
}
#[inline]
fn twos_complement(&mut self) {
*self = self.wrapping_neg();
}
#[inline]
#[allow(trivial_numeric_casts, clippy::cast_possible_wrap)]
fn randomize<R: Rand>(&mut self, rand: &mut R) {
*self = rand.next() as $t;
}
}
)*)
}
impl_numeric_cast_randomize!( u8 u16 u32 u64 usize i8 i16 i32 i64 isize );
// Macro to implement the Numeric trait for multiple integer types a u64 cannot be cast to
macro_rules! impl_numeric_128_bits_randomize {
($($t:ty)*) => ($(
impl Numeric for $t {
#[inline]
fn flip_all_bits(&mut self) {
*self = !*self;
}
#[inline]
fn flip_bit_at(&mut self, offset: usize) {
*self ^= 1 << offset;
}
#[inline]
fn wrapping_inc(&mut self) {
*self = self.wrapping_add(1);
}
#[inline]
fn wrapping_dec(&mut self) {
*self = self.wrapping_sub(1);
}
#[inline]
fn twos_complement(&mut self) {
*self = self.wrapping_neg();
}
#[inline]
#[allow(trivial_numeric_casts, clippy::cast_possible_wrap)]
fn randomize<R: Rand>(&mut self, rand: &mut R) {
*self = (u128::from(rand.next()) << 64 | u128::from(rand.next())) as $t;
}
}
)*)
}
// Apply the macro to all desired integer types
impl_numeric_128_bits_randomize! { u128 i128 }
/// Bitflip mutation for integer-like inputs
#[derive(Debug)]
pub struct BitFlipMutator;
impl<I, S> Mutator<I, S> for BitFlipMutator
where
S: HasRand,
I: Numeric,
{
fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
let offset = state.rand_mut().choose(0..size_of::<I>()).unwrap();
input.flip_bit_at(offset);
Ok(MutationResult::Mutated)
}
}
impl Named for BitFlipMutator {
fn name(&self) -> &Cow<'static, str> {
&Cow::Borrowed("BitFlipMutator")
}
}
/// Negate mutation for integer-like inputs, i.e. flip all bits
#[derive(Debug)]
pub struct NegateMutator;
impl<I, S> Mutator<I, S> for NegateMutator
where
I: Numeric,
{
fn mutate(&mut self, _state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
input.flip_all_bits();
Ok(MutationResult::Mutated)
}
}
impl Named for NegateMutator {
fn name(&self) -> &Cow<'static, str> {
&Cow::Borrowed("ByteFlipMutator")
}
}
/// Increment mutation for integer-like inputs. Wraps on overflows.
#[derive(Debug)]
pub struct IncMutator;
impl<I, S> Mutator<I, S> for IncMutator
where
I: Numeric,
{
fn mutate(&mut self, _state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
input.wrapping_inc();
Ok(MutationResult::Mutated)
}
}
impl Named for IncMutator {
fn name(&self) -> &Cow<'static, str> {
&Cow::Borrowed("IncMutator")
}
}
/// Decrement mutation for integer-like inputs. Wraps on underflow.
#[derive(Debug)]
pub struct DecMutator;
impl<I, S> Mutator<I, S> for DecMutator
where
I: Numeric,
{
fn mutate(&mut self, _state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
input.wrapping_dec();
Ok(MutationResult::Mutated)
}
}
impl Named for DecMutator {
fn name(&self) -> &Cow<'static, str> {
&Cow::Borrowed("DecMutator")
}
}
/// Two's complement mutation for integer-like inputs
#[derive(Debug)]
pub struct TwosComplementMutator;
impl<I, S> Mutator<I, S> for TwosComplementMutator
where
I: Numeric,
{
fn mutate(&mut self, _state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
input.twos_complement();
Ok(MutationResult::Mutated)
}
}
impl Named for TwosComplementMutator {
fn name(&self) -> &Cow<'static, str> {
&Cow::Borrowed("NegMutator")
}
}
/// Randomize mutation for integer-like inputs
#[derive(Debug)]
pub struct RandMutator;
impl<I, S> Mutator<I, S> for RandMutator
where
S: HasRand,
I: Numeric,
{
fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
// set to random data byte-wise since the RNGs don't work for all numeric types
input.randomize(state.rand_mut());
Ok(MutationResult::Mutated)
}
}
impl Named for RandMutator {
fn name(&self) -> &Cow<'static, str> {
&Cow::Borrowed("RandMutator")
}
}
/// Crossover mutation for integer-like inputs
#[derive(Debug)]
pub struct CrossoverMutator;
impl<I, S> Mutator<I, S> for CrossoverMutator
where
S: HasRand + HasCorpus,
S::Corpus: Corpus<Input = I>,
I: Copy,
{
fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
let id = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut());
if state.corpus().current().is_some_and(|cur| cur == id) {
return Ok(MutationResult::Skipped);
}
let other_testcase = state.corpus().get_from_all(id)?.borrow_mut();
*input = *other_testcase.input().as_ref().unwrap();
Ok(MutationResult::Mutated)
}
}
impl Named for CrossoverMutator {
fn name(&self) -> &Cow<'static, str> {
&Cow::Borrowed("CrossoverMutator")
}
}
/// Crossover mutation for integer-like inputs with custom state extraction function
#[derive(Debug)]
pub struct MappedCrossoverMutator<F> {
input_mapper: F,
}
impl<F> MappedCrossoverMutator<F> {
/// Create a new [`MappedCrossoverMutator`]
pub fn new(input_mapper: F) -> Self {
Self { input_mapper }
}
}
impl<I, S, F> Mutator<ValueMutRefInput<'_, I>, S> for MappedCrossoverMutator<F>
where
S: HasRand + HasCorpus,
for<'b> F: Fn(&'b <S::Corpus as Corpus>::Input) -> &'b I,
I: Clone,
{
fn mutate(
&mut self,
state: &mut S,
input: &mut ValueMutRefInput<'_, I>,
) -> Result<MutationResult, Error> {
let id = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut());
if state.corpus().current().is_some_and(|cur| cur == id) {
return Ok(MutationResult::Skipped);
}
let other_testcase = state.corpus().get_from_all(id)?.borrow_mut();
let other_input = other_testcase.input().as_ref().unwrap();
let mapped_input = (self.input_mapper)(other_input).clone();
**input = mapped_input;
Ok(MutationResult::Mutated)
}
}
impl<F> Named for MappedCrossoverMutator<F> {
fn name(&self) -> &Cow<'static, str> {
&Cow::Borrowed("MappedCrossoverMutator")
}
}
#[cfg(test)]
mod tests {
use libafl_bolts::{
rands::{Rand, XkcdRand},
tuples::IntoVec as _,
};
use serde::{Deserialize, Serialize};
use super::{int_mutators, Numeric};
use crate::{
corpus::{Corpus as _, InMemoryCorpus, Testcase},
inputs::value::I16Input,
mutators::MutationResult,
state::StdState,
};
#[test]
fn randomized() {
const RAND_NUM: u64 = 0xAAAAAAAAAAAAAAAA; // 0b10101010..
#[derive(Serialize, Deserialize, Debug)]
struct FixedRand;
impl Rand for FixedRand {
fn set_seed(&mut self, _seed: u64) {}
fn next(&mut self) -> u64 {
RAND_NUM
}
}
let rand = &mut FixedRand;
let mut i = 0_u8;
Numeric::randomize(&mut i, rand);
assert_eq!(0xAA, i);
let mut i = 0_u128;
Numeric::randomize(&mut i, rand);
assert_eq!(((u128::from(RAND_NUM) << 64) | u128::from(RAND_NUM)), i);
let mut i = 0_i16;
Numeric::randomize(&mut i, rand);
assert_eq!(-0b101010101010110, i); // two's complement
}
#[test]
fn all_mutate_owned() {
let mut corpus = InMemoryCorpus::new();
corpus.add(Testcase::new(42_i16.into())).unwrap();
let mut state = StdState::new(
XkcdRand::new(),
corpus,
InMemoryCorpus::new(),
&mut (),
&mut (),
)
.unwrap();
let mutators = int_mutators().into_vec();
for mut m in mutators {
let mut input: I16Input = 1_i16.into();
assert_eq!(
MutationResult::Mutated,
m.mutate(&mut state, &mut input).unwrap(),
"Errored with {}",
m.name()
);
assert_ne!(1, input.into_inner(), "Errored with {}", m.name());
}
}
}

View File

@ -146,7 +146,7 @@ use alloc::{borrow::Cow, vec::Vec};
#[cfg(all(not(feature = "xxh3"), feature = "alloc"))]
use core::hash::BuildHasher;
#[cfg(any(feature = "xxh3", feature = "alloc"))]
use core::hash::Hasher;
use core::hash::{Hash, Hasher};
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(all(unix, feature = "std"))]
@ -265,6 +265,21 @@ pub fn hash_std(input: &[u8]) -> u64 {
}
}
/// Hashes the input with a given hash
///
/// Hashes the input with a given hash, depending on features:
/// [`xxh3_64`](https://docs.rs/xxhash-rust/latest/xxhash_rust/xxh3/fn.xxh3_64.html)
/// if the `xxh3` feature is used, /// else [`ahash`](https://docs.rs/ahash/latest/ahash/).
///
/// If you have access to a `&[u8]` directly, [`hash_std`] may provide better performance
#[cfg(any(feature = "xxh3", feature = "alloc"))]
#[must_use]
pub fn generic_hash_std<I: Hash>(input: &I) -> u64 {
let mut hasher = hasher_std();
input.hash(&mut hasher);
hasher.finish()
}
/// Main error struct for `LibAFL`
#[derive(Debug)]
pub enum Error {