use super::Mutator;
use crate::atomic_load;
use crate::corpora::Corpus;
use crate::error::FeroxFuzzError;
use crate::input::Data;
use crate::metadata::AsAny;
use crate::state::SharedState;
use crate::std_ext::tuple::Named;
use crate::AsBytes;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::any::Any;
use std::sync::atomic::Ordering;
use tracing::{error, instrument, trace};
#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ReplaceKeyword {
keyword: Vec<u8>,
corpus_name: String,
}
impl ReplaceKeyword {
pub fn new<S>(keyword: &S, corpus_name: &str) -> Self
where
S: AsBytes + ?Sized,
{
Self {
keyword: keyword.as_bytes().to_vec(),
corpus_name: corpus_name.to_string(),
}
}
}
impl Mutator for ReplaceKeyword {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
#[instrument(skip_all, fields(?self.keyword), level = "trace")]
fn mutate(&mut self, input: &mut Data, state: &mut SharedState) -> Result<(), FeroxFuzzError> {
if let Data::Fuzzable(data) = input {
let corpus = state.corpus_by_name(&self.corpus_name)?;
let corpus_index = state.corpus_index_by_name(&self.corpus_name)?;
if let Ok(guard) = corpus.read() {
let fetched_idx = atomic_load!(corpus_index);
let result = guard.get(fetched_idx);
if result.is_none() {
error!(
name = guard.name(),
index = fetched_idx,
"could not find requested corpus entry"
);
return Err(FeroxFuzzError::CorpusEntryNotFound {
name: guard.name().to_string(),
index: fetched_idx,
});
}
let entry = result.unwrap();
let indices = data
.windows(self.keyword.len())
.enumerate()
.filter(|(_, window)| *window == self.keyword.as_slice())
.fold(Vec::new(), |mut acc, (idx, _)| {
acc.push(idx);
acc
});
if indices.is_empty() {
trace!(
"keyword '{:?}' not found in data '{:?}'",
&self.keyword,
data
);
return Ok(());
}
let step = entry.len() as i64 - self.keyword.len() as i64;
indices.iter().enumerate().for_each(|(i, &idx)| {
let offset = if step.is_negative() {
idx - (i * step.wrapping_abs() as usize)
} else {
idx + (i * step as usize)
};
data.splice(
offset..offset + self.keyword.len(),
entry.as_bytes().iter().copied(),
);
});
};
}
Ok(())
}
}
impl Named for ReplaceKeyword {
fn name(&self) -> &str {
"ReplaceKeyword"
}
}
impl AsAny for ReplaceKeyword {
fn as_any(&self) -> &dyn Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::corpora::Wordlist;
fn keyword_mutator_test_helper(words: Vec<String>, to_replace: &mut Data) {
let corpus = Wordlist::with_words(words).name("corpus").build();
let mut state = SharedState::with_corpus(corpus);
let mut replacer = ReplaceKeyword::new(&"FUZZ", "corpus");
replacer.mutate(to_replace, &mut state).unwrap();
}
#[test]
fn keyword_mutator_with_longer_replacement() {
let words = vec![String::from("longer")];
let mut to_replace = Data::Fuzzable(b"stFUFUZZZZackedFUZZ".to_vec());
keyword_mutator_test_helper(words, &mut to_replace);
assert_eq!(
to_replace,
Data::Fuzzable(b"stFUlongerZZackedlonger".to_vec())
);
}
#[test]
fn keyword_mutator_with_shorter_replacement() {
let words = vec![String::from("st")];
let mut to_replace = Data::Fuzzable(b"stFUFUZZZZackedFUZZ".to_vec());
keyword_mutator_test_helper(words, &mut to_replace);
assert_eq!(to_replace, Data::Fuzzable(b"stFUstZZackedst".to_vec()));
}
#[test]
fn keyword_mutator_with_no_replacement() {
let words = vec![String::from("st")];
let mut to_replace = Data::Fuzzable(b"stacked".to_vec());
keyword_mutator_test_helper(words, &mut to_replace);
assert_eq!(to_replace, Data::Fuzzable(b"stacked".to_vec()));
}
#[test]
fn keyword_mutator_with_static_data() {
let words = vec![String::from("derp")];
let mut to_replace = Data::Static(b"staFUZZcked".to_vec());
keyword_mutator_test_helper(words, &mut to_replace);
assert_eq!(to_replace, Data::Static(b"staFUZZcked".to_vec()));
}
#[test]
fn keyword_mutator_with_empty_corpus() {
let words: Vec<String> = vec![];
let mut to_replace = Data::Fuzzable(b"staFUZZcked".to_vec());
let corpus = Wordlist::with_words(words).name("corpus").build();
let mut state = SharedState::with_corpus(corpus);
let mut replacer = ReplaceKeyword::new(&"FUZZ", "corpus");
assert!(replacer.mutate(&mut to_replace, &mut state).is_err());
}
}