Repository: AFLplusplus/LibAFL (language: rust)

Issue:
Constraints regarding `BitFlipMutator` inputs.
Hi LibAFL.

Before all, I'm using libAFL, and I'm very amazed by the work you all have done here. I wanted to say, was long overdue. :) 

I have a question regarding the [`BitFlipMutator`]. To my understanding, it is used for flipping bits from byte buffers:

> Bitflip mutation for inputs with a bytes vector.

Looks handy, but I don't understand why the input type must implement the [`HasMutatorBytes`] trait:

https://github.com/AFLplusplus/LibAFL/blob/cfe051e5d9d3f7ec703a1cfeb4330db23df1c748/libafl/src/mutators/mutations.rs#L129-L133

[`HasMutatorBytes`] requires the following methods:

https://github.com/AFLplusplus/LibAFL/blob/c9531aa2424a8b81bdb1b33bc6d23eba98523fd9/libafl/src/inputs/bytes.rs#L35-L56

In my opinion, these methods shouldn't be required for inputs of [`BitFlipMutator`] since - again to my understanding - this mutator is solely going to flip existing bits. Therefore, requiring the input to be resizable looks out of scope here.

It sounds like [`BitFlipMutator`] would only need a `&mut [u8]` to work, wouldn't it?


I'm willing to make a PR for improving this, if you believe that it should be done. :)

Thank you 🫡 


[`BitFlipMutator`]: https://docs.rs/libafl/latest/libafl/mutators/mutations/struct.BitFlipMutator.html
[`HasMutatorBytes`]: https://docs.rs/libafl/latest/libafl/inputs/trait.HasMutatorBytes.html


Discussion / hints:
> It sounds like [BitFlipMutator](https://docs.rs/libafl/latest/libafl/mutators/mutations/struct.BitFlipMutator.html) would only need a &mut [u8] to work, wouldn't it?

I agree.

sounds like HasMutatorBytes should be splitted? @domenukk 
if the input is a vector then these can be defined (like, resize, extend), but if it is just a slice we can't define these methods. (and it shouldn't require the input bytes to be a vector)
> if the input is a vector then these can be defined (like, resize, extend), but if it is just a slice we can't define these methods. (and it shouldn't require the input bytes to be a vector)

I would say that regardless of the type, `BitFlipMutator` should only require `AsMut<[u8]>`. It isn't going to resize anything, is it?
> I would say that regardless of the type, BitFlipMutator should only require AsMut<[u8]>. It isn't going to resize anything, is it?

makes sense. No, it won't resize.

All those mutations operators were implemented all together. Some assumes that the input has to be a vector (for example, some mutations extend the input so it requires the underlying input to be a vector)
Of course some other mutations, such as your BitFlipMutator doesn't need those assumptions.
But we didn't split the trait. and just give them the most encompassing trait bounds

And as a result, for some of those mutators the trait bounds are too restrictive.
If you feel like you can split it, and make it less restrictive for those mutations

Fix patch:
diff --git a/libafl/src/inputs/bytes.rs b/libafl/src/inputs/bytes.rs
index 3cfe0ec45b..128ecc14c6 100644
--- a/libafl/src/inputs/bytes.rs
+++ b/libafl/src/inputs/bytes.rs
@@ -11,7 +11,7 @@ use core::cell::RefCell;
 use libafl_bolts::{ownedref::OwnedSlice, HasLen};
 
 use super::ValueInput;
-use crate::inputs::{HasMutatorBytes, HasTargetBytes};
+use crate::inputs::{HasMutatorBytes, HasMutatorResizableBytes, HasTargetBytes};
 
 /// A bytes input is the basic input
 pub type BytesInput = ValueInput<Vec<u8>>;
@@ -31,7 +31,9 @@ impl HasMutatorBytes for BytesInput {
     fn bytes_mut(&mut self) -> &mut [u8] {
         self.as_mut()
     }
+}
 
