1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::{set_states_corpus_index, CorpusIndex, Scheduler};
use crate::error::FeroxFuzzError;
use crate::state::SharedState;
use crate::std_ext::ops::Len;
use tracing::{error, instrument, trace};
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(docsrs)] {
// just bringing in types for easier intra-doc linking during doc build
use crate::corpora::Corpus;
}
}
/// In-order access of the associated [`Corpus`]
///
/// # Examples
///
/// if you have a corpus with the following entries:
///
/// `FUZZ_USER`: ["user1", "user2", "user3"]
/// `FUZZ_PASS`: ["pass1", "pass2", "pass3"]
///
/// and a fuzzable url defined as
///
/// `http://example.com/login?username=FUZZ_USER&password=FUZZ_PASS`
///
///
/// then the resultant `OrderedScheduler` scheduling of the two corpora would be:
///
/// `http://example.com/login?username=user1&password=pass1`
/// `http://example.com/login?username=user2&password=pass2`
/// `http://example.com/login?username=user3&password=pass3`
///
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OrderedScheduler {
current: usize,
indices: Vec<CorpusIndex>,
#[cfg_attr(feature = "serde", serde(skip))]
state: SharedState,
}
impl Scheduler for OrderedScheduler {
#[instrument(skip(self), fields(%self.current, ?self.indices), level = "trace")]
fn next(&mut self) -> Result<(), FeroxFuzzError> {
// iterate through the indices and increment the current index
for index in &mut self.indices {
if self.current > 0 && index.should_reset(self.current) {
// once any of the indices reaches the end of its loop, the entire
// scheduler has run to completion
trace!("scheduler has run to completion");
return Err(FeroxFuzzError::IterationStopped);
}
set_states_corpus_index(&self.state, index.name(), index.current())?;
index.next()?;
}
self.current += 1; // update the total number of times .next has been called
Ok(())
}
/// resets all indexes that are tracked by the scheduler as well as their associated atomic
/// indexes in the [`SharedState`] instance
#[instrument(skip(self), level = "trace")]
fn reset(&mut self) {
self.current = 0;
self.indices.iter_mut().for_each(|index| {
// first, we get the corpus associated with the current corpus_index
let corpus = self.state.corpus_by_name(index.name()).unwrap();
// and then get its length
let len = corpus.len();
// if any items were added to the corpus, we'll need to update the length/expected iterations
// accordingly
index.update_length(len);
index.update_iterations(len);
// we'll also reset the current index as well
index.reset();
// finally, we get the SharedState's view of the index in sync with the Scheduler's
//
// i.e. at this point, the state and local indices should all be 0, and any items
// added to the corpus should be reflected in each index's length/iterations
set_states_corpus_index(&self.state, index.name(), 0).unwrap();
});
trace!("scheduler has been reset");
}
}
impl OrderedScheduler {
/// create a new `OrderedScheduler`
///
/// # Errors
///
/// This function will return an error if any corpus found in the `SharedState`'s
/// `corpora` map is empty, or if the `SharedState`'s `corpora` map is empty.
///
/// # Examples
///
/// see `examples/cartesian-product.rs` for a more robust example
/// and explanation
///
/// ```
/// use feroxfuzz::schedulers::{Scheduler, ProductScheduler};
/// use feroxfuzz::prelude::*;
/// use feroxfuzz::corpora::{RangeCorpus, Wordlist};
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// // create two corpora, one with a set of user names, and one with a range of ids
/// // where only even ids are considered
/// let users = Wordlist::new().word("user").word("admin").name("users").build();
/// let ids = RangeCorpus::with_stop(5).name("ids").build()?;
///
/// let state = SharedState::with_corpora([ids, users]);
///
/// let order = ["users", "ids"];
/// let mut scheduler = ProductScheduler::new(order, state.clone())?;
///
/// let mut counter = 0;
///
/// while Scheduler::next(&mut scheduler).is_ok() {
/// counter += 1;
/// }
///
/// // users.len() * ids.len() = 2 * 5 = 10
/// assert_eq!(counter, 10);
///
/// # Ok(())
/// # }
#[inline]
#[instrument(skip_all, level = "trace")]
pub fn new(state: SharedState) -> Result<Self, FeroxFuzzError> {
let corpora = state.corpora();
let mut indices = Vec::with_capacity(corpora.len());
for (name, corpus) in corpora.iter() {
let length = corpus.len();
if length == 0 {
// one of the corpora was empty
error!(%name, "corpus is empty");
return Err(FeroxFuzzError::EmptyCorpus {
name: name.to_string(),
});
}
// the total number of expected iterations per corpus is simply
// the length of the corpus
indices.push(CorpusIndex::new(name, length, length));
}
if indices.is_empty() {
// empty iterator passed in
error!("no corpora were found");
return Err(FeroxFuzzError::EmptyCorpusMap);
}
Ok(Self {
state,
indices,
current: 0,
})
}
}
#[allow(clippy::copy_iterator)]
impl Iterator for OrderedScheduler {
type Item = ();
fn next(&mut self) -> Option<Self::Item> {
<Self as Scheduler>::next(self).ok()
}
}