From 0f891730877fbdb21efcb6b67ea3f6ea9da2e73b Mon Sep 17 00:00:00 2001 From: epi Date: Sun, 9 Oct 2022 13:41:58 -0500 Subject: [PATCH 1/6] added action to stop fuzzing --- src/actions.rs | 10 +- src/error.rs | 8 + src/fuzzers/async_fuzzer.rs | 105 ++++++++++--- src/fuzzers/blocking_fuzzer.rs | 26 +++- src/fuzzers/mod.rs | 24 ++- src/std_ext/ops.rs | 265 ++++++++++++++++++++++++++++----- 6 files changed, 367 insertions(+), 71 deletions(-) diff --git a/src/actions.rs b/src/actions.rs index 07b02c8..e3e7d01 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -45,9 +45,13 @@ pub enum Action { /// resulting `Action` will still be passed to any configured /// [`Processor`]s. AddToCorpus(&'static str, FlowControl), + + /// break out of the current fuzz loop; no more iterations other than + /// what's already in flight will be performed + StopFuzzing, } -/// analogous to the [`Action::Keep`] and [`Action::Discard`] variants +/// analogous to the [`Action::Keep`], [`Action::Discard`], and [`Action::StopFuzzing`] variants /// /// used when the [`Action`] isn't a flow control directive itself #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] @@ -61,4 +65,8 @@ pub enum FlowControl { /// when used in a pre-send context, ignore the current [`Request`], if /// used in a post-send context, ignore the current [`Response`] Discard, + + /// break out of the current fuzz loop; no more iterations other than + /// what's already in flight will be performed + StopFuzzing, } diff --git a/src/error.rs b/src/error.rs index d02574d..eea8e91 100644 --- a/src/error.rs +++ b/src/error.rs @@ -215,6 +215,14 @@ pub enum FeroxFuzzError { /// async fuzz_once loop. #[error("Discarded request based on user-provided criteria")] DiscardedRequest, + + /// Represents a recommended [`Action::StopFuzzing`] during asynchronous fuzzing + /// + /// Note: this is only used because of how the async fuzz_once loop + /// is implemented. It is not intended to be used outside of the + /// async fuzz_once loop. + #[error("Stopped fuzzing based on user-provided criteria")] + FuzzingStopped, } /// Used to differentiate between different types of errors that occur when making requests. diff --git a/src/fuzzers/async_fuzzer.rs b/src/fuzzers/async_fuzzer.rs index 7230028..f3cf47d 100644 --- a/src/fuzzers/async_fuzzer.rs +++ b/src/fuzzers/async_fuzzer.rs @@ -1,8 +1,11 @@ use std::fmt::Debug; use std::iter::Iterator; use std::marker::Send; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use async_trait::async_trait; +use futures::future; use futures::stream; use futures::StreamExt; use tokio::task::JoinHandle; @@ -133,12 +136,21 @@ where ::Item: Debug, { #[instrument(skip_all, fields(%self.threads, ?self.post_send_logic, ?self.pre_send_logic) name = "fuzz-loop", level = "trace")] - async fn fuzz_once(&mut self, state: &mut SharedState) -> Result<(), FeroxFuzzError> { + async fn fuzz_once( + &mut self, + state: &mut SharedState, + ) -> Result, FeroxFuzzError> { let num_threads = self.threads; let post_send_logic = self.post_send_logic().unwrap_or_default(); let scheduler = self.scheduler.clone(); - stream::iter(scheduler) + // facilitates a way to 'break' out of the iterator + let mut err = Ok(()); + let mut pre_send_action = None; + // let mut post_send_action = &mut None; + let should_quit = Arc::new(AtomicBool::new(false)); + + let maybe_action = stream::iter(scheduler) .map( |_| -> Result< ( @@ -148,6 +160,7 @@ where P, Request, SharedState, + Arc, ), FeroxFuzzError, > { @@ -184,12 +197,23 @@ where state.add_to_corpus(name, &mutated_request)?; - if matches!(flow_control, FlowControl::Discard) { - self.request_id += 1; - return Err(FeroxFuzzError::DiscardedRequest); + match flow_control { + FlowControl::StopFuzzing => { + pre_send_action = Some(Action::StopFuzzing); + return Err(FeroxFuzzError::FuzzingStopped); + } + FlowControl::Discard => { + self.request_id += 1; + return Err(FeroxFuzzError::DiscardedRequest); + } + _ => {} } + // ignore when flow control is Keep, same as we do for Action::Keep below } + Some(Action::StopFuzzing) => { + return Err(FeroxFuzzError::FuzzingStopped); + } _ => { // do nothing if it's None or an Action::Keep } @@ -201,6 +225,7 @@ where let cloned_processors = self.processors.clone(); let cloned_state = state.clone(); let cloned_request = mutated_request.clone(); + let cloned_quit_flag = should_quit.clone(); let response_handle = tokio::spawn(async move { let result = cloned_client.send(mutated_request).await?; @@ -216,17 +241,42 @@ where cloned_processors, cloned_request, cloned_state, + cloned_quit_flag, )) }, - ) - .for_each_concurrent(num_threads, |result| async move { + ).scan(&mut err, |err, result| { + // println!("post send action: {:?}", post_send_action); + if should_quit.load(Ordering::Relaxed) { + // this check accounts for us setting the action to StopFuzzing in the PostSend phase + **err = Err(FeroxFuzzError::FuzzingStopped); + println!("[REAL3] Stopping fuzzing due to FlowControl::StopFuzzing"); + return future::ready(None); + } + + match result { + Ok((response_handle, observers, deciders, processors, request, state, quit_flag)) => { + future::ready(Some(Ok((response_handle, observers, deciders, processors, request, state, quit_flag)))) + } + Err(e) => { + if matches!(e, FeroxFuzzError::DiscardedRequest) { + future::ready(Some(Err(e))) + } else { + // this is the check that comes from PreSend + **err = Err(FeroxFuzzError::FuzzingStopped); + future::ready(None) + } + } + } + }) + .for_each_concurrent(num_threads, |result| + async move { // the is_err -> return paradigm below isn't necessarily idiomatic rust, however, i didn't like the // heavily indented match -> match -> if let Ok ..., so to keep the code more left-aligned, i // chose to write it the way you see here if result.is_err() { // as of right now, the only possible error states the .map iterator above can get into is - // when a mutator fails for w/e random reason and when a Action::Discard is encounter; in + // when a mutator fails for w/e random reason and when a Action::Discard is encountered; in // either event, the request was never sent, so we can't reasonably continue // failed mutation errors are logged at the error point, not here @@ -234,8 +284,12 @@ where } // result cannot be Err after this point, so is safe to unwrap - let (resp, mut c_observers, mut c_deciders, mut c_processors, c_request, c_state) = + let (resp, mut c_observers, mut c_deciders, mut c_processors, c_request, c_state, c_should_quit) = result.unwrap(); + + if c_should_quit.load(Ordering::Relaxed) { + return; + } // await the actual response, which is a double-nested Result // Result, tokio::task::JoinError> @@ -277,21 +331,38 @@ where c_processors.call_post_send_hooks(&c_state, &c_observers, decision.as_ref()); - if let Some(Action::AddToCorpus(name, _flow_control)) = decision { - // if we've reached this point, flow control doesn't matter anymore; the - // only thing we need to check at this point is if we need to alter the - // corpus + match decision { + Some(Action::AddToCorpus(name, flow_control)) => { + // if we've reached this point, flow control doesn't matter anymore; the + // only thing we need to check at this point is if we need to alter the + // corpus - if let Err(err) = c_state.add_to_corpus(name, &c_request) { - warn!("Could not add {:?} to corpus[{name}]: {:?}", c_request, err); + if let Err(err) = c_state.add_to_corpus(name, &c_request) { + warn!("Could not add {:?} to corpus[{name}]: {:?}", c_request, err); + } + + if matches!(flow_control, FlowControl::StopFuzzing) { + c_should_quit.store(true, Ordering::Relaxed); + } } + Some(Action::StopFuzzing) => { + c_should_quit.store(true, Ordering::Relaxed); + } + _ => {} + } - }) + }) .await; + println!("Fuzzing complete: {:?}", maybe_action); + + if let Err(FeroxFuzzError::FuzzingStopped) = err { + return Ok(Some(Action::StopFuzzing)); + } + // in case we're fuzzing more than once, reset the scheduler self.scheduler.reset(); - Ok(()) + Ok(None) // no action taken } } diff --git a/src/fuzzers/blocking_fuzzer.rs b/src/fuzzers/blocking_fuzzer.rs index ae21640..0781ec8 100644 --- a/src/fuzzers/blocking_fuzzer.rs +++ b/src/fuzzers/blocking_fuzzer.rs @@ -74,7 +74,7 @@ where S: Scheduler, { #[instrument(skip_all, fields(?self.post_send_logic, ?self.pre_send_logic), name = "fuzz-loop", level = "trace")] - fn fuzz_once(&mut self, state: &mut SharedState) -> Result<(), FeroxFuzzError> { + fn fuzz_once(&mut self, state: &mut SharedState) -> Result, FeroxFuzzError> { while self.scheduler.next().is_ok() { let mut mutated_request = self .mutators @@ -112,6 +112,9 @@ where } // ignore when flow control is Keep, same as we do for Action::Keep below } + Some(Action::StopFuzzing) => { + return Ok(Some(Action::StopFuzzing)); + } _ => { // do nothing if it's None or an Action::Keep } @@ -142,12 +145,21 @@ where self.processors .call_post_send_hooks(state, &self.observers, decision.as_ref()); - if let Some(Action::AddToCorpus(name, _flow_control)) = decision { - // if we've reached this point, flow control doesn't matter anymore; the - // only thing we need to check at this point is if we need to alter the - // corpus + match decision { + // if we've reached this point, the only flow control that matters anymore is + // if the fuzzer should stop fuzzing; that means the only thing we need + // to check at this point is if we need to alter the corpus + Some(Action::AddToCorpus(name, flow_control)) => { + state.add_to_corpus(name, &mutated_request)?; - state.add_to_corpus(name, &mutated_request)?; + if matches!(flow_control, FlowControl::StopFuzzing) { + return Ok(Some(Action::StopFuzzing)); + } + } + Some(Action::StopFuzzing) => { + return Ok(Some(Action::StopFuzzing)); + } + _ => {} } self.request_id += 1; @@ -156,7 +168,7 @@ where // in case we're fuzzing more than once, reset the scheduler self.scheduler.reset(); - Ok(()) + Ok(None) // no action to take } } diff --git a/src/fuzzers/mod.rs b/src/fuzzers/mod.rs index cfbe2bb..6cd922f 100644 --- a/src/fuzzers/mod.rs +++ b/src/fuzzers/mod.rs @@ -1,4 +1,5 @@ //! [`Corpus`] based iterators of different flavors +use crate::actions::Action; use crate::deciders::LogicOperation; use crate::error::FeroxFuzzError; use crate::state::SharedState; @@ -63,7 +64,9 @@ pub trait AsyncFuzzing: Fuzzer { /// see [`Fuzzer`]'s Errors section for more details async fn fuzz(&mut self, state: &mut SharedState) -> Result<(), FeroxFuzzError> { loop { - self.fuzz_once(state).await?; + if let Some(Action::StopFuzzing) = self.fuzz_once(state).await? { + break Ok(()); + } } } @@ -74,7 +77,10 @@ pub trait AsyncFuzzing: Fuzzer { /// /// implementors of this function may return an error from any of the composable /// fuzzer components, as this is the primary driver function of any fuzzer - async fn fuzz_once(&mut self, state: &mut SharedState) -> Result<(), FeroxFuzzError>; + async fn fuzz_once( + &mut self, + state: &mut SharedState, + ) -> Result, FeroxFuzzError>; /// fuzz for `n` cycles, where a cycle is one full iteration of the corpus along /// with all fuzzer stages @@ -89,7 +95,9 @@ pub trait AsyncFuzzing: Fuzzer { state: &mut SharedState, ) -> Result<(), FeroxFuzzError> { for _ in 0..num_iterations { - self.fuzz_once(state).await?; + if let Some(Action::StopFuzzing) = self.fuzz_once(state).await? { + break; + } } Ok(()) @@ -108,7 +116,9 @@ pub trait BlockingFuzzing: Fuzzer { /// see [`Fuzzer`]'s Errors section for more details fn fuzz(&mut self, state: &mut SharedState) -> Result<(), FeroxFuzzError> { loop { - self.fuzz_once(state)?; + if let Some(Action::StopFuzzing) = self.fuzz_once(state)? { + break Ok(()); + } } } @@ -119,7 +129,7 @@ pub trait BlockingFuzzing: Fuzzer { /// /// implementors of this function may return an error from any of the composable /// fuzzer components, as this is the primary driver function of any fuzzer - fn fuzz_once(&mut self, state: &mut SharedState) -> Result<(), FeroxFuzzError>; + fn fuzz_once(&mut self, state: &mut SharedState) -> Result, FeroxFuzzError>; /// fuzz for `n` cycles, where a cycle is one full iteration of the corpus along /// with all fuzzer stages @@ -134,7 +144,9 @@ pub trait BlockingFuzzing: Fuzzer { state: &mut SharedState, ) -> Result<(), FeroxFuzzError> { for _ in 0..num_iterations { - self.fuzz_once(state)?; + if let Some(Action::StopFuzzing) = self.fuzz_once(state)? { + break; + } } Ok(()) diff --git a/src/std_ext/ops.rs b/src/std_ext/ops.rs index 4dc340c..a766c27 100644 --- a/src/std_ext/ops.rs +++ b/src/std_ext/ops.rs @@ -132,53 +132,63 @@ impl Len for usize { /// [`Action::Keep`] and [`Action::Discard`] are analogous to true and false /// and bitwise operations work on them the same way they would on true and false. /// +/// [`Action::StopFuzzing`] takes precedence over all other actions. +/// /// # Truth tables /// /// ## `LogicOperation::And` operation on an [`Action`] /// -/// |-------------------------------------|-------------------------------------|-------------------------------------| -/// | A | B | A & B | -/// |-------------------------------------|-------------------------------------|-------------------------------------| -/// | `Keep` | `Keep` | `Keep` | -/// | `Keep` | `Discard` | `Discard` | -/// | `Keep` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | -/// | `Keep` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | -/// | `Discard` | `Keep` | `Discard` | -/// | `Discard` | `Discard` | `Discard` | -/// | `Discard` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Discard)` | -/// | `Discard` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | -/// | `AddToCorpus(FlowControl::Keep)` | `Keep` | `AddToCorpus(FlowControl::Keep)` | -/// | `AddToCorpus(FlowControl::Keep)` | `Discard` | `AddToCorpus(FlowControl::Discard)` | -/// | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | -/// | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | -/// | `AddToCorpus(FlowControl::Discard)` | `Keep` | `AddToCorpus(FlowControl::Discard)` | -/// | `AddToCorpus(FlowControl::Discard)` | `Discard` | `AddToCorpus(FlowControl::Discard)` | -/// | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Discard)` | -/// | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | -/// |-------------------------------------|-------------------------------------|-------------------------------------| +/// |-----------------------------------------|-----------------------------------------|-----------------------------------------| +/// | A | B | A & B | +/// |-----------------------------------------|-----------------------------------------|-----------------------------------------| +/// | `StopFuzzing` | `*` | `StopFuzzing` | +/// | `*` | `StopFuzzing` | `StopFuzzing` | +/// | `AddToCorpus(FlowControl::StopFuzzing)` | `*` | `AddToCorpus(FlowControl::StopFuzzing)` | +/// | `*` | `AddToCorpus(FlowControl::StopFuzzing)` | `AddToCorpus(FlowControl::StopFuzzing)` | +/// | `Keep` | `Keep` | `Keep` | +/// | `Keep` | `Discard` | `Discard` | +/// | `Keep` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | +/// | `Keep` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | +/// | `Discard` | `Keep` | `Discard` | +/// | `Discard` | `Discard` | `Discard` | +/// | `Discard` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Discard)` | +/// | `Discard` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | +/// | `AddToCorpus(FlowControl::Keep)` | `Keep` | `AddToCorpus(FlowControl::Keep)` | +/// | `AddToCorpus(FlowControl::Keep)` | `Discard` | `AddToCorpus(FlowControl::Discard)` | +/// | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | +/// | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | +/// | `AddToCorpus(FlowControl::Discard)` | `Keep` | `AddToCorpus(FlowControl::Discard)` | +/// | `AddToCorpus(FlowControl::Discard)` | `Discard` | `AddToCorpus(FlowControl::Discard)` | +/// | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Discard)` | +/// | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | +/// |-----------------------------------------|-----------------------------------------|-----------------------------------------| /// /// ## `LogicOperation::Or` operation on an [`Action`] /// -/// |-------------------------------------|-------------------------------------|-------------------------------------| -/// | A | B | A | B | -/// |-------------------------------------|-------------------------------------|-------------------------------------| -/// | `Keep` | `Keep` | `Keep` | -/// | `Keep` | `Discard` | `Keep` | -/// | `Keep` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | -/// | `Keep` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Keep)` | -/// | `Discard` | `Keep` | `Keep` | -/// | `Discard` | `Discard` | `Discard` | -/// | `Discard` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | -/// | `Discard` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | -/// | `AddToCorpus(FlowControl::Keep)` | `Keep` | `AddToCorpus(FlowControl::Keep)` | -/// | `AddToCorpus(FlowControl::Keep)` | `Discard` | `AddToCorpus(FlowControl::Keep)` | -/// | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | -/// | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Keep)` | -/// | `AddToCorpus(FlowControl::Discard)` | `Keep` | `AddToCorpus(FlowControl::Keep)` | -/// | `AddToCorpus(FlowControl::Discard)` | `Discard` | `AddToCorpus(FlowControl::Discard)` | -/// | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | -/// | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | -/// |-------------------------------------|-------------------------------------|-------------------------------------| +/// |-----------------------------------------|-----------------------------------------|-----------------------------------------| +/// | A | B | A | B | +/// |-----------------------------------------|-----------------------------------------|-----------------------------------------| +/// | `StopFuzzing` | `*` | `StopFuzzing` | +/// | `*` | `StopFuzzing` | `StopFuzzing` | +/// | `AddToCorpus(FlowControl::StopFuzzing)` | `*` | `AddToCorpus(FlowControl::StopFuzzing)` | +/// | `*` | `AddToCorpus(FlowControl::StopFuzzing)` | `AddToCorpus(FlowControl::StopFuzzing)` | +/// | `Keep` | `Keep` | `Keep` | +/// | `Keep` | `Discard` | `Keep` | +/// | `Keep` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | +/// | `Keep` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Keep)` | +/// | `Discard` | `Keep` | `Keep` | +/// | `Discard` | `Discard` | `Discard` | +/// | `Discard` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | +/// | `Discard` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | +/// | `AddToCorpus(FlowControl::Keep)` | `Keep` | `AddToCorpus(FlowControl::Keep)` | +/// | `AddToCorpus(FlowControl::Keep)` | `Discard` | `AddToCorpus(FlowControl::Keep)` | +/// | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | +/// | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Keep)` | +/// | `AddToCorpus(FlowControl::Discard)` | `Keep` | `AddToCorpus(FlowControl::Keep)` | +/// | `AddToCorpus(FlowControl::Discard)` | `Discard` | `AddToCorpus(FlowControl::Discard)` | +/// | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Keep)` | `AddToCorpus(FlowControl::Keep)` | +/// | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | `AddToCorpus(FlowControl::Discard)` | +/// |-----------------------------------------|-----------------------------------------|-----------------------------------------| /// #[derive(Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -208,6 +218,8 @@ impl BitAnd for Action { (lhs, Self::AddToCorpus(name, flow_control)) => { Self::AddToCorpus(name, flow_control & lhs) } + (Self::StopFuzzing, _) => Self::StopFuzzing, + (_, Self::StopFuzzing) => Self::StopFuzzing, } } } @@ -223,6 +235,8 @@ impl BitAnd for Action { (Self::AddToCorpus(name, flow_control), other) => { Self::AddToCorpus(name, flow_control & other) } + (Self::StopFuzzing, _) => Self::StopFuzzing, + (_, FlowControl::StopFuzzing) => Self::StopFuzzing, } } } @@ -237,6 +251,8 @@ impl BitAnd for FlowControl { (Self::Keep | Self::Discard, Self::Discard) | (Self::Discard, Self::Keep) => { Self::Discard } + (Self::StopFuzzing, _) => Self::StopFuzzing, + (_, Self::StopFuzzing) => Self::StopFuzzing, } } } @@ -256,6 +272,8 @@ impl BitAnd for FlowControl { // just use the bitwise operation lhs & flow_control } + (_, Action::StopFuzzing) => Self::StopFuzzing, + (Self::StopFuzzing, Action::Keep | Action::Discard) => Self::StopFuzzing, } } } @@ -274,6 +292,8 @@ impl BitOr for Action { (lhs, Self::AddToCorpus(name, flow_control)) => { Self::AddToCorpus(name, flow_control | lhs) } + (Self::StopFuzzing, _) => Self::StopFuzzing, + (_, Self::StopFuzzing) => Self::StopFuzzing, } } } @@ -290,6 +310,8 @@ impl BitOr for Action { (Self::AddToCorpus(name, flow_control), other) => { Self::AddToCorpus(name, flow_control | other) } + (Self::StopFuzzing, _) => Self::StopFuzzing, + (_, FlowControl::StopFuzzing) => Self::StopFuzzing, } } } @@ -302,6 +324,8 @@ impl BitOr for FlowControl { match (&self, &rhs) { (Self::Keep | Self::Discard, Self::Keep) | (Self::Keep, Self::Discard) => Self::Keep, (Self::Discard, Self::Discard) => Self::Discard, + (Self::StopFuzzing, _) => Self::StopFuzzing, + (_, Self::StopFuzzing) => Self::StopFuzzing, } } } @@ -321,6 +345,8 @@ impl BitOr for FlowControl { // just use the bitwise operation lhs | flow_control } + (_, Action::StopFuzzing) => Self::StopFuzzing, + (Self::StopFuzzing, Action::Keep | Action::Discard) => Self::StopFuzzing, } } } @@ -336,6 +362,7 @@ mod tests { // action & action::keep assert_eq!(Action::Keep & Action::Keep, Action::Keep); assert_eq!(Action::Discard & Action::Keep, Action::Discard); + assert_eq!(Action::StopFuzzing & Action::Keep, Action::StopFuzzing); assert_eq!( Action::AddToCorpus("stuff", FlowControl::Keep) & Action::Keep, Action::AddToCorpus("stuff", FlowControl::Keep) @@ -344,10 +371,15 @@ mod tests { Action::AddToCorpus("stuff", FlowControl::Discard) & Action::Keep, Action::AddToCorpus("stuff", FlowControl::Discard) ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) & Action::Keep, + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); // action & action::discard assert_eq!(Action::Keep & Action::Discard, Action::Discard); assert_eq!(Action::Discard & Action::Discard, Action::Discard); + assert_eq!(Action::StopFuzzing & Action::Discard, Action::StopFuzzing); assert_eq!( Action::AddToCorpus("stuff", FlowControl::Keep) & Action::Discard, Action::AddToCorpus("stuff", FlowControl::Discard) @@ -356,6 +388,10 @@ mod tests { Action::AddToCorpus("stuff", FlowControl::Discard) & Action::Discard, Action::AddToCorpus("stuff", FlowControl::Discard) ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) & Action::Discard, + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); // action & addtocorpus::keep assert_eq!( @@ -366,6 +402,10 @@ mod tests { Action::Discard & Action::AddToCorpus("stuff", FlowControl::Keep), Action::AddToCorpus("stuff", FlowControl::Discard) ); + assert_eq!( + Action::StopFuzzing & Action::AddToCorpus("stuff", FlowControl::Keep), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); assert_eq!( Action::AddToCorpus("stuff", FlowControl::Keep) & Action::AddToCorpus("stuff", FlowControl::Keep), @@ -376,6 +416,11 @@ mod tests { & Action::AddToCorpus("stuff", FlowControl::Keep), Action::AddToCorpus("stuff", FlowControl::Discard) ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + & Action::AddToCorpus("stuff", FlowControl::Keep), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); // action & addtocorpus::discard assert_eq!( @@ -386,6 +431,10 @@ mod tests { Action::Discard & Action::AddToCorpus("stuff", FlowControl::Discard), Action::AddToCorpus("stuff", FlowControl::Discard) ); + assert_eq!( + Action::StopFuzzing & Action::AddToCorpus("stuff", FlowControl::Discard), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); assert_eq!( Action::AddToCorpus("stuff", FlowControl::Keep) & Action::AddToCorpus("stuff", FlowControl::Discard), @@ -396,6 +445,40 @@ mod tests { & Action::AddToCorpus("stuff", FlowControl::Discard), Action::AddToCorpus("stuff", FlowControl::Discard) ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + & Action::AddToCorpus("stuff", FlowControl::Discard), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); + + // action & addtocorpus::stopfuzzing + assert_eq!( + Action::Keep & Action::AddToCorpus("stuff", FlowControl::StopFuzzing), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); + assert_eq!( + Action::Discard & Action::AddToCorpus("stuff", FlowControl::StopFuzzing), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); + assert_eq!( + Action::StopFuzzing & Action::AddToCorpus("stuff", FlowControl::StopFuzzing), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::Keep) + & Action::AddToCorpus("stuff", FlowControl::StopFuzzing), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::Discard) + & Action::AddToCorpus("stuff", FlowControl::StopFuzzing), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + & Action::AddToCorpus("stuff", FlowControl::StopFuzzing), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); } /// test that the `BitAnd` implementation for `Action` and `FlowControl` @@ -405,6 +488,7 @@ mod tests { // action & flowcontrol::keep assert_eq!(Action::Keep & FlowControl::Keep, Action::Keep); assert_eq!(Action::Discard & FlowControl::Keep, Action::Discard); + assert_eq!(Action::StopFuzzing & FlowControl::Keep, Action::StopFuzzing); assert_eq!( Action::AddToCorpus("stuff", FlowControl::Keep) & FlowControl::Keep, Action::AddToCorpus("stuff", FlowControl::Keep) @@ -413,10 +497,18 @@ mod tests { Action::AddToCorpus("stuff", FlowControl::Discard) & FlowControl::Keep, Action::AddToCorpus("stuff", FlowControl::Discard) ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) & FlowControl::Keep, + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); // action & flowcontrol::discard assert_eq!(Action::Keep & FlowControl::Discard, Action::Discard); assert_eq!(Action::Discard & FlowControl::Discard, Action::Discard); + assert_eq!( + Action::StopFuzzing & FlowControl::Discard, + Action::StopFuzzing + ); assert_eq!( Action::AddToCorpus("stuff", FlowControl::Keep) & FlowControl::Discard, Action::AddToCorpus("stuff", FlowControl::Discard) @@ -425,6 +517,10 @@ mod tests { Action::AddToCorpus("stuff", FlowControl::Discard) & FlowControl::Discard, Action::AddToCorpus("stuff", FlowControl::Discard) ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) & FlowControl::Discard, + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); } /// test that the `BitAnd` implementation for `FlowControl` @@ -437,6 +533,10 @@ mod tests { FlowControl::Discard & FlowControl::Keep, FlowControl::Discard ); + assert_eq!( + FlowControl::StopFuzzing & FlowControl::Keep, + FlowControl::StopFuzzing + ); // flowcontrol & flowcontrol::discard assert_eq!( @@ -447,6 +547,10 @@ mod tests { FlowControl::Discard & FlowControl::Discard, FlowControl::Discard ); + assert_eq!( + FlowControl::StopFuzzing & FlowControl::Discard, + FlowControl::StopFuzzing + ); } /// test that the `BitAnd` implementation for `Action` and `FlowControl` @@ -456,10 +560,18 @@ mod tests { // flowcontrol & action::keep assert_eq!(FlowControl::Keep & Action::Keep, FlowControl::Keep); assert_eq!(FlowControl::Discard & Action::Keep, FlowControl::Discard); + assert_eq!( + FlowControl::StopFuzzing & Action::Keep, + FlowControl::StopFuzzing + ); // flowcontrol & action::discard assert_eq!(FlowControl::Keep & Action::Discard, FlowControl::Discard); assert_eq!(FlowControl::Discard & Action::Discard, FlowControl::Discard); + assert_eq!( + FlowControl::StopFuzzing & Action::Discard, + FlowControl::StopFuzzing + ); // flowcontrol & action::addtocorpus::keep assert_eq!( @@ -470,6 +582,10 @@ mod tests { FlowControl::Discard & Action::AddToCorpus("stuff", FlowControl::Keep), FlowControl::Discard ); + assert_eq!( + FlowControl::StopFuzzing & Action::AddToCorpus("stuff", FlowControl::Keep), + FlowControl::StopFuzzing + ); // flowcontrol & action::addtocorpus::discard assert_eq!( @@ -480,6 +596,10 @@ mod tests { FlowControl::Discard & Action::AddToCorpus("stuff", FlowControl::Discard), FlowControl::Discard ); + assert_eq!( + FlowControl::StopFuzzing & Action::AddToCorpus("stuff", FlowControl::Discard), + FlowControl::StopFuzzing + ); } // bitor operations @@ -491,6 +611,7 @@ mod tests { // action | action::keep assert_eq!(Action::Keep | Action::Keep, Action::Keep); assert_eq!(Action::Discard | Action::Keep, Action::Keep); + assert_eq!(Action::StopFuzzing | Action::Keep, Action::StopFuzzing); assert_eq!( Action::AddToCorpus("stuff", FlowControl::Keep) | Action::Keep, Action::AddToCorpus("stuff", FlowControl::Keep) @@ -499,10 +620,15 @@ mod tests { Action::AddToCorpus("stuff", FlowControl::Discard) | Action::Keep, Action::AddToCorpus("stuff", FlowControl::Keep) ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) | Action::Keep, + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); // action | action::discard assert_eq!(Action::Keep | Action::Discard, Action::Keep); assert_eq!(Action::Discard | Action::Discard, Action::Discard); + assert_eq!(Action::StopFuzzing | Action::Discard, Action::StopFuzzing); assert_eq!( Action::AddToCorpus("stuff", FlowControl::Keep) | Action::Discard, Action::AddToCorpus("stuff", FlowControl::Keep) @@ -511,6 +637,10 @@ mod tests { Action::AddToCorpus("stuff", FlowControl::Discard) | Action::Discard, Action::AddToCorpus("stuff", FlowControl::Discard) ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) | Action::Discard, + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); // action | addtocorpus::keep assert_eq!( @@ -521,6 +651,10 @@ mod tests { Action::Discard | Action::AddToCorpus("stuff", FlowControl::Keep), Action::AddToCorpus("stuff", FlowControl::Keep) ); + assert_eq!( + Action::StopFuzzing | Action::AddToCorpus("stuff", FlowControl::Keep), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); assert_eq!( Action::AddToCorpus("stuff", FlowControl::Keep) | Action::AddToCorpus("stuff", FlowControl::Keep), @@ -531,6 +665,11 @@ mod tests { | Action::AddToCorpus("stuff", FlowControl::Keep), Action::AddToCorpus("stuff", FlowControl::Keep) ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + | Action::AddToCorpus("stuff", FlowControl::Keep), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); // action | addtocorpus::discard assert_eq!( @@ -541,6 +680,10 @@ mod tests { Action::Discard | Action::AddToCorpus("stuff", FlowControl::Discard), Action::AddToCorpus("stuff", FlowControl::Discard) ); + assert_eq!( + Action::StopFuzzing | Action::AddToCorpus("stuff", FlowControl::Discard), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); assert_eq!( Action::AddToCorpus("stuff", FlowControl::Keep) | Action::AddToCorpus("stuff", FlowControl::Discard), @@ -551,6 +694,11 @@ mod tests { | Action::AddToCorpus("stuff", FlowControl::Discard), Action::AddToCorpus("stuff", FlowControl::Discard) ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + | Action::AddToCorpus("stuff", FlowControl::Discard), + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); } /// test that the `BitOr` implementation for `Action` and `FlowControl` @@ -560,6 +708,7 @@ mod tests { // action | flowcontrol::keep assert_eq!(Action::Keep | FlowControl::Keep, Action::Keep); assert_eq!(Action::Discard | FlowControl::Keep, Action::Keep); + assert_eq!(Action::StopFuzzing | FlowControl::Keep, Action::StopFuzzing); assert_eq!( Action::AddToCorpus("stuff", FlowControl::Keep) | FlowControl::Keep, Action::AddToCorpus("stuff", FlowControl::Keep) @@ -568,10 +717,18 @@ mod tests { Action::AddToCorpus("stuff", FlowControl::Discard) | FlowControl::Keep, Action::AddToCorpus("stuff", FlowControl::Keep) ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) | FlowControl::Keep, + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); // action | flowcontrol::discard assert_eq!(Action::Keep | FlowControl::Discard, Action::Keep); assert_eq!(Action::Discard | FlowControl::Discard, Action::Discard); + assert_eq!( + Action::StopFuzzing | FlowControl::Discard, + Action::StopFuzzing + ); assert_eq!( Action::AddToCorpus("stuff", FlowControl::Keep) | FlowControl::Discard, Action::AddToCorpus("stuff", FlowControl::Keep) @@ -580,6 +737,10 @@ mod tests { Action::AddToCorpus("stuff", FlowControl::Discard) | FlowControl::Discard, Action::AddToCorpus("stuff", FlowControl::Discard) ); + assert_eq!( + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) | FlowControl::Discard, + Action::AddToCorpus("stuff", FlowControl::StopFuzzing) + ); } /// test that the `BitOr` implementation for `FlowControl` @@ -589,6 +750,10 @@ mod tests { // flowcontrol | flowcontrol::keep assert_eq!(FlowControl::Keep | FlowControl::Keep, FlowControl::Keep); assert_eq!(FlowControl::Discard | FlowControl::Keep, FlowControl::Keep); + assert_eq!( + FlowControl::StopFuzzing | FlowControl::Keep, + FlowControl::StopFuzzing + ); // flowcontrol | flowcontrol::discard assert_eq!(FlowControl::Keep | FlowControl::Discard, FlowControl::Keep); @@ -596,6 +761,10 @@ mod tests { FlowControl::Discard | FlowControl::Discard, FlowControl::Discard ); + assert_eq!( + FlowControl::StopFuzzing | FlowControl::Discard, + FlowControl::StopFuzzing + ); } /// test that the `BitOr` implementation for `Action` and `FlowControl` @@ -605,10 +774,18 @@ mod tests { // flowcontrol | action::keep assert_eq!(FlowControl::Keep | Action::Keep, FlowControl::Keep); assert_eq!(FlowControl::Discard | Action::Keep, FlowControl::Keep); + assert_eq!( + FlowControl::StopFuzzing | Action::Keep, + FlowControl::StopFuzzing + ); // flowcontrol | action::discard assert_eq!(FlowControl::Keep | Action::Discard, FlowControl::Keep); assert_eq!(FlowControl::Discard | Action::Discard, FlowControl::Discard); + assert_eq!( + FlowControl::StopFuzzing | Action::Discard, + FlowControl::StopFuzzing + ); // flowcontrol | action::addtocorpus::keep assert_eq!( @@ -619,6 +796,10 @@ mod tests { FlowControl::Discard | Action::AddToCorpus("stuff", FlowControl::Keep), FlowControl::Keep ); + assert_eq!( + FlowControl::StopFuzzing | Action::AddToCorpus("stuff", FlowControl::Keep), + FlowControl::StopFuzzing + ); // flowcontrol | action::addtocorpus::discard assert_eq!( @@ -629,5 +810,9 @@ mod tests { FlowControl::Discard | Action::AddToCorpus("stuff", FlowControl::Discard), FlowControl::Discard ); + assert_eq!( + FlowControl::StopFuzzing | Action::AddToCorpus("stuff", FlowControl::Discard), + FlowControl::StopFuzzing + ); } } From 3e1cd8632dfa59c18a2030cbd72f97540c1185e6 Mon Sep 17 00:00:00 2001 From: epi Date: Mon, 10 Oct 2022 09:17:57 -0500 Subject: [PATCH 2/6] added StopFuzzing action --- src/fuzzers/async_fuzzer.rs | 229 +++++++++++++++++++++++++++++++-- src/fuzzers/blocking_fuzzer.rs | 188 +++++++++++++++++++++++++++ src/fuzzers/mod.rs | 12 +- src/mutators/afl.rs | 2 +- src/std_ext/ops.rs | 30 ++--- 5 files changed, 428 insertions(+), 33 deletions(-) diff --git a/src/fuzzers/async_fuzzer.rs b/src/fuzzers/async_fuzzer.rs index f3cf47d..2f8906e 100644 --- a/src/fuzzers/async_fuzzer.rs +++ b/src/fuzzers/async_fuzzer.rs @@ -150,7 +150,7 @@ where // let mut post_send_action = &mut None; let should_quit = Arc::new(AtomicBool::new(false)); - let maybe_action = stream::iter(scheduler) + stream::iter(scheduler) .map( |_| -> Result< ( @@ -249,7 +249,6 @@ where if should_quit.load(Ordering::Relaxed) { // this check accounts for us setting the action to StopFuzzing in the PostSend phase **err = Err(FeroxFuzzError::FuzzingStopped); - println!("[REAL3] Stopping fuzzing due to FlowControl::StopFuzzing"); return future::ready(None); } @@ -262,6 +261,7 @@ where future::ready(Some(Err(e))) } else { // this is the check that comes from PreSend + should_quit.store(true, Ordering::Relaxed); **err = Err(FeroxFuzzError::FuzzingStopped); future::ready(None) } @@ -286,7 +286,7 @@ where // result cannot be Err after this point, so is safe to unwrap let (resp, mut c_observers, mut c_deciders, mut c_processors, c_request, c_state, c_should_quit) = result.unwrap(); - + if c_should_quit.load(Ordering::Relaxed) { return; } @@ -343,26 +343,235 @@ where if matches!(flow_control, FlowControl::StopFuzzing) { c_should_quit.store(true, Ordering::Relaxed); - } + } } Some(Action::StopFuzzing) => { c_should_quit.store(true, Ordering::Relaxed); - } + } _ => {} } }) .await; - println!("Fuzzing complete: {:?}", maybe_action); + // in case we're fuzzing more than once, reset the scheduler + self.scheduler.reset(); - if let Err(FeroxFuzzError::FuzzingStopped) = err { + if should_quit.load(Ordering::SeqCst) { return Ok(Some(Action::StopFuzzing)); } - // in case we're fuzzing more than once, reset the scheduler - self.scheduler.reset(); - Ok(None) // no action taken } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::client::AsyncClient; + use crate::corpora::RangeCorpus; + use crate::deciders::{RequestRegexDecider, ResponseRegexDecider}; + use crate::fuzzers::AsyncFuzzer; + use crate::mutators::ReplaceKeyword; + use crate::observers::ResponseObserver; + use crate::prelude::*; + use crate::requests::ShouldFuzz; + use crate::responses::AsyncResponse; + use crate::schedulers::OrderedScheduler; + use ::regex::Regex; + use httpmock::Method::GET; + use httpmock::MockServer; + use reqwest; + use std::time::Duration; + + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + /// test that the fuzz loop will stop if the decider returns a StopFuzzing action in + /// the pre-send phase + async fn test_async_fuzzer_stops_fuzzing_pre_send() -> Result<(), Box> { + let srv = MockServer::start(); + + let _mock = srv.mock(|when, then| { + // registers hits for 0, 1, 2 + when.method(GET).path_matches(Regex::new("[012]").unwrap()); + then.status(200).body("this is a test"); + }); + + // 0, 1, 2 + let range = RangeCorpus::with_stop(3).name("range").build()?; + let mut state = SharedState::with_corpus(range); + + let req_client = reqwest::Client::builder() + .timeout(Duration::from_secs(1)) + .build()?; + + let client = AsyncClient::with_client(req_client); + + let mutator = ReplaceKeyword::new(&"FUZZ", "range"); + + let request = Request::from_url(&srv.url("/FUZZ"), Some(&[ShouldFuzz::URLPath(b"/FUZZ")]))?; + + // stop fuzzing if path matches '1' + let decider = RequestRegexDecider::new("1", |regex, request, _state| { + if regex.is_match(request.path().inner()) { + Action::StopFuzzing + } else { + Action::Keep + } + }); + + let mut fuzzer = AsyncFuzzer::new( + 1, + client.clone(), + request.clone(), + OrderedScheduler::new(state.clone())?, + build_mutators!(mutator.clone()), + build_observers!(ResponseObserver::new()), + (), + build_deciders!(decider.clone()), + ); + + fuzzer.fuzz_once(&mut state.clone()).await?; + + // /1 and /2 never sent + assert_eq!( + state + .stats() + .read() + .unwrap() + .status_code_count(200) + .unwrap(), + 1 + ); + + fuzzer.scheduler.reset(); + fuzzer.fuzz_n_iterations(3, &mut state).await?; + + // /1 and /2 never sent, but /0 was sent again + assert_eq!( + state + .stats() + .read() + .unwrap() + .status_code_count(200) + .unwrap(), + 2 + ); + + fuzzer.scheduler.reset(); + fuzzer.fuzz(&mut state).await?; + + // /1 and /2 never sent, but /0 was sent again + assert_eq!( + state + .stats() + .read() + .unwrap() + .status_code_count(200) + .unwrap(), + 3 + ); + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + /// test that the fuzz loop will stop if the decider returns a StopFuzzing action + /// in the post-send phase + async fn test_async_fuzzer_stops_fuzzing_post_send() -> Result<(), Box> { + let srv = MockServer::start(); + + let _mock = srv.mock(|when, then| { + // registers hits for 0 + when.method(GET).path_matches(Regex::new("[02]").unwrap()); + then.status(200).body("this is a test"); + }); + + let _mock2 = srv.mock(|when, then| { + // registers hits for 1, 2 + #[allow(clippy::trivial_regex)] + when.method(GET).path_matches(Regex::new("1").unwrap()); + then.status(201).body("derp"); + }); + + // 0, 1, 2 + let range = RangeCorpus::with_stop(3).name("range").build()?; + let mut state = SharedState::with_corpus(range.clone()); + + let req_client = reqwest::Client::builder() + .timeout(Duration::from_secs(1)) + .build()?; + + let client = AsyncClient::with_client(req_client); + + let mutator = ReplaceKeyword::new(&"FUZZ", "range"); + + let request = Request::from_url(&srv.url("/FUZZ"), Some(&[ShouldFuzz::URLPath(b"/FUZZ")]))?; + + // stop fuzzing if path matches '1' + let decider = ResponseRegexDecider::new("derp", |regex, response, _state| { + if regex.is_match(response.body()) { + Action::StopFuzzing + } else { + Action::Keep + } + }); + + let scheduler = OrderedScheduler::new(state.clone())?; + let response_observer: ResponseObserver = ResponseObserver::new(); + + let observers = build_observers!(response_observer); + let deciders = build_deciders!(decider); + let mutators = build_mutators!(mutator); + + let mut fuzzer = AsyncFuzzer::new( + 1, + client, + request, + scheduler, + mutators, + observers, + (), + deciders, + ); + + fuzzer.fuzz_once(&mut state).await?; + + // /0 sent/recv'd and ok + // /1 sent/recv'd and bad + // /2 never *processed* + // + // in an async context, this works ok by itself with a threadcount of 1, but the other request + // is still in-flight and will likely hit the target, this matters for the following test + // assertions as the expected count is more than what one may think is accurate + if let Ok(guard) = state.stats().read() { + assert!((guard.requests() - 2.0).abs() < std::f64::EPSILON); + assert_eq!(guard.status_code_count(200).unwrap(), 1); + assert_eq!(guard.status_code_count(201).unwrap(), 1); + } + + fuzzer.scheduler.reset(); + fuzzer.fuzz_n_iterations(2, &mut state).await?; + + // at this point, /2 was hit from the previous test, so we're 1 higher than expected + if let Ok(guard) = state.stats().read() { + assert!((guard.requests() - 4.0).abs() < std::f64::EPSILON); + assert_eq!(guard.status_code_count(200).unwrap(), 2); + assert_eq!(guard.status_code_count(201).unwrap(), 2); + } + + fuzzer.scheduler.reset(); + fuzzer.fuzz(&mut state).await?; + + // at this point, /2 was hit from both previous tests, so we're 2 higher than expected + if let Ok(guard) = state.stats().read() { + assert!((guard.requests() - 6.0).abs() < std::f64::EPSILON); + assert_eq!(guard.status_code_count(200).unwrap(), 3); + assert_eq!(guard.status_code_count(201).unwrap(), 3); + } + + // the take away is that the fuzz/fuzz_n_iterations methods stop when told to, even though + // the request is still in-flight, i.e. we don't have a never-ending test or anything + // which proves that the logic is working and correct + Ok(()) + } +} diff --git a/src/fuzzers/blocking_fuzzer.rs b/src/fuzzers/blocking_fuzzer.rs index 0781ec8..bc1298b 100644 --- a/src/fuzzers/blocking_fuzzer.rs +++ b/src/fuzzers/blocking_fuzzer.rs @@ -153,10 +153,18 @@ where state.add_to_corpus(name, &mutated_request)?; if matches!(flow_control, FlowControl::StopFuzzing) { + tracing::info!( + "[ID: {}] stopping fuzzing due to AddToCorpus[StopFuzzing] action", + self.request_id + ); return Ok(Some(Action::StopFuzzing)); } } Some(Action::StopFuzzing) => { + tracing::info!( + "[ID: {}] stopping fuzzing due to StopFuzzing action", + self.request_id + ); return Ok(Some(Action::StopFuzzing)); } _ => {} @@ -210,3 +218,183 @@ where &mut self.request } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::client::BlockingClient; + use crate::corpora::RangeCorpus; + use crate::deciders::{RequestRegexDecider, ResponseRegexDecider}; + use crate::fuzzers::BlockingFuzzer; + use crate::mutators::ReplaceKeyword; + use crate::observers::ResponseObserver; + use crate::prelude::*; + use crate::requests::ShouldFuzz; + use crate::responses::BlockingResponse; + use crate::schedulers::OrderedScheduler; + use ::regex::Regex; + use httpmock::Method::GET; + use httpmock::MockServer; + use reqwest; + use std::time::Duration; + + #[test] + /// test that the fuzz loop will stop if the decider returns a `StopFuzzing` action in + /// the pre-send phase + fn test_blocking_fuzzer_stops_fuzzing_pre_send() -> Result<(), Box> { + let srv = MockServer::start(); + + let mock = srv.mock(|when, then| { + // registers hits for 0, 1, 2 + when.method(GET).path_matches(Regex::new("[012]").unwrap()); + then.status(200).body("this is a test"); + }); + + // 0, 1, 2 + let range = RangeCorpus::with_stop(3).name("range").build()?; + let mut state = SharedState::with_corpus(range); + + let req_client = reqwest::blocking::Client::builder() + .timeout(Duration::from_secs(1)) + .build()?; + + let client = BlockingClient::with_client(req_client); + + let mutator = ReplaceKeyword::new(&"FUZZ", "range"); + + let request = Request::from_url(&srv.url("/FUZZ"), Some(&[ShouldFuzz::URLPath(b"/FUZZ")]))?; + + // stop fuzzing if path matches '1' + let decider = RequestRegexDecider::new("1", |regex, request, _state| { + if regex.is_match(request.path().inner()) { + Action::StopFuzzing + } else { + Action::Keep + } + }); + + let scheduler = OrderedScheduler::new(state.clone())?; + let response_observer: ResponseObserver = ResponseObserver::new(); + + let observers = build_observers!(response_observer); + let deciders = build_deciders!(decider); + let mutators = build_mutators!(mutator); + + let mut fuzzer = BlockingFuzzer::new( + client, + request, + scheduler, + mutators, + observers, + (), + deciders, + ); + + fuzzer.fuzz_once(&mut state.clone())?; + + // /1 and /2 never sent + mock.assert_hits(1); + + fuzzer.scheduler.reset(); + fuzzer.fuzz_n_iterations(1, &mut state.clone())?; + + // /1 and /2 never sent, but /0 was sent again + mock.assert_hits(2); + + fuzzer.scheduler.reset(); + fuzzer.fuzz(&mut state)?; + + // /1 and /2 never sent, but /0 was sent again + mock.assert_hits(3); + + Ok(()) + } + + #[test] + /// test that the fuzz loop will stop if the decider returns a `StopFuzzing` action + /// in the post-send phase + fn test_blocking_fuzzer_stops_fuzzing_post_send() -> Result<(), Box> { + let srv = MockServer::start(); + + let mock = srv.mock(|when, then| { + // registers hits for 0/2 + when.method(GET).path_matches(Regex::new("[02]").unwrap()); + then.status(200).body("this is a test"); + }); + + let mock2 = srv.mock(|when, then| { + // registers hits for 1 + #[allow(clippy::trivial_regex)] + when.method(GET).path_matches(Regex::new("1").unwrap()); + then.status(200).body("derp"); + }); + + // 0, 1, 2 + let range = RangeCorpus::with_stop(3).name("range").build()?; + let mut state = SharedState::with_corpus(range); + + let req_client = reqwest::blocking::Client::builder() + .timeout(Duration::from_secs(1)) + .build()?; + + let client = BlockingClient::with_client(req_client); + + let mutator = ReplaceKeyword::new(&"FUZZ", "range"); + + let request = Request::from_url(&srv.url("/FUZZ"), Some(&[ShouldFuzz::URLPath(b"/FUZZ")]))?; + + // stop fuzzing if path matches '1' + let decider = ResponseRegexDecider::new("derp", |regex, response, _state| { + if regex.is_match(response.body()) { + Action::StopFuzzing + } else { + Action::Keep + } + }); + + let scheduler = OrderedScheduler::new(state.clone())?; + let response_observer: ResponseObserver = ResponseObserver::new(); + + let observers = build_observers!(response_observer); + let deciders = build_deciders!(decider); + let mutators = build_mutators!(mutator); + + let mut fuzzer = BlockingFuzzer::new( + client, + request, + scheduler, + mutators, + observers, + (), + deciders, + ); + + fuzzer.fuzz_once(&mut state)?; + + // /0 sent/recv'd and ok + // /1 sent/recv'd and bad + // /2 never sent + mock.assert_hits(1); + mock2.assert_hits(1); + + // fuzzer.scheduler.reset(); + // fuzzer.fuzz_n_iterations(2, &mut state)?; + + // // /0 sent/recv'd and ok + // // /1 sent/recv'd and bad + // // /2 never sent + // mock.assert_hits(2); + // mock2.assert_hits(2); + + // fuzzer.scheduler.reset(); + // fuzzer.fuzz(&mut state)?; + + // // /0 sent/recv'd and ok + // // /1 sent/recv'd and bad + // // /2 never sent + // mock.assert_hits(3); + // mock2.assert_hits(3); + + Ok(()) + } +} diff --git a/src/fuzzers/mod.rs b/src/fuzzers/mod.rs index 6cd922f..18c8a94 100644 --- a/src/fuzzers/mod.rs +++ b/src/fuzzers/mod.rs @@ -64,7 +64,7 @@ pub trait AsyncFuzzing: Fuzzer { /// see [`Fuzzer`]'s Errors section for more details async fn fuzz(&mut self, state: &mut SharedState) -> Result<(), FeroxFuzzError> { loop { - if let Some(Action::StopFuzzing) = self.fuzz_once(state).await? { + if self.fuzz_once(state).await? == Some(Action::StopFuzzing) { break Ok(()); } } @@ -95,8 +95,8 @@ pub trait AsyncFuzzing: Fuzzer { state: &mut SharedState, ) -> Result<(), FeroxFuzzError> { for _ in 0..num_iterations { - if let Some(Action::StopFuzzing) = self.fuzz_once(state).await? { - break; + if self.fuzz_once(state).await? == Some(Action::StopFuzzing) { + return Ok(()); } } @@ -116,7 +116,7 @@ pub trait BlockingFuzzing: Fuzzer { /// see [`Fuzzer`]'s Errors section for more details fn fuzz(&mut self, state: &mut SharedState) -> Result<(), FeroxFuzzError> { loop { - if let Some(Action::StopFuzzing) = self.fuzz_once(state)? { + if self.fuzz_once(state)? == Some(Action::StopFuzzing) { break Ok(()); } } @@ -144,8 +144,8 @@ pub trait BlockingFuzzing: Fuzzer { state: &mut SharedState, ) -> Result<(), FeroxFuzzError> { for _ in 0..num_iterations { - if let Some(Action::StopFuzzing) = self.fuzz_once(state)? { - break; + if self.fuzz_once(state)? == Some(Action::StopFuzzing) { + return Ok(()); } } diff --git a/src/mutators/afl.rs b/src/mutators/afl.rs index 952a746..ee6c4ce 100644 --- a/src/mutators/afl.rs +++ b/src/mutators/afl.rs @@ -4,8 +4,8 @@ use std::any::Any; use std::cmp::min; use std::fmt; -use std::sync::atomic::Ordering; use std::marker::PhantomData; +use std::sync::atomic::Ordering; use super::Mutator; use crate::corpora::Corpus; diff --git a/src/std_ext/ops.rs b/src/std_ext/ops.rs index a766c27..02f4420 100644 --- a/src/std_ext/ops.rs +++ b/src/std_ext/ops.rs @@ -218,8 +218,7 @@ impl BitAnd for Action { (lhs, Self::AddToCorpus(name, flow_control)) => { Self::AddToCorpus(name, flow_control & lhs) } - (Self::StopFuzzing, _) => Self::StopFuzzing, - (_, Self::StopFuzzing) => Self::StopFuzzing, + (_, Self::StopFuzzing) | (Self::StopFuzzing, _) => Self::StopFuzzing, } } } @@ -235,8 +234,7 @@ impl BitAnd for Action { (Self::AddToCorpus(name, flow_control), other) => { Self::AddToCorpus(name, flow_control & other) } - (Self::StopFuzzing, _) => Self::StopFuzzing, - (_, FlowControl::StopFuzzing) => Self::StopFuzzing, + (Self::StopFuzzing, _) | (_, FlowControl::StopFuzzing) => Self::StopFuzzing, } } } @@ -251,8 +249,7 @@ impl BitAnd for FlowControl { (Self::Keep | Self::Discard, Self::Discard) | (Self::Discard, Self::Keep) => { Self::Discard } - (Self::StopFuzzing, _) => Self::StopFuzzing, - (_, Self::StopFuzzing) => Self::StopFuzzing, + (_, Self::StopFuzzing) | (Self::StopFuzzing, _) => Self::StopFuzzing, } } } @@ -272,8 +269,9 @@ impl BitAnd for FlowControl { // just use the bitwise operation lhs & flow_control } - (_, Action::StopFuzzing) => Self::StopFuzzing, - (Self::StopFuzzing, Action::Keep | Action::Discard) => Self::StopFuzzing, + (Self::StopFuzzing, Action::Keep | Action::Discard) | (_, Action::StopFuzzing) => { + Self::StopFuzzing + } } } } @@ -292,8 +290,7 @@ impl BitOr for Action { (lhs, Self::AddToCorpus(name, flow_control)) => { Self::AddToCorpus(name, flow_control | lhs) } - (Self::StopFuzzing, _) => Self::StopFuzzing, - (_, Self::StopFuzzing) => Self::StopFuzzing, + (Self::StopFuzzing, _) | (_, Self::StopFuzzing) => Self::StopFuzzing, } } } @@ -310,8 +307,7 @@ impl BitOr for Action { (Self::AddToCorpus(name, flow_control), other) => { Self::AddToCorpus(name, flow_control | other) } - (Self::StopFuzzing, _) => Self::StopFuzzing, - (_, FlowControl::StopFuzzing) => Self::StopFuzzing, + (_, FlowControl::StopFuzzing) | (Self::StopFuzzing, _) => Self::StopFuzzing, } } } @@ -324,8 +320,7 @@ impl BitOr for FlowControl { match (&self, &rhs) { (Self::Keep | Self::Discard, Self::Keep) | (Self::Keep, Self::Discard) => Self::Keep, (Self::Discard, Self::Discard) => Self::Discard, - (Self::StopFuzzing, _) => Self::StopFuzzing, - (_, Self::StopFuzzing) => Self::StopFuzzing, + (_, Self::StopFuzzing) | (Self::StopFuzzing, _) => Self::StopFuzzing, } } } @@ -345,8 +340,9 @@ impl BitOr for FlowControl { // just use the bitwise operation lhs | flow_control } - (_, Action::StopFuzzing) => Self::StopFuzzing, - (Self::StopFuzzing, Action::Keep | Action::Discard) => Self::StopFuzzing, + (_, Action::StopFuzzing) | (Self::StopFuzzing, Action::Keep | Action::Discard) => { + Self::StopFuzzing + } } } } @@ -358,6 +354,8 @@ mod tests { /// test that the `BitAnd` implementation for `Action` produces the correct /// results when action is both the lhs and rhs #[test] + #[allow(clippy::cognitive_complexity)] + #[allow(clippy::too_many_lines)] fn test_bitand_action_and_action() { // action & action::keep assert_eq!(Action::Keep & Action::Keep, Action::Keep); From e33c68f9c36f047af76741bae6c36ef1d0724961 Mon Sep 17 00:00:00 2001 From: epi Date: Mon, 10 Oct 2022 09:30:12 -0500 Subject: [PATCH 3/6] added logging; removed cruft --- src/fuzzers/async_fuzzer.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/fuzzers/async_fuzzer.rs b/src/fuzzers/async_fuzzer.rs index 2f8906e..cb36489 100644 --- a/src/fuzzers/async_fuzzer.rs +++ b/src/fuzzers/async_fuzzer.rs @@ -21,7 +21,7 @@ use crate::mutators::Mutators; use crate::observers::Observers; use crate::processors::Processors; use crate::requests::Request; -use crate::responses::AsyncResponse; +use crate::responses::{AsyncResponse, Response}; use crate::schedulers::Scheduler; use crate::state::SharedState; use crate::std_ext::ops::LogicOperation; @@ -144,11 +144,9 @@ where let post_send_logic = self.post_send_logic().unwrap_or_default(); let scheduler = self.scheduler.clone(); - // facilitates a way to 'break' out of the iterator - let mut err = Ok(()); - let mut pre_send_action = None; - // let mut post_send_action = &mut None; + // facilitates a threadsafe way to 'break' out of the iterator let should_quit = Arc::new(AtomicBool::new(false)); + let mut err = Ok(()); stream::iter(scheduler) .map( @@ -199,7 +197,6 @@ where match flow_control { FlowControl::StopFuzzing => { - pre_send_action = Some(Action::StopFuzzing); return Err(FeroxFuzzError::FuzzingStopped); } FlowControl::Discard => { @@ -245,7 +242,6 @@ where )) }, ).scan(&mut err, |err, result| { - // println!("post send action: {:?}", post_send_action); if should_quit.load(Ordering::Relaxed) { // this check accounts for us setting the action to StopFuzzing in the PostSend phase **err = Err(FeroxFuzzError::FuzzingStopped); @@ -315,6 +311,8 @@ where return; } + let request_id = response.unwrap().id(); // only used for logging + // response cannot be Err after this point, so is safe to unwrap c_observers.call_post_send_hooks(response.unwrap()); @@ -342,10 +340,18 @@ where } if matches!(flow_control, FlowControl::StopFuzzing) { + tracing::info!( + "[ID: {}] stopping fuzzing due to AddToCorpus[StopFuzzing] action", + request_id + ); c_should_quit.store(true, Ordering::Relaxed); } } Some(Action::StopFuzzing) => { + tracing::info!( + "[ID: {}] stopping fuzzing due to AddToCorpus[StopFuzzing] action", + request_id + ); c_should_quit.store(true, Ordering::Relaxed); } _ => {} @@ -357,7 +363,7 @@ where // in case we're fuzzing more than once, reset the scheduler self.scheduler.reset(); - if should_quit.load(Ordering::SeqCst) { + if err.is_err() { return Ok(Some(Action::StopFuzzing)); } From 2be5f954e4e9e4f1886bccae2f0280f662644c46 Mon Sep 17 00:00:00 2001 From: epi Date: Mon, 10 Oct 2022 09:33:09 -0500 Subject: [PATCH 4/6] fixed stuff --- src/fuzzers/async_fuzzer.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/fuzzers/async_fuzzer.rs b/src/fuzzers/async_fuzzer.rs index cb36489..6e8eb2a 100644 --- a/src/fuzzers/async_fuzzer.rs +++ b/src/fuzzers/async_fuzzer.rs @@ -197,6 +197,10 @@ where match flow_control { FlowControl::StopFuzzing => { + tracing::info!( + "[ID: {}] stopping fuzzing due to AddToCorpus[StopFuzzing] action", + self.request_id + ); return Err(FeroxFuzzError::FuzzingStopped); } FlowControl::Discard => { @@ -209,6 +213,10 @@ where // ignore when flow control is Keep, same as we do for Action::Keep below } Some(Action::StopFuzzing) => { + tracing::info!( + "[ID: {}] stopping fuzzing due to StopFuzzing action", + self.request_id + ); return Err(FeroxFuzzError::FuzzingStopped); } _ => { @@ -349,7 +357,7 @@ where } Some(Action::StopFuzzing) => { tracing::info!( - "[ID: {}] stopping fuzzing due to AddToCorpus[StopFuzzing] action", + "[ID: {}] stopping fuzzing due to StopFuzzing action", request_id ); c_should_quit.store(true, Ordering::Relaxed); From ce8c74c4cf88a8766b5cfd51fd69e5371c090f3b Mon Sep 17 00:00:00 2001 From: epi Date: Mon, 10 Oct 2022 09:34:54 -0500 Subject: [PATCH 5/6] fixed quit logic --- src/fuzzers/async_fuzzer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fuzzers/async_fuzzer.rs b/src/fuzzers/async_fuzzer.rs index 6e8eb2a..2e6dd76 100644 --- a/src/fuzzers/async_fuzzer.rs +++ b/src/fuzzers/async_fuzzer.rs @@ -371,7 +371,7 @@ where // in case we're fuzzing more than once, reset the scheduler self.scheduler.reset(); - if err.is_err() { + if err.is_err() || should_quit.load(Ordering::SeqCst) { return Ok(Some(Action::StopFuzzing)); } From 5e7500bb05c8d1d57f0278b7b91762055c581573 Mon Sep 17 00:00:00 2001 From: epi Date: Mon, 10 Oct 2022 09:37:10 -0500 Subject: [PATCH 6/6] fixed version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9d49827..8c23e5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "feroxfuzz" -version = "0.1.0-rc.0" +version = "1.0.0-rc.1" edition = "2021" authors = ["Ben 'epi' Risher (@epi052)"] license = "Apache-2.0"