+impl HasMutatorResizableBytes for BytesInput {
     fn resize(&mut self, new_len: usize, value: u8) {
         self.as_mut().resize(new_len, value);
     }
diff --git a/libafl/src/inputs/bytessub.rs b/libafl/src/inputs/bytessub.rs
index c0280a9ec8..e96c96d376 100644
--- a/libafl/src/inputs/bytessub.rs
+++ b/libafl/src/inputs/bytessub.rs
@@ -11,7 +11,7 @@ use libafl_bolts::{
     HasLen,
 };
 
-use crate::inputs::HasMutatorBytes;
+use crate::inputs::{HasMutatorBytes, HasMutatorResizableBytes};
 
 /// The [`BytesSubInput`] makes it possible to use [`crate::mutators::Mutator`]`s` that work on
 /// inputs implementing the [`HasMutatorBytes`] for a sub-range of this input.
@@ -37,14 +37,15 @@ use crate::inputs::HasMutatorBytes;
 /// assert_eq!(bytes_input.bytes()[1], 42);
 /// ```
 ///
-/// Growing or shrinking the sub input will grow or shrink the parent input,
+/// If inputs implement the [`HasMutatorResizableBytes`] trait, growing or shrinking the sub input
+/// will grow or shrink the parent input,
 /// and keep elements around the current range untouched / move them accordingly.
 ///
 /// For example:
 /// ```rust
 /// # extern crate alloc;
 /// # extern crate libafl;
-/// # use libafl::inputs::{BytesInput, HasMutatorBytes};
+/// # use libafl::inputs::{BytesInput, HasMutatorBytes, HasMutatorResizableBytes};
 /// # use alloc::vec::Vec;
 /// #
 /// # #[cfg(not(feature = "std"))]
@@ -109,7 +110,12 @@ where
     fn bytes_mut(&mut self) -> &mut [u8] {
         &mut self.parent_input.bytes_mut()[self.range.clone()]
     }
+}
 
