From 9ad3c21d374eaf5a4be1ead7f407da60f6b6b49c Mon Sep 17 00:00:00 2001 From: Valentin Huber Date: Thu, 2 Oct 2025 10:39:53 +0200 Subject: [PATCH 1/4] Add StateAwareMappingMutator --- crates/libafl/src/mutators/mapping.rs | 197 ++++++++++++++++++++++++-- 1 file changed, 186 insertions(+), 11 deletions(-) diff --git a/crates/libafl/src/mutators/mapping.rs b/crates/libafl/src/mutators/mapping.rs index bdc064348dd..ead08d0a49a 100644 --- a/crates/libafl/src/mutators/mapping.rs +++ b/crates/libafl/src/mutators/mapping.rs @@ -5,6 +5,7 @@ use libafl_bolts::{Named, tuples::MappingFunctor}; use crate::{ Error, + corpus::CorpusId, mutators::{MutationResult, Mutator}, }; @@ -12,6 +13,8 @@ use crate::{ /// /// Allows using [`Mutator`]s for a certain type on (parts of) other input types that can be mapped to this type. /// +/// For a more flexible alternative, which allows access to `state`, see [`StateAwareMappingMutator`]. +/// /// # Example #[cfg_attr(feature = "std", doc = " ```")] #[cfg_attr(not(feature = "std"), doc = " ```ignore")] @@ -74,11 +77,7 @@ where self.inner.mutate(state, (self.mapper)(input)) } #[inline] - fn post_exec( - &mut self, - state: &mut S, - new_corpus_id: Option, - ) -> Result<(), Error> { + fn post_exec(&mut self, state: &mut S, new_corpus_id: Option) -> Result<(), Error> { self.inner.post_exec(state, new_corpus_id) } } @@ -89,7 +88,7 @@ impl Named for MappingMutator { } } -/// Mapper to use to map a [`tuple_list`] of [`Mutator`]s using [`ToMappingMutator`]s. +/// Mapper to use to map a [`tuple_list`] of [`Mutator`]s using [`MappingMutator`]s. /// /// See the explanation of [`MappingMutator`] for details. /// @@ -211,11 +210,7 @@ where } } #[inline] - fn post_exec( - &mut self, - state: &mut S, - new_corpus_id: Option, - ) -> Result<(), Error> { + fn post_exec(&mut self, state: &mut S, new_corpus_id: Option) -> Result<(), Error> { self.inner.post_exec(state, new_corpus_id) } } @@ -272,3 +267,183 @@ where OptionalMutator::new(from) } } + +/// Mapping [`Mutator`] using a function returning a reference. +/// +/// Allows using [`Mutator`]s for a certain type on (parts of) other input types that can be mapped to this type. +/// +/// Provides access to the state. If [`Option::None`] is returned from the mapping function, the mutator is returning [`MutationResult::Skipped`]. +/// +/// # Example +#[cfg_attr(feature = "std", doc = " ```")] +#[cfg_attr(not(feature = "std"), doc = " ```ignore")] +/// use std::vec::Vec; +/// +/// use libafl::{ +/// mutators::{ByteIncMutator, MutationResult, Mutator, StateAwareMappingMutator}, +/// state::{HasRand, NopState}, +/// }; +/// use libafl_bolts::rands::Rand as _; +/// +/// #[derive(Debug, PartialEq)] +/// struct CustomInput(Vec); +/// +/// impl CustomInput { +/// pub fn possibly_vec<'a, S: HasRand>( +/// &'a mut self, +/// state: &'a mut S, +/// ) -> (Option<&'a mut Vec>, &'a mut S) { +/// // we have access to the state +/// if state.rand_mut().coinflip(0.5) { +/// // If this input cannot be mutated with the outer mutator, return None +/// // e.g. because the input doesn't contain the field this mutator is supposed to mutate +/// (None, state) +/// } else { +/// // else, return the type that the outer mutator can mutate +/// (Some(&mut self.0), state) +/// } +/// } +/// } +/// +/// // construct a mutator that works on &mut Vec (since it impls `HasMutatorBytes`) +/// let inner = ByteIncMutator::new(); +/// // construct a mutator that works on &mut CustomInput +/// let mut outer = StateAwareMappingMutator::new(CustomInput::possibly_vec, inner); +/// +/// let mut input = CustomInput(vec![1]); +/// +/// let mut state: NopState = NopState::new(); +/// let res = outer.mutate(&mut state, &mut input).unwrap(); +/// if res == MutationResult::Mutated { +/// assert_eq!(input, CustomInput(vec![2],)); +/// } else { +/// assert_eq!(input, CustomInput(vec![1],)); +/// } +/// ``` +#[derive(Debug)] +pub struct StateAwareMappingMutator { + mapper: F, + inner: M, + name: Cow<'static, str>, +} + +impl StateAwareMappingMutator { + /// Creates a new [`StateAwareMappingMutator`] + pub fn new(mapper: F, inner: M) -> Self + where + M: Named, + { + let name = Cow::Owned(format!("StateAwareMappingMutator<{}>", inner.name())); + Self { + mapper, + inner, + name, + } + } +} + +impl Mutator for StateAwareMappingMutator +where + F: for<'a> FnMut(&'a mut IO, &'a mut S) -> (Option<&'a mut II>, &'a mut S), + M: Mutator, +{ + fn mutate(&mut self, state: &mut S, input: &mut IO) -> Result { + let (mapped, state) = (self.mapper)(input, state); + match mapped { + Some(mapped) => self.inner.mutate(state, mapped), + None => Ok(MutationResult::Skipped), + } + } + #[inline] + fn post_exec(&mut self, state: &mut S, new_corpus_id: Option) -> Result<(), Error> { + self.inner.post_exec(state, new_corpus_id) + } +} + +impl Named for StateAwareMappingMutator { + fn name(&self) -> &Cow<'static, str> { + &self.name + } +} + +/// Mapper to use to map a [`tuple_list`] of [`Mutator`]s using [`StateAwareMappingMutator`]s. +/// +/// See the explanation of [`StateAwareMappingMutator`] for details. +/// +/// # Example +#[cfg_attr(feature = "std", doc = " ```")] +#[cfg_attr(not(feature = "std"), doc = " ```ignore")] +/// use std::vec::Vec; +/// +/// use libafl::{ +/// mutators::{ +/// ByteIncMutator, MutationResult, MutatorsTuple, ToStateAwareMappingMutator, +/// }, +/// state::{HasRand, NopState}, +/// }; +/// +/// use libafl_bolts::{ +/// tuples::{tuple_list, Map}, +/// rands::Rand as _, +/// }; +/// +/// #[derive(Debug, PartialEq)] +/// struct CustomInput(Vec); +/// +/// impl CustomInput { +/// pub fn possibly_vec<'a, S: HasRand>( +/// &'a mut self, +/// state: &'a mut S, +/// ) -> (Option<&'a mut Vec>, &'a mut S) { +/// // we have access to the state +/// if state.rand_mut().coinflip(0.5) { +/// // If this input cannot be mutated with the outer mutator, return None +/// // e.g. because the input doesn't contain the field this mutator is supposed to mutate +/// (None, state) +/// } else { +/// // else, return the type that the outer mutator can mutate +/// (Some(&mut self.0), state) +/// } +/// } +/// } +/// +/// // construct a mutator that works on &mut Vec (since it impls `HasMutatorBytes`) +/// let mutators = tuple_list!(ByteIncMutator::new(), ByteIncMutator::new()); +/// // construct a mutator that works on &mut CustomInput +/// let mut mapped_mutators = +/// mutators.map(ToStateAwareMappingMutator::new(CustomInput::possibly_vec)); +/// +/// let mut input = CustomInput(vec![1]); +/// +/// let mut state: NopState = NopState::new(); +/// let res = mapped_mutators.mutate_all(&mut state, &mut input).unwrap(); +/// if res == MutationResult::Mutated { +/// // no way of knowing if either or both mutated +/// assert!(input.0 == vec![2] || input.0 == vec![3]); +/// } else { +/// assert_eq!(input, CustomInput(vec![1],)); +/// } +/// ``` +#[derive(Debug)] +pub struct ToStateAwareMappingMutator { + mapper: F, +} + +impl ToStateAwareMappingMutator { + /// Creates a new [`ToStateAwareMappingMutator`] + pub fn new(mapper: F) -> Self { + Self { mapper } + } +} + +impl MappingFunctor for ToStateAwareMappingMutator +where + F: Clone, + M: Named, +{ + type Output = StateAwareMappingMutator; + + fn apply(&mut self, from: M) -> Self::Output { + StateAwareMappingMutator::new(self.mapper.clone(), from) + } +} From 2cd1137d65dfa82e4174e7c552c859dfd1ed8506 Mon Sep 17 00:00:00 2001 From: Valentin Huber Date: Thu, 2 Oct 2025 10:55:17 +0200 Subject: [PATCH 2/4] Create easy-to-use adapter functions for StateAwareMappingMutator --- crates/libafl/src/mutators/havoc_mutations.rs | 29 ++++- crates/libafl/src/mutators/numeric.rs | 40 ++++-- .../baby_fuzzer_custom_input/Cargo.lock | 118 ++++++++++-------- 3 files changed, 124 insertions(+), 63 deletions(-) diff --git a/crates/libafl/src/mutators/havoc_mutations.rs b/crates/libafl/src/mutators/havoc_mutations.rs index f03b5e903be..b754f28d7b9 100644 --- a/crates/libafl/src/mutators/havoc_mutations.rs +++ b/crates/libafl/src/mutators/havoc_mutations.rs @@ -6,7 +6,7 @@ use libafl_bolts::{ }; use crate::mutators::{ - mapping::{ToMappingMutator, ToOptionalMutator}, + mapping::{ToMappingMutator, ToOptionalMutator, ToStateAwareMappingMutator}, mutations::{ BitFlipMutator, ByteAddMutator, ByteDecMutator, ByteFlipMutator, ByteIncMutator, ByteInterestingMutator, ByteNegMutator, ByteRandMutator, BytesCopyMutator, @@ -66,6 +66,12 @@ pub type MappedHavocMutationsType = map_tuple_list_type!( ToMappingMutator ); +/// Tuple type of the mutations that compose the Havoc mutator for state-aware-mapped input types +pub type StateAwareMappedHavocMutationsType = map_tuple_list_type!( + merge_tuple_list_type!(HavocMutationsNoCrossoverType, MappedHavocCrossoverType), + ToStateAwareMappingMutator +); + /// Tuple type of the mutations that compose the Havoc mutator for mapped input types, for optional byte array input parts pub type OptionMappedHavocMutationsType = map_tuple_list_type!( map_tuple_list_type!( @@ -165,6 +171,27 @@ where .map(ToMappingMutator::new(current_input_mapper)) } +/// Get the mutations that compose the Havoc mutator for state-aware-mapped input types +/// +/// Check the example fuzzer for details on how to use this. +/// Check the docs of [`libafl::mutators::mapping::StateAwareMappingMutator`] for how mapping works internally. +#[must_use] +pub fn state_aware_mapped_havoc_mutations<'a, F1, F2, IO1, IO2, II, O, S>( + current_input_mapper: F1, + input_from_corpus_mapper: F2, +) -> StateAwareMappedHavocMutationsType +where + F1: Clone + FnMut(&'a mut IO1, &'a mut S) -> (Option<&'a mut II>, &'a mut S), + F2: Clone + Fn(&IO2) -> &O, + II: 'a, + IO1: 'a, + S: 'a, +{ + havoc_mutations_no_crossover() + .merge(havoc_crossover_with_corpus_mapper(input_from_corpus_mapper)) + .map(ToStateAwareMappingMutator::new(current_input_mapper)) +} + /// Get the mutations that compose the Havoc mutator for mapped input types, for optional input parts /// /// Check the example fuzzer for details on how to use this. diff --git a/crates/libafl/src/mutators/numeric.rs b/crates/libafl/src/mutators/numeric.rs index f5ac9ba00f8..6665aac8a08 100644 --- a/crates/libafl/src/mutators/numeric.rs +++ b/crates/libafl/src/mutators/numeric.rs @@ -4,13 +4,13 @@ use alloc::borrow::Cow; use core::marker::PhantomData; use libafl_bolts::{ - Error, Named, + Error, Named, map_tuple_list_type, merge_tuple_list_type, rands::Rand, tuples::{Map as _, Merge}, }; use tuple_list::{tuple_list, tuple_list_type}; -use super::{MappingMutator, MutationResult, Mutator, ToMappingMutator}; +use super::{MutationResult, Mutator, ToMappingMutator, ToStateAwareMappingMutator}; use crate::{ corpus::Corpus, random_corpus_id_with_disabled, @@ -80,14 +80,15 @@ pub fn int_mutators() -> IntMutatorsType { } /// Mapped mutators for integer-like inputs -pub type MappedIntMutatorsType = tuple_list_type!( - MappingMutator, - MappingMutator, - MappingMutator, - MappingMutator, - MappingMutator, - MappingMutator, - MappingMutator,F1> +pub type MappedIntMutatorsType = map_tuple_list_type!( + merge_tuple_list_type!(IntMutatorsNoCrossoverType, MappedIntMutatorsCrossoverType), + ToMappingMutator +); + +/// State-aware Mapped mutators for integer-like inputs +pub type StateAwareMappedIntMutatorsType = map_tuple_list_type!( + merge_tuple_list_type!(IntMutatorsNoCrossoverType, MappedIntMutatorsCrossoverType), + ToStateAwareMappingMutator ); /// Mapped mutators for integer-like inputs @@ -104,6 +105,25 @@ where .merge(mapped_int_mutators_crossover(input_from_corpus_mapper)) .map(ToMappingMutator::new(current_input_mapper)) } + +/// State-awareMapped mutators for integer-like inputs +/// +/// Modelled after the applicable mutators from [`super::havoc_mutations::havoc_mutations`] +pub fn state_aware_mapped_int_mutators<'a, F1, F2, IO, II, S>( + current_input_mapper: F1, + input_from_corpus_mapper: F2, +) -> StateAwareMappedIntMutatorsType +where + F1: Clone + FnMut(&'a mut IO, &'a mut S) -> (Option<&'a mut II>, &'a mut S), + IO: 'a, + II: 'a, + S: 'a, +{ + int_mutators_no_crossover() + .merge(mapped_int_mutators_crossover(input_from_corpus_mapper)) + .map(ToStateAwareMappingMutator::new(current_input_mapper)) +} + /// Functionality required for Numeric Mutators (see [`int_mutators`]) pub trait Numeric { /// Flip all bits of the number. diff --git a/fuzzers/structure_aware/baby_fuzzer_custom_input/Cargo.lock b/fuzzers/structure_aware/baby_fuzzer_custom_input/Cargo.lock index df86e94728c..5831155735b 100644 --- a/fuzzers/structure_aware/baby_fuzzer_custom_input/Cargo.lock +++ b/fuzzers/structure_aware/baby_fuzzer_custom_input/Cargo.lock @@ -77,18 +77,29 @@ dependencies = [ [[package]] name = "bincode" -version = "1.3.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" dependencies = [ + "bincode_derive", "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", ] [[package]] name = "bitbybit" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb157f9753a7cddfcf4a4f5fed928fbf4ce1b7b64b6bcc121d7a9f95d698997b" +checksum = "ec187a89ab07e209270175faf9e07ceb2755d984954e58a2296e325ddece2762" dependencies = [ "arbitrary-int", "proc-macro2", @@ -160,9 +171,9 @@ checksum = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e" [[package]] name = "ctor" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4735f265ba6a1188052ca32d461028a7d1125868be18e287e756019da7607b5" +checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb" dependencies = [ "ctor-proc-macro", "dtor", @@ -170,24 +181,24 @@ dependencies = [ [[package]] name = "ctor-proc-macro" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" [[package]] name = "dtor" -version = "0.0.6" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" +checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" dependencies = [ "dtor-proc-macro", ] [[package]] name = "dtor-proc-macro" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" [[package]] name = "embedded-io" @@ -213,13 +224,13 @@ dependencies = [ [[package]] name = "fastbloom" -version = "0.9.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27cea6e7f512d43b098939ff4d5a5d6fe3db07971e1d05176fe26c642d33f5b8" +checksum = "18c1ddb9231d8554c2d6bdf4cfaabf0c59251658c68b6c95cd52dd0c513a912a" dependencies = [ "getrandom", + "libm", "siphasher", - "wide 0.7.32 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -289,7 +300,7 @@ dependencies = [ [[package]] name = "libafl" -version = "0.15.2" +version = "0.15.4" dependencies = [ "ahash", "arbitrary-int", @@ -325,7 +336,7 @@ dependencies = [ [[package]] name = "libafl_bolts" -version = "0.15.2" +version = "0.15.4" dependencies = [ "ahash", "backtrace", @@ -334,6 +345,7 @@ dependencies = [ "hashbrown", "hostname", "libafl_derive", + "libafl_wide", "libc", "log", "mach2", @@ -351,7 +363,6 @@ dependencies = [ "typeid", "uds", "uuid", - "wide 0.7.32 (git+https://github.com/Lokathor/wide?rev=71b5df0b2620da753836fafce5f99076181a49fe)", "winapi", "windows", "windows-result", @@ -360,13 +371,23 @@ dependencies = [ [[package]] name = "libafl_derive" -version = "0.15.2" +version = "0.15.4" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "libafl_wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f28d525f6e361b6cd55c0da5347027860a902d638d15194c16dc2f39a5ba9f" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "libc" version = "0.2.172" @@ -397,9 +418,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "mach2" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" dependencies = [ "libc", ] @@ -412,9 +433,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "meminterval" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f8614cf855d251be1c2138d330c04f134923fddec0dcfc8b6f58ac499bf248" +checksum = "8e0f9a537564310a87dc77d5c88a407e27dd0aa740e070f0549439cfcc68fcfd" dependencies = [ "num-traits", "serde", @@ -440,9 +461,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", @@ -606,9 +627,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -739,18 +760,18 @@ checksum = "141fb9f71ee586d956d7d6e4d5a9ef8e946061188520140f7591b668841d502e" [[package]] name = "typed-builder" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce63bcaf7e9806c206f7d7b9c1f38e0dce8bb165a80af0898161058b19248534" +checksum = "fef81aec2ca29576f9f6ae8755108640d0a86dd3161b2e8bca6cfa554e98f77d" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d8d828da2a3d759d3519cdf29a5bac49c77d039ad36d0782edadbf9cd5415b" +checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18" dependencies = [ "proc-macro2", "quote", @@ -784,11 +805,17 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom", "js-sys", @@ -802,6 +829,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -878,25 +911,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wide" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" -dependencies = [ - "bytemuck", - "safe_arch", -] - -[[package]] -name = "wide" -version = "0.7.32" -source = "git+https://github.com/Lokathor/wide?rev=71b5df0b2620da753836fafce5f99076181a49fe#71b5df0b2620da753836fafce5f99076181a49fe" -dependencies = [ - "bytemuck", - "safe_arch", -] - [[package]] name = "winapi" version = "0.3.9" From 76faaef3f67bbbe5d2b0360d6763e12d9538e09f Mon Sep 17 00:00:00 2001 From: Valentin Huber Date: Thu, 2 Oct 2025 11:29:47 +0200 Subject: [PATCH 3/4] Use StateAwareMappingMutator in example fuzzer --- .../baby_fuzzer_custom_input/src/input.rs | 40 ++++++++++ .../baby_fuzzer_custom_input/src/main.rs | 75 +++++++++++++++++-- 2 files changed, 110 insertions(+), 5 deletions(-) diff --git a/fuzzers/structure_aware/baby_fuzzer_custom_input/src/input.rs b/fuzzers/structure_aware/baby_fuzzer_custom_input/src/input.rs index 74ffe3f90fd..5c3bcccdbfc 100644 --- a/fuzzers/structure_aware/baby_fuzzer_custom_input/src/input.rs +++ b/fuzzers/structure_aware/baby_fuzzer_custom_input/src/input.rs @@ -23,7 +23,9 @@ use serde::{Deserialize, Serialize}; pub struct CustomInput { pub byte_array: Vec, pub optional_byte_array: Option>, + pub sometimes_hidden_byte_array: Vec, pub num: i16, + pub sometimes_hidden_num: i16, pub boolean: bool, } @@ -51,6 +53,23 @@ impl CustomInput { &self.optional_byte_array } + /// Sometimes returns a mutable reference to the sometimes-hidden byte array with logic depending on state + pub fn sometimes_hidden_byte_array_mut<'a, S: HasRand>( + &'a mut self, + state: &'a mut S, + ) -> (Option<&'a mut Vec>, &'a mut S) { + if state.rand_mut().coinflip(0.5) { + (Some(&mut self.sometimes_hidden_byte_array), state) + } else { + (None, state) + } + } + + /// Returns an immutable reference to the sometimes hidden byte array + pub fn sometimes_hidden_byte_array(&self) -> &Vec { + &self.sometimes_hidden_byte_array + } + /// Returns a mutable reference to the number pub fn num_mut(&mut self) -> &mut i16 { &mut self.num @@ -60,6 +79,23 @@ impl CustomInput { pub fn num(&self) -> &i16 { &self.num } + + /// Sometimes returns a mutable reference to the sometimes-hidden number with logic depending on state + pub fn sometimes_hidden_num_mut<'a, S: HasRand>( + &'a mut self, + state: &'a mut S, + ) -> (Option<&'a mut i16>, &'a mut S) { + if state.rand_mut().coinflip(0.5) { + (Some(&mut self.sometimes_hidden_num), state) + } else { + (None, state) + } + } + + /// Returns an immutable reference to the sometimes hidden number + pub fn sometimes_hidden_num(&self) -> &i16 { + &self.sometimes_hidden_num + } } /// A generator for [`CustomInput`] used in this example @@ -88,13 +124,17 @@ where .rand_mut() .coinflip(0.5) .then(|| generator.generate(state).unwrap().target_bytes().into()); + let sometimes_hidden_byte_array = generator.generate(state).unwrap().target_bytes().into(); let boolean = state.rand_mut().coinflip(0.5); let num = state.rand_mut().next() as i16; + let sometimes_hidden_num = state.rand_mut().next() as i16; Ok(CustomInput { byte_array, optional_byte_array, + sometimes_hidden_byte_array, num, + sometimes_hidden_num, boolean, }) } diff --git a/fuzzers/structure_aware/baby_fuzzer_custom_input/src/main.rs b/fuzzers/structure_aware/baby_fuzzer_custom_input/src/main.rs index 04d4c416a8c..bca74cfab79 100644 --- a/fuzzers/structure_aware/baby_fuzzer_custom_input/src/main.rs +++ b/fuzzers/structure_aware/baby_fuzzer_custom_input/src/main.rs @@ -34,7 +34,7 @@ use libafl_bolts::{ use { libafl::mutators::{ havoc_mutations::{havoc_crossover_with_corpus_mapper, havoc_mutations_no_crossover}, - mapping::{ToMappingMutator, ToOptionalMutator}, + mapping::{ToMappingMutator, ToOptionalMutator, ToStateAwareMappingMutator}, numeric::{int_mutators_no_crossover, mapped_int_mutators_crossover}, }, libafl_bolts::tuples::Map, @@ -145,8 +145,18 @@ pub fn main() { .expect("Failed to generate the initial corpus"); #[cfg(feature = "simple_interface")] - let (mapped_mutators, optional_mapped_mutators, int_mutators) = { + let ( + mapped_mutators, + optional_mapped_mutators, + sometimes_hidden_mapped_mutators, + int_mutators, + sometimes_hidden_int_mutators, + ) = { // Creating mutators that will operate on input.byte_array + + use libafl::mutators::{ + numeric::state_aware_mapped_int_mutators, state_aware_mapped_havoc_mutations, + }; let mapped_mutators = mapped_havoc_mutations(CustomInput::byte_array_mut, CustomInput::byte_array); @@ -156,12 +166,38 @@ pub fn main() { CustomInput::optional_byte_array, ); + // Creating mutators that will operate on input.sometimes_hidden_byte_array + let sometimes_hidden_mapped_mutators = state_aware_mapped_havoc_mutations( + CustomInput::sometimes_hidden_byte_array_mut, + CustomInput::sometimes_hidden_byte_array, + ); + + // Creating mutators that will operate on input.num let int_mutators = mapped_int_mutators(CustomInput::num_mut, CustomInput::num); - (mapped_mutators, optional_mapped_mutators, int_mutators) + + // Creating mutators that will operate on input.sometimes_hidden_num + let sometimes_hidden_int_mutators = state_aware_mapped_int_mutators( + CustomInput::sometimes_hidden_num_mut, + CustomInput::sometimes_hidden_num, + ); + + ( + mapped_mutators, + optional_mapped_mutators, + sometimes_hidden_mapped_mutators, + int_mutators, + sometimes_hidden_int_mutators, + ) }; #[cfg(not(feature = "simple_interface"))] - let (mapped_mutators, optional_mapped_mutators, int_mutators) = { + let ( + mapped_mutators, + optional_mapped_mutators, + sometimes_hidden_mapped_mutators, + int_mutators, + sometimes_hidden_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)) @@ -175,11 +211,36 @@ pub fn main() { .map(ToOptionalMutator) .map(ToMappingMutator::new(CustomInput::optional_byte_array_mut)); + // Creating mutators that will operate on input.sometimes_hidden_byte_array + let sometimes_hidden_mapped_mutators = havoc_mutations_no_crossover() + .merge(havoc_crossover_with_corpus_mapper( + CustomInput::sometimes_hidden_byte_array, + )) + .map(ToStateAwareMappingMutator::new( + CustomInput::sometimes_hidden_byte_array_mut, + )); + // Creating mutators that will operate on input.num let int_mutators = int_mutators_no_crossover() .merge(mapped_int_mutators_crossover(CustomInput::num)) .map(ToMappingMutator::new(CustomInput::num_mut)); - (mapped_mutators, optional_mapped_mutators, int_mutators) + + // Creating mutators that will operate on input.sometimes_hidden_num + let sometimes_hidden_int_mutators = int_mutators_no_crossover() + .merge(mapped_int_mutators_crossover( + CustomInput::sometimes_hidden_num, + )) + .map(ToStateAwareMappingMutator::new( + CustomInput::sometimes_hidden_num_mut, + )); + + ( + mapped_mutators, + optional_mapped_mutators, + sometimes_hidden_mapped_mutators, + int_mutators, + sometimes_hidden_int_mutators, + ) }; // Merging multiple lists of mutators that mutate a sub-part of the custom input @@ -189,8 +250,12 @@ 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 sometimes hidden byte array + .merge(sometimes_hidden_mapped_mutators) // Then, mutators for the number .merge(int_mutators) + // Then, mutators for the sometimes hidden number + .merge(sometimes_hidden_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 From 4348467262d8828532f168957529f931bcb76bc3 Mon Sep 17 00:00:00 2001 From: Valentin Huber Date: Thu, 2 Oct 2025 11:44:47 +0200 Subject: [PATCH 4/4] Fix link in comments --- crates/libafl/src/mutators/havoc_mutations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/libafl/src/mutators/havoc_mutations.rs b/crates/libafl/src/mutators/havoc_mutations.rs index b754f28d7b9..3cfba015822 100644 --- a/crates/libafl/src/mutators/havoc_mutations.rs +++ b/crates/libafl/src/mutators/havoc_mutations.rs @@ -174,7 +174,7 @@ where /// Get the mutations that compose the Havoc mutator for state-aware-mapped input types /// /// Check the example fuzzer for details on how to use this. -/// Check the docs of [`libafl::mutators::mapping::StateAwareMappingMutator`] for how mapping works internally. +/// Check the docs of [`crate::mutators::mapping::StateAwareMappingMutator`] for how mapping works internally. #[must_use] pub fn state_aware_mapped_havoc_mutations<'a, F1, F2, IO1, IO2, II, O, S>( current_input_mapper: F1,