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" 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..2e6dd76 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; @@ -18,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; @@ -133,11 +136,18 @@ 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(); + // 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( |_| -> Result< @@ -148,6 +158,7 @@ where P, Request, SharedState, + Arc, ), FeroxFuzzError, > { @@ -184,12 +195,30 @@ 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 => { + tracing::info!( + "[ID: {}] stopping fuzzing due to AddToCorpus[StopFuzzing] action", + self.request_id + ); + 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) => { + tracing::info!( + "[ID: {}] stopping fuzzing due to StopFuzzing action", + self.request_id + ); + return Err(FeroxFuzzError::FuzzingStopped); + } _ => { // do nothing if it's None or an Action::Keep } @@ -201,6 +230,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 +246,41 @@ where cloned_processors, cloned_request, cloned_state, + cloned_quit_flag, )) }, - ) - .for_each_concurrent(num_threads, |result| async move { + ).scan(&mut err, |err, result| { + if should_quit.load(Ordering::Relaxed) { + // this check accounts for us setting the action to StopFuzzing in the PostSend phase + **err = Err(FeroxFuzzError::FuzzingStopped); + 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 + should_quit.store(true, Ordering::Relaxed); + **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,9 +288,13 @@ 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> let response = resp.await; @@ -261,6 +319,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()); @@ -277,21 +337,255 @@ 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) { + 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 StopFuzzing action", + request_id + ); + c_should_quit.store(true, Ordering::Relaxed); + } + _ => {} + } - }) + }) .await; // in case we're fuzzing more than once, reset the scheduler self.scheduler.reset(); + if err.is_err() || should_quit.load(Ordering::SeqCst) { + return Ok(Some(Action::StopFuzzing)); + } + + 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 ae21640..bc1298b 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,29 @@ 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) { + 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)); + } + _ => {} } self.request_id += 1; @@ -156,7 +176,7 @@ where // in case we're fuzzing more than once, reset the scheduler self.scheduler.reset(); - Ok(()) + Ok(None) // no action to take } } @@ -198,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 cfbe2bb..18c8a94 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 self.fuzz_once(state).await? == Some(Action::StopFuzzing) { + 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 self.fuzz_once(state).await? == Some(Action::StopFuzzing) { + return Ok(()); + } } 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 self.fuzz_once(state)? == Some(Action::StopFuzzing) { + 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 self.fuzz_once(state)? == Some(Action::StopFuzzing) { + return Ok(()); + } } 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 4dc340c..02f4420 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,7 @@ impl BitAnd for Action { (lhs, Self::AddToCorpus(name, flow_control)) => { Self::AddToCorpus(name, flow_control & lhs) } + (_, Self::StopFuzzing) | (Self::StopFuzzing, _) => Self::StopFuzzing, } } } @@ -223,6 +234,7 @@ impl BitAnd for Action { (Self::AddToCorpus(name, flow_control), other) => { Self::AddToCorpus(name, flow_control & other) } + (Self::StopFuzzing, _) | (_, FlowControl::StopFuzzing) => Self::StopFuzzing, } } } @@ -237,6 +249,7 @@ impl BitAnd for FlowControl { (Self::Keep | Self::Discard, Self::Discard) | (Self::Discard, Self::Keep) => { Self::Discard } + (_, Self::StopFuzzing) | (Self::StopFuzzing, _) => Self::StopFuzzing, } } } @@ -256,6 +269,9 @@ impl BitAnd for FlowControl { // just use the bitwise operation lhs & flow_control } + (Self::StopFuzzing, Action::Keep | Action::Discard) | (_, Action::StopFuzzing) => { + Self::StopFuzzing + } } } } @@ -274,6 +290,7 @@ impl BitOr for Action { (lhs, Self::AddToCorpus(name, flow_control)) => { Self::AddToCorpus(name, flow_control | lhs) } + (Self::StopFuzzing, _) | (_, Self::StopFuzzing) => Self::StopFuzzing, } } } @@ -290,6 +307,7 @@ impl BitOr for Action { (Self::AddToCorpus(name, flow_control), other) => { Self::AddToCorpus(name, flow_control | other) } + (_, FlowControl::StopFuzzing) | (Self::StopFuzzing, _) => Self::StopFuzzing, } } } @@ -302,6 +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, } } } @@ -321,6 +340,9 @@ impl BitOr for FlowControl { // just use the bitwise operation lhs | flow_control } + (_, Action::StopFuzzing) | (Self::StopFuzzing, Action::Keep | Action::Discard) => { + Self::StopFuzzing + } } } } @@ -332,10 +354,13 @@ 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); 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 +369,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 +386,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 +400,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 +414,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 +429,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 +443,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 +486,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 +495,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 +515,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 +531,10 @@ mod tests { FlowControl::Discard & FlowControl::Keep, FlowControl::Discard ); + assert_eq!( + FlowControl::StopFuzzing & FlowControl::Keep, + FlowControl::StopFuzzing + ); // flowcontrol & flowcontrol::discard assert_eq!( @@ -447,6 +545,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 +558,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 +580,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 +594,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 +609,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 +618,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 +635,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 +649,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 +663,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 +678,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 +692,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 +706,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 +715,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 +735,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 +748,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 +759,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 +772,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 +794,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 +808,9 @@ mod tests { FlowControl::Discard | Action::AddToCorpus("stuff", FlowControl::Discard), FlowControl::Discard ); + assert_eq!( + FlowControl::StopFuzzing | Action::AddToCorpus("stuff", FlowControl::Discard), + FlowControl::StopFuzzing + ); } }