+impl<I> HasMutatorResizableBytes for BytesSubInput<'_, I>
+where
+    I: HasMutatorResizableBytes,
+{
     fn resize(&mut self, new_len: usize, value: u8) {
         let start_index = self.range.start;
         let end_index = self.range.end;
@@ -208,7 +214,7 @@ mod tests {
     use libafl_bolts::HasLen;
 
     use crate::{
-        inputs::{BytesInput, HasMutatorBytes, NopInput},
+        inputs::{BytesInput, HasMutatorBytes, HasMutatorResizableBytes, NopInput},
         mutators::{havoc_mutations_no_crossover, MutatorsTuple},
         state::NopState,
     };
diff --git a/libafl/src/inputs/mod.rs b/libafl/src/inputs/mod.rs
index 432cdef35a..348aa0609c 100644
--- a/libafl/src/inputs/mod.rs
+++ b/libafl/src/inputs/mod.rs
@@ -155,7 +155,7 @@ pub trait HasTargetBytes {
     fn target_bytes(&self) -> OwnedSlice<u8>;
 }
 
-/// Contains mutable and resizable bytes
+/// Contains mutable bytes
 pub trait HasMutatorBytes: HasLen {
     /// The bytes
     fn bytes(&self) -> &[u8];
@@ -163,25 +163,6 @@ pub trait HasMutatorBytes: HasLen {
     /// The bytes to mutate
     fn bytes_mut(&mut self) -> &mut [u8];
 
-    /// Resize the mutator bytes to a given new size.
-    /// Use `value` to fill new slots in case the buffer grows.
-    /// See [`Vec::splice`].
-    fn resize(&mut self, new_len: usize, value: u8);
-
-    /// Extends the given buffer with an iterator. See [`alloc::vec::Vec::extend`]
-    fn extend<'a, I: IntoIterator<Item = &'a u8>>(&mut self, iter: I);
-
-    /// Splices the given target bytes according to [`Vec::splice`]'s rules
-    fn splice<R, I>(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter>
-    where
-        R: RangeBounds<usize>,
-        I: IntoIterator<Item = u8>;
-
-    /// Drains the given target bytes according to [`Vec::drain`]'s rules
-    fn drain<R>(&mut self, range: R) -> Drain<'_, u8>
-    where
-        R: RangeBounds<usize>;
-
     /// Creates a [`SubRangeSlice`] from this input, that can be used to slice a byte array.
     fn sub_bytes<R>(&self, range: R) -> SubRangeSlice<u8>
     where
@@ -215,7 +196,45 @@ impl HasMutatorBytes for Vec<u8> {
     fn bytes_mut(&mut self) -> &mut [u8] {
         self.as_mut()
     }
+}
+
+/// A wrapper type that allows us to use mutators for Mutators for `&mut `[`Vec`].
+#[deprecated(since = "0.15.0", note = "Use &mut Vec<u8> directly")]
+pub type MutVecInput<'a> = &'a mut Vec<u8>;
+
+impl HasMutatorBytes for &'_ mut Vec<u8> {
+    fn bytes(&self) -> &[u8] {
+        self
+    }
+
+    fn bytes_mut(&mut self) -> &mut [u8] {
+        self
+    }
+}
 
+/// Contains mutable and resizable bytes
+pub trait HasMutatorResizableBytes: HasMutatorBytes {
+    /// Resize the mutator bytes to a given new size.
+    /// Use `value` to fill new slots in case the buffer grows.
+    /// See [`Vec::splice`].
+    fn resize(&mut self, new_len: usize, value: u8);
+
+    /// Extends the given buffer with an iterator. See [`alloc::vec::Vec::extend`]
+    fn extend<'a, I: IntoIterator<Item = &'a u8>>(&mut self, iter: I);
+
+    /// Splices the given target bytes according to [`Vec::splice`]'s rules
+    fn splice<R, I>(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter>
+    where
+        R: RangeBounds<usize>,
+        I: IntoIterator<Item = u8>;
+
+    /// Drains the given target bytes according to [`Vec::drain`]'s rules
+    fn drain<R>(&mut self, range: R) -> Drain<'_, u8>
+    where
+        R: RangeBounds<usize>;
+}
+
+impl HasMutatorResizableBytes for Vec<u8> {
     fn resize(&mut self, new_len: usize, value: u8) {
         <Vec<u8>>::resize(self, new_len, value);
     }
@@ -240,19 +259,7 @@ impl HasMutatorBytes for Vec<u8> {
     }
 }
 
-/// A wrapper type that allows us to use mutators for Mutators for `&mut `[`Vec`].
-#[deprecated(since = "0.15.0", note = "Use &mut Vec<u8> directly")]
-pub type MutVecInput<'a> = &'a mut Vec<u8>;
-
-impl HasMutatorBytes for &mut Vec<u8> {
-    fn bytes(&self) -> &[u8] {
-        self
-    }
-
-    fn bytes_mut(&mut self) -> &mut [u8] {
-        self
-    }
-
+impl HasMutatorResizableBytes for &mut Vec<u8> {
     fn resize(&mut self, new_len: usize, value: u8) {
         self.deref_mut().resize(new_len, value);
     }
diff --git a/libafl/src/mutators/multi.rs b/libafl/src/mutators/multi.rs
index 66dd1ab672..f7c353e866 100644
--- a/libafl/src/mutators/multi.rs
+++ b/libafl/src/mutators/multi.rs
@@ -10,7 +10,7 @@ use libafl_bolts::{rands::Rand, Error};
 use crate::{
     corpus::{Corpus, CorpusId},
     impl_default_multipart,
-    inputs::{multi::MultipartInput, HasMutatorBytes, Input},
+    inputs::{multi::MultipartInput, HasMutatorResizableBytes, Input},
     mutators::{
         mutations::{
             rand_range, BitFlipMutator, ByteAddMutator, ByteDecMutator, ByteFlipMutator,
@@ -119,7 +119,7 @@ impl_default_multipart!(
 impl<I, S> Mutator<MultipartInput<I>, S> for CrossoverInsertMutator
 where
     S: HasCorpus + HasMaxSize + HasRand,
-    I: Input + HasMutatorBytes,
+    I: Input + HasMutatorResizableBytes,
     S::Corpus: Corpus<Input = MultipartInput<I>>,
 {
     fn mutate(
@@ -255,7 +255,7 @@ where
 impl<I, S> Mutator<MultipartInput<I>, S> for CrossoverReplaceMutator
 where
     S: HasCorpus + HasMaxSize + HasRand,
-    I: Input + HasMutatorBytes,
+    I: Input + HasMutatorResizableBytes,
     S::Corpus: Corpus<Input = MultipartInput<I>>,
 {
     fn mutate(
diff --git a/libafl/src/mutators/mutations.rs b/libafl/src/mutators/mutations.rs
index e9f729a0d9..dce3ffb730 100644
--- a/libafl/src/mutators/mutations.rs
+++ b/libafl/src/mutators/mutations.rs
@@ -16,7 +16,7 @@ use libafl_bolts::{rands::Rand, Named};
 
 use crate::{
     corpus::Corpus,
-    inputs::HasMutatorBytes,
+    inputs::{HasMutatorBytes, HasMutatorResizableBytes},
     mutators::{MutationResult, Mutator},
     nonzero, random_corpus_id_with_disabled,
     state::{HasCorpus, HasMaxSize, HasRand},
@@ -464,7 +464,7 @@ pub struct BytesDeleteMutator;
 impl<I, S> Mutator<I, S> for BytesDeleteMutator
 where
     S: HasRand,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
 {
     fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
         let size = input.bytes().len();
@@ -506,7 +506,7 @@ pub struct BytesExpandMutator;
 impl<I, S> Mutator<I, S> for BytesExpandMutator
 where
     S: HasRand + HasMaxSize,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
 {
     fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
         let max_size = state.max_size();
@@ -557,7 +557,7 @@ pub struct BytesInsertMutator;
 impl<I, S> Mutator<I, S> for BytesInsertMutator
 where
     S: HasRand + HasMaxSize,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
 {
     fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
         let max_size = state.max_size();
@@ -620,7 +620,7 @@ pub struct BytesRandInsertMutator;
 impl<I, S> Mutator<I, S> for BytesRandInsertMutator
 where
     S: HasRand + HasMaxSize,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
 {
     fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
         let max_size = state.max_size();
@@ -815,7 +815,7 @@ pub struct BytesInsertCopyMutator {
 impl<I, S> Mutator<I, S> for BytesInsertCopyMutator
 where
     S: HasRand + HasMaxSize,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
 {
     fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
         let size = input.bytes().len();
@@ -886,7 +886,7 @@ pub struct BytesSwapMutator {
 impl<I, S> Mutator<I, S> for BytesSwapMutator
 where
     S: HasRand,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
 {
     fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
         let size = input.bytes().len();
@@ -1104,7 +1104,7 @@ impl CrossoverInsertMutator {
         other: &[u8],
     ) -> MutationResult
     where
-        I: HasMutatorBytes,
+        I: HasMutatorResizableBytes,
     {
         input.resize(size + range.len(), 0);
         unsafe {
@@ -1127,7 +1127,7 @@ impl<I, S> Mutator<I, S> for CrossoverInsertMutator
 where
     S: HasCorpus + HasRand + HasMaxSize,
     <S::Corpus as Corpus>::Input: HasMutatorBytes,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
 {
     fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
         let size = input.bytes().len();
@@ -1315,7 +1315,7 @@ impl<F, O> MappedCrossoverInsertMutator<F, O> {
 impl<S, F, I, O> Mutator<I, S> for MappedCrossoverInsertMutator<F, O>
 where
     S: HasCorpus + HasMaxSize + HasRand,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
     O: IntoOptionBytes,
     F: Fn(&<S::Corpus as Corpus>::Input) -> &O,
 {
@@ -1494,7 +1494,7 @@ impl<I, S> Mutator<I, S> for SpliceMutator
 where
     S: HasCorpus + HasRand,
     <S::Corpus as Corpus>::Input: HasMutatorBytes,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
 {
     #[expect(clippy::cast_sign_loss)]
     fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
diff --git a/libafl/src/mutators/token_mutations.rs b/libafl/src/mutators/token_mutations.rs
index 23664f3d53..e56b118f4a 100644
--- a/libafl/src/mutators/token_mutations.rs
+++ b/libafl/src/mutators/token_mutations.rs
@@ -25,7 +25,7 @@ use serde::{Deserialize, Serialize};
 use crate::mutators::str_decode;
 use crate::{
     corpus::{CorpusId, HasCurrentCorpusId},
-    inputs::HasMutatorBytes,
+    inputs::HasMutatorResizableBytes,
     mutators::{
         buffer_self_copy, mutations::buffer_copy, MultiMutator, MutationResult, Mutator, Named,
     },
@@ -306,7 +306,7 @@ pub struct TokenInsert;
 impl<I, S> Mutator<I, S> for TokenInsert
 where
     S: HasMetadata + HasRand + HasMaxSize,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
 {
     fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
         let max_size = state.max_size();
@@ -375,7 +375,7 @@ pub struct TokenReplace;
 impl<I, S> Mutator<I, S> for TokenReplace
 where
     S: HasMetadata + HasRand + HasMaxSize,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
 {
     fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
         let size = input.bytes().len();
@@ -435,7 +435,7 @@ pub struct I2SRandReplace;
 impl<I, S> Mutator<I, S> for I2SRandReplace
 where
     S: HasMetadata + HasRand + HasMaxSize,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
 {
     #[expect(clippy::too_many_lines)]
     fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
@@ -636,7 +636,7 @@ where
 impl<I, S> Mutator<I, S> for I2SRandReplaceBinonly
 where
     S: HasMetadata + HasRand + HasMaxSize,
-    I: HasMutatorBytes,
+    I: HasMutatorResizableBytes,
 {
     #[expect(clippy::too_many_lines)]
     fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
@@ -1306,7 +1306,7 @@ impl AFLppRedQueen {
 impl<I, S> MultiMutator<I, S> for AFLppRedQueen
 where
     S: HasMetadata + HasRand + HasMaxSize + HasCorpus + HasCurrentCorpusId,
-    I: HasMutatorBytes + From<Vec<u8>>,
+    I: HasMutatorResizableBytes + From<Vec<u8>>,
 {
     #[expect(clippy::needless_range_loop, clippy::too_many_lines)]
     fn multi_mutate(
diff --git a/libafl/src/mutators/unicode/mod.rs b/libafl/src/mutators/unicode/mod.rs
index c719377736..ef7dd90edb 100644
--- a/libafl/src/mutators/unicode/mod.rs
+++ b/libafl/src/mutators/unicode/mod.rs
@@ -11,7 +11,7 @@ use libafl_bolts::{rands::Rand, Error, HasLen, Named};
 
 use crate::{
     corpus::{Corpus, CorpusId, HasTestcase, Testcase},
-    inputs::{BytesInput, HasMutatorBytes},
+    inputs::{BytesInput, HasMutatorBytes, HasMutatorResizableBytes},
     mutators::{rand_range, MutationResult, Mutator, Tokens},
     nonzero,
     stages::{
diff --git a/libafl/src/stages/colorization.rs b/libafl/src/stages/colorization.rs
index 87109a3820..6c06e10dce 100644
--- a/libafl/src/stages/colorization.rs
+++ b/libafl/src/stages/colorization.rs
@@ -17,7 +17,7 @@ use crate::{
     corpus::{Corpus, HasCurrentCorpusId},
     events::EventFirer,
     executors::{Executor, HasObservers},
-    inputs::HasMutatorBytes,
+    inputs::{HasMutatorBytes, HasMutatorResizableBytes},
     mutators::mutations::buffer_copy,
     nonzero,
     observers::{MapObserver, ObserversTuple},
@@ -80,7 +80,7 @@ where
     E: HasObservers + Executor<EM, <S::Corpus as Corpus>::Input, S, Z>,
     S: HasCorpus + HasMetadata + HasRand + HasNamedMetadata + HasCurrentCorpusId,
     E::Observers: ObserversTuple<<S::Corpus as Corpus>::Input, S>,
-    <S::Corpus as Corpus>::Input: HasMutatorBytes + Clone,
+    <S::Corpus as Corpus>::Input: HasMutatorResizableBytes + Clone,
     O: MapObserver,
     C: AsRef<O> + Named,
 {
@@ -157,7 +157,7 @@ where
     E: HasObservers + Executor<EM, <S::Corpus as Corpus>::Input, S, Z>,
     E::Observers: ObserversTuple<<S::Corpus as Corpus>::Input, S>,
     S: HasCorpus + HasMetadata + HasRand + HasCurrentCorpusId + HasCurrentTestcase,
-    <S::Corpus as Corpus>::Input: HasMutatorBytes + Clone,
+    <S::Corpus as Corpus>::Input: HasMutatorResizableBytes + Clone,
 {
     #[inline]
     fn colorize(
diff --git a/libafl/src/stages/generalization.rs b/libafl/src/stages/generalization.rs
index 432514f09b..88d14c7f5a 100644
--- a/libafl/src/stages/generalization.rs
+++ b/libafl/src/stages/generalization.rs
@@ -17,7 +17,10 @@ use crate::{
     corpus::{Corpus, HasCurrentCorpusId},
     executors::{Executor, HasObservers},
     feedbacks::map::MapNoveltiesMetadata,
-    inputs::{BytesInput, GeneralizedInputMetadata, GeneralizedItem, HasMutatorBytes},
+    inputs::{
+        BytesInput, GeneralizedInputMetadata, GeneralizedItem, HasMutatorBytes,
+        HasMutatorResizableBytes,
+    },
     mark_feature_time,
     observers::{CanTrack, MapObserver, ObserversTuple},
     require_novelties_tracking,
diff --git a/libafl_targets/src/libfuzzer/mutators.rs b/libafl_targets/src/libfuzzer/mutators.rs
index 34abfb72a3..ed47616c5f 100644
--- a/libafl_targets/src/libfuzzer/mutators.rs
+++ b/libafl_targets/src/libfuzzer/mutators.rs
@@ -11,7 +11,7 @@ use std::{
 
 use libafl::{
     corpus::Corpus,
-    inputs::{BytesInput, HasMutatorBytes},
+    inputs::{BytesInput, HasMutatorBytes, HasMutatorResizableBytes},
     mutators::{
         ComposedByMutations, MutationId, MutationResult, Mutator, MutatorsTuple, ScheduledMutator,
     },


Test patch: