diff --git a/libafl/src/corpus/cached.rs b/libafl/src/corpus/cached.rs index 6385f7687a..ca32966d9a 100644 --- a/libafl/src/corpus/cached.rs +++ b/libafl/src/corpus/cached.rs @@ -37,22 +37,75 @@ where type Input = I; } +impl CachedOnDiskCorpus +where + I: Input, +{ + fn cache_testcase<'a>( + &'a self, + testcase: &'a RefCell>, + idx: CorpusId, + is_disabled: bool, + ) -> Result<(), Error> { + if testcase.borrow().input().is_none() { + self.load_input_into(&mut testcase.borrow_mut())?; + let mut borrowed_num = 0; + while self.cached_indexes.borrow().len() >= self.cache_max_len { + let removed = self.cached_indexes.borrow_mut().pop_front().unwrap(); + if let Ok(mut borrowed) = if is_disabled { + self.inner.get_from_all(removed) + } else { + self.inner.get(removed) + }? + .try_borrow_mut() + { + *borrowed.input_mut() = None; + } else { + self.cached_indexes.borrow_mut().push_back(removed); + borrowed_num += 1; + if self.cache_max_len == borrowed_num { + break; + } + } + } + self.cached_indexes.borrow_mut().push_back(idx); + } + Ok(()) + } +} impl Corpus for CachedOnDiskCorpus where I: Input, { - /// Returns the number of elements + /// Returns the number of all enabled entries #[inline] fn count(&self) -> usize { self.inner.count() } - /// Add an entry to the corpus and return its index + /// Returns the number of all disabled entries + fn count_disabled(&self) -> usize { + self.inner.count_disabled() + } + + /// Returns the number of elements including disabled entries + #[inline] + fn count_all(&self) -> usize { + self.inner.count_all() + } + + /// Add an enabled testcase to the corpus and return its index #[inline] fn add(&mut self, testcase: Testcase) -> Result { self.inner.add(testcase) } + /// Add a disabled testcase to the corpus and return its index + #[inline] + fn add_disabled(&mut self, testcase: Testcase) -> Result { + self.inner.add_disabled(testcase) + } + /// Replaces the testcase at the given idx #[inline] fn replace(&mut self, idx: CorpusId, testcase: Testcase) -> Result, Error> { @@ -68,27 +121,18 @@ where Ok(testcase) } - /// Get by id + /// Get by id; considers only enabled testcases #[inline] fn get(&self, idx: CorpusId) -> Result<&RefCell>, Error> { let testcase = { self.inner.get(idx)? }; - if testcase.borrow().input().is_none() { - self.load_input_into(&mut testcase.borrow_mut())?; - let mut borrowed_num = 0; - while self.cached_indexes.borrow().len() >= self.cache_max_len { - let removed = self.cached_indexes.borrow_mut().pop_front().unwrap(); - if let Ok(mut borrowed) = self.inner.get(removed)?.try_borrow_mut() { - *borrowed.input_mut() = None; - } else { - self.cached_indexes.borrow_mut().push_back(removed); - borrowed_num += 1; - if self.cache_max_len == borrowed_num { - break; - } - } - } - self.cached_indexes.borrow_mut().push_back(idx); - } + self.cache_testcase(testcase, idx, false)?; + Ok(testcase) + } + /// Get by id; considers both enabled and disabled testcases + #[inline] + fn get_from_all(&self, idx: CorpusId) -> Result<&RefCell>, Error> { + let testcase = { self.inner.get_from_all(idx)? }; + self.cache_testcase(testcase, idx, true)?; Ok(testcase) } @@ -124,10 +168,16 @@ where self.inner.last() } + /// Get the nth corpus id; considers only enabled testcases #[inline] fn nth(&self, nth: usize) -> CorpusId { self.inner.nth(nth) } + /// Get the nth corpus id; considers both enabled and disabled testcases + #[inline] + fn nth_from_all(&self, nth: usize) -> CorpusId { + self.inner.nth_from_all(nth) + } #[inline] fn load_input_into(&self, testcase: &mut Testcase) -> Result<(), Error> { diff --git a/libafl/src/corpus/inmemory.rs b/libafl/src/corpus/inmemory.rs index 7bcae71f95..f0ce03cfc2 100644 --- a/libafl/src/corpus/inmemory.rs +++ b/libafl/src/corpus/inmemory.rs @@ -28,28 +28,21 @@ where pub next: Option, } -#[cfg(not(feature = "corpus_btreemap"))] -/// The map type in which testcases are stored (enable the feature `corpus_btreemap` to use a `BTreeMap` instead of `HashMap`) -pub type TestcaseStorageMap = hashbrown::HashMap>; - -#[cfg(feature = "corpus_btreemap")] /// The map type in which testcases are stored (disable the feature `corpus_btreemap` to use a `HashMap` instead of `BTreeMap`) -pub type TestcaseStorageMap = - alloc::collections::btree_map::BTreeMap>>; - -/// Storage map for the testcases (used in `Corpus` implementations) with an incremental index #[derive(Default, Serialize, Deserialize, Clone, Debug)] #[serde(bound = "I: serde::de::DeserializeOwned")] -pub struct TestcaseStorage +pub struct TestcaseStorageMap where I: Input, { - /// The map in which testcases are stored - pub map: TestcaseStorageMap, + #[cfg(not(feature = "corpus_btreemap"))] + /// A map of `CorpusId` to `TestcaseStorageItem` + pub map: hashbrown::HashMap>, + #[cfg(feature = "corpus_btreemap")] + /// A map of `CorpusId` to `Testcase`. + pub map: alloc::collections::btree_map::BTreeMap>>, /// The keys in order (use `Vec::binary_search`) pub keys: Vec, - /// The progressive idx - progressive_idx: usize, /// First inserted idx #[cfg(not(feature = "corpus_btreemap"))] first_idx: Option, @@ -58,14 +51,7 @@ where last_idx: Option, } -impl UsesInput for TestcaseStorage -where - I: Input, -{ - type Input = I; -} - -impl TestcaseStorage +impl TestcaseStorageMap where I: Input, { @@ -83,43 +69,6 @@ where } } - /// Insert a testcase assigning a `CorpusId` to it - #[cfg(not(feature = "corpus_btreemap"))] - pub fn insert(&mut self, testcase: RefCell>) -> CorpusId { - let idx = CorpusId::from(self.progressive_idx); - self.progressive_idx += 1; - let prev = if let Some(last_idx) = self.last_idx { - self.map.get_mut(&last_idx).unwrap().next = Some(idx); - Some(last_idx) - } else { - None - }; - if self.first_idx.is_none() { - self.first_idx = Some(idx); - } - self.last_idx = Some(idx); - self.insert_key(idx); - self.map.insert( - idx, - TestcaseStorageItem { - testcase, - prev, - next: None, - }, - ); - idx - } - - /// Insert a testcase assigning a `CorpusId` to it - #[cfg(feature = "corpus_btreemap")] - pub fn insert(&mut self, testcase: RefCell>) -> CorpusId { - let idx = CorpusId::from(self.progressive_idx); - self.progressive_idx += 1; - self.insert_key(idx); - self.map.insert(idx, testcase); - idx - } - /// Replace a testcase given a `CorpusId` #[cfg(not(feature = "corpus_btreemap"))] pub fn replace(&mut self, idx: CorpusId, testcase: Testcase) -> Option> { @@ -270,13 +219,13 @@ where self.map.iter().next_back().map(|x| *x.0) } - /// Create new `TestcaseStorage` - #[must_use] - pub fn new() -> Self { + fn new() -> Self { Self { - map: TestcaseStorageMap::default(), - keys: vec![], - progressive_idx: 0, + #[cfg(not(feature = "corpus_btreemap"))] + map: hashbrown::HashMap::default(), + #[cfg(feature = "corpus_btreemap")] + map: alloc::collections::BTreeMap::default(), + keys: Vec::default(), #[cfg(not(feature = "corpus_btreemap"))] first_idx: None, #[cfg(not(feature = "corpus_btreemap"))] @@ -284,6 +233,98 @@ where } } } +/// Storage map for the testcases (used in `Corpus` implementations) with an incremental index +#[derive(Default, Serialize, Deserialize, Clone, Debug)] +#[serde(bound = "I: serde::de::DeserializeOwned")] +pub struct TestcaseStorage +where + I: Input, +{ + /// The map in which enabled testcases are stored + pub enabled: TestcaseStorageMap, + /// The map in which disabled testcases are stored + pub disabled: TestcaseStorageMap, + /// The progressive idx for both maps + progressive_idx: usize, +} + +impl UsesInput for TestcaseStorage +where + I: Input, +{ + type Input = I; +} + +impl TestcaseStorage +where + I: Input, +{ + /// Insert a testcase assigning a `CorpusId` to it + pub fn insert(&mut self, testcase: RefCell>) -> CorpusId { + self._insert(testcase, false) + } + + /// Insert a testcase assigning a `CorpusId` to it + pub fn insert_disabled(&mut self, testcase: RefCell>) -> CorpusId { + self._insert(testcase, true) + } + /// Insert a testcase assigning a `CorpusId` to it + #[cfg(not(feature = "corpus_btreemap"))] + fn _insert(&mut self, testcase: RefCell>, is_disabled: bool) -> CorpusId { + let idx = CorpusId::from(self.progressive_idx); + self.progressive_idx += 1; + let corpus = if is_disabled { + &mut self.disabled + } else { + &mut self.enabled + }; + let prev = if let Some(last_idx) = corpus.last_idx { + corpus.map.get_mut(&last_idx).unwrap().next = Some(idx); + Some(last_idx) + } else { + None + }; + if corpus.first_idx.is_none() { + corpus.first_idx = Some(idx); + } + corpus.last_idx = Some(idx); + corpus.insert_key(idx); + corpus.map.insert( + idx, + TestcaseStorageItem { + testcase, + prev, + next: None, + }, + ); + idx + } + + /// Insert a testcase assigning a `CorpusId` to it + #[cfg(feature = "corpus_btreemap")] + fn _insert(&mut self, testcase: RefCell>, is_disabled: bool) -> CorpusId { + let idx = CorpusId::from(self.progressive_idx); + self.progressive_idx += 1; + let corpus = if is_disabled { + &mut self.disabled + } else { + &mut self.enabled + }; + corpus.insert_key(idx); + corpus.map.insert(idx, testcase); + idx + } + + /// Create new `TestcaseStorage` + #[must_use] + pub fn new() -> Self { + Self { + enabled: TestcaseStorageMap::new(), + disabled: TestcaseStorageMap::new(), + progressive_idx: 0, + } + } +} /// A corpus handling all in memory. #[derive(Default, Serialize, Deserialize, Clone, Debug)] @@ -307,22 +348,44 @@ impl Corpus for InMemoryCorpus where I: Input, { - /// Returns the number of elements + /// Returns the number of all enabled entries #[inline] fn count(&self) -> usize { - self.storage.map.len() + self.storage.enabled.map.len() } - /// Add an entry to the corpus and return its index + /// Returns the number of all disabled entries + fn count_disabled(&self) -> usize { + self.storage.disabled.map.len() + } + + /// Returns the number of elements including disabled entries + #[inline] + fn count_all(&self) -> usize { + self.storage + .enabled + .map + .len() + .saturating_add(self.storage.disabled.map.len()) + } + + /// Add an enabled testcase to the corpus and return its index #[inline] fn add(&mut self, testcase: Testcase) -> Result { Ok(self.storage.insert(RefCell::new(testcase))) } + /// Add a disabled testcase to the corpus and return its index + #[inline] + fn add_disabled(&mut self, testcase: Testcase) -> Result { + Ok(self.storage.insert_disabled(RefCell::new(testcase))) + } + /// Replaces the testcase at the given idx #[inline] fn replace(&mut self, idx: CorpusId, testcase: Testcase) -> Result, Error> { self.storage + .enabled .replace(idx, testcase) .ok_or_else(|| Error::key_not_found(format!("Index {idx} not found"))) } @@ -331,18 +394,29 @@ where #[inline] fn remove(&mut self, idx: CorpusId) -> Result, Error> { self.storage + .enabled .remove(idx) .map(|x| x.take()) .ok_or_else(|| Error::key_not_found(format!("Index {idx} not found"))) } - /// Get by id + /// Get by id; considers only enabled testcases #[inline] fn get(&self, idx: CorpusId) -> Result<&RefCell>, Error> { self.storage + .enabled .get(idx) .ok_or_else(|| Error::key_not_found(format!("Index {idx} not found"))) } + /// Get by id; considers both enabled and disabled testcases + #[inline] + fn get_from_all(&self, idx: CorpusId) -> Result<&RefCell>, Error> { + let mut testcase = self.storage.enabled.get(idx); + if testcase.is_none() { + testcase = self.storage.disabled.get(idx); + } + testcase.ok_or_else(|| Error::key_not_found(format!("Index {idx} not found"))) + } /// Current testcase scheduled #[inline] @@ -358,27 +432,38 @@ where #[inline] fn next(&self, idx: CorpusId) -> Option { - self.storage.next(idx) + self.storage.enabled.next(idx) } #[inline] fn prev(&self, idx: CorpusId) -> Option { - self.storage.prev(idx) + self.storage.enabled.prev(idx) } #[inline] fn first(&self) -> Option { - self.storage.first() + self.storage.enabled.first() } #[inline] fn last(&self) -> Option { - self.storage.last() + self.storage.enabled.last() } + /// Get the nth corpus id; considers only enabled testcases #[inline] fn nth(&self, nth: usize) -> CorpusId { - self.storage.keys[nth] + self.storage.enabled.keys[nth] + } + + /// Get the nth corpus id; considers both enabled and disabled testcases + #[inline] + fn nth_from_all(&self, nth: usize) -> CorpusId { + let enabled_count = self.count(); + if nth >= enabled_count { + return self.storage.disabled.keys[nth.saturating_sub(enabled_count)]; + } + self.storage.enabled.keys[nth] } #[inline] diff --git a/libafl/src/corpus/inmemory_ondisk.rs b/libafl/src/corpus/inmemory_ondisk.rs index 4209c94b62..ccd3c49cc4 100644 --- a/libafl/src/corpus/inmemory_ondisk.rs +++ b/libafl/src/corpus/inmemory_ondisk.rs @@ -64,13 +64,24 @@ impl Corpus for InMemoryOnDiskCorpus where I: Input, { - /// Returns the number of elements + /// Returns the number of all enabled entries #[inline] fn count(&self) -> usize { self.inner.count() } - /// Add an entry to the corpus and return its index + /// Returns the number of all disabled entries + fn count_disabled(&self) -> usize { + self.inner.count_disabled() + } + + /// Returns the number of elements including disabled entries + #[inline] + fn count_all(&self) -> usize { + self.inner.count_all() + } + + /// Add an enabled testcase to the corpus and return its index #[inline] fn add(&mut self, testcase: Testcase) -> Result { let idx = self.inner.add(testcase)?; @@ -80,6 +91,16 @@ where Ok(idx) } + /// Add a disabled testcase to the corpus and return its index + #[inline] + fn add_disabled(&mut self, testcase: Testcase) -> Result { + let idx = self.inner.add_disabled(testcase)?; + let testcase = &mut self.get_from_all(idx).unwrap().borrow_mut(); + self.save_testcase(testcase, idx)?; + *testcase.input_mut() = None; + Ok(idx) + } + /// Replaces the testcase at the given idx #[inline] fn replace(&mut self, idx: CorpusId, testcase: Testcase) -> Result, Error> { @@ -99,12 +120,18 @@ where Ok(entry) } - /// Get by id + /// Get by id; considers only enabled testcases #[inline] fn get(&self, idx: CorpusId) -> Result<&RefCell>, Error> { self.inner.get(idx) } + /// Get by id; considers both enabled and disabled testcases + #[inline] + fn get_from_all(&self, idx: CorpusId) -> Result<&RefCell>, Error> { + self.inner.get_from_all(idx) + } + /// Current testcase scheduled #[inline] fn current(&self) -> &Option { @@ -137,10 +164,16 @@ where self.inner.last() } + /// Get the nth corpus id; considers only enabled testcases #[inline] fn nth(&self, nth: usize) -> CorpusId { self.inner.nth(nth) } + /// Get the nth corpus id; considers both enabled and disabled testcases + #[inline] + fn nth_from_all(&self, nth: usize) -> CorpusId { + self.inner.nth_from_all(nth) + } fn load_input_into(&self, testcase: &mut Testcase) -> Result<(), Error> { if testcase.input_mut().is_none() { diff --git a/libafl/src/corpus/mod.rs b/libafl/src/corpus/mod.rs index 8c677b6a5d..b11518997e 100644 --- a/libafl/src/corpus/mod.rs +++ b/libafl/src/corpus/mod.rs @@ -63,7 +63,7 @@ impl From for usize { } } -/// Utility macro to call `Corpus::random_id` +/// Utility macro to call `Corpus::random_id`; fetches only enabled testcases #[macro_export] macro_rules! random_corpus_id { ($corpus:expr, $rand:expr) => {{ @@ -73,19 +73,39 @@ macro_rules! random_corpus_id { }}; } +/// Utility macro to call `Corpus::random_id`; fetches both enabled and disabled testcases +/// Note: use `Corpus::get_from_all` as disabled entries are inaccessible from `Corpus::get` +#[macro_export] +macro_rules! random_corpus_id_with_disabled { + ($corpus:expr, $rand:expr) => {{ + let cnt = $corpus.count_all() as u64; + let nth = $rand.below(cnt) as usize; + $corpus.nth_from_all(nth) + }}; +} + /// Corpus with all current [`Testcase`]s, or solutions pub trait Corpus: UsesInput + Serialize + for<'de> Deserialize<'de> { - /// Returns the number of elements + /// Returns the number of all enabled entries fn count(&self) -> usize; + /// Returns the number of all disabled entries + fn count_disabled(&self) -> usize; + + /// Returns the number of elements including disabled entries + fn count_all(&self) -> usize; + /// Returns true, if no elements are in this corpus yet fn is_empty(&self) -> bool { self.count() == 0 } - /// Add an entry to the corpus and return its index + /// Add an enabled testcase to the corpus and return its index fn add(&mut self, testcase: Testcase) -> Result; + /// Add a disabled testcase to the corpus and return its index + fn add_disabled(&mut self, testcase: Testcase) -> Result; + /// Replaces the [`Testcase`] at the given idx, returning the existing. fn replace( &mut self, @@ -96,9 +116,12 @@ pub trait Corpus: UsesInput + Serialize + for<'de> Deserialize<'de> { /// Removes an entry from the corpus, returning it if it was present. fn remove(&mut self, id: CorpusId) -> Result, Error>; - /// Get by id + /// Get by id; considers only enabled testcases fn get(&self, id: CorpusId) -> Result<&RefCell>, Error>; + /// Get by id; considers both enabled and disabled testcases + fn get_from_all(&self, id: CorpusId) -> Result<&RefCell>, Error>; + /// Current testcase scheduled fn current(&self) -> &Option; @@ -126,13 +149,16 @@ pub trait Corpus: UsesInput + Serialize + for<'de> Deserialize<'de> { } } - /// Get the nth corpus id + /// Get the nth corpus id; considers only enabled testcases fn nth(&self, nth: usize) -> CorpusId { self.ids() .nth(nth) .expect("Failed to get the {nth} CorpusId") } + /// Get the nth corpus id; considers both enabled and disabled testcases + fn nth_from_all(&self, nth: usize) -> CorpusId; + /// Method to load the input for this [`Testcase`] from persistent storage, /// if necessary, and if was not already loaded (`== Some(input)`). /// After this call, `testcase.input()` must always return `Some(input)`. diff --git a/libafl/src/corpus/nop.rs b/libafl/src/corpus/nop.rs index 091e2c29cb..ea6211a23d 100644 --- a/libafl/src/corpus/nop.rs +++ b/libafl/src/corpus/nop.rs @@ -28,18 +28,35 @@ impl Corpus for NopCorpus where I: Input, { - /// Returns the number of elements + /// Returns the number of all enabled entries #[inline] fn count(&self) -> usize { 0 } - /// Add an entry to the corpus and return its index + /// Returns the number of all disabled entries + fn count_disabled(&self) -> usize { + 0 + } + + /// Returns the number of all entries + #[inline] + fn count_all(&self) -> usize { + 0 + } + + /// Add an enabled testcase to the corpus and return its index #[inline] fn add(&mut self, _testcase: Testcase) -> Result { Err(Error::unsupported("Unsupported by NopCorpus")) } + /// Add a disabled testcase to the corpus and return its index + #[inline] + fn add_disabled(&mut self, _testcase: Testcase) -> Result { + Err(Error::unsupported("Unsupported by NopCorpus")) + } + /// Replaces the testcase at the given idx #[inline] fn replace(&mut self, _idx: CorpusId, _testcase: Testcase) -> Result, Error> { @@ -52,12 +69,18 @@ where Err(Error::unsupported("Unsupported by NopCorpus")) } - /// Get by id + /// Get by id; considers only enabled testcases #[inline] fn get(&self, _idx: CorpusId) -> Result<&RefCell>, Error> { Err(Error::unsupported("Unsupported by NopCorpus")) } + /// Get by id; considers both enabled and disabled testcases + #[inline] + fn get_from_all(&self, _idx: CorpusId) -> Result<&RefCell>, Error> { + Err(Error::unsupported("Unsupported by NopCorpus")) + } + /// Current testcase scheduled #[inline] fn current(&self) -> &Option { @@ -90,11 +113,18 @@ where None } + /// Get the nth corpus id; considers only enabled testcases #[inline] fn nth(&self, _nth: usize) -> CorpusId { CorpusId::from(0_usize) } + /// Get the nth corpus id; considers both enabled and disabled testcases + #[inline] + fn nth_from_all(&self, _nth: usize) -> CorpusId { + CorpusId::from(0_usize) + } + #[inline] fn load_input_into(&self, _testcase: &mut Testcase) -> Result<(), Error> { Err(Error::unsupported("Unsupported by NopCorpus")) diff --git a/libafl/src/corpus/ondisk.rs b/libafl/src/corpus/ondisk.rs index fc0a901e45..926b7269c7 100644 --- a/libafl/src/corpus/ondisk.rs +++ b/libafl/src/corpus/ondisk.rs @@ -71,18 +71,35 @@ impl Corpus for OnDiskCorpus where I: Input, { - /// Returns the number of elements + /// Returns the number of all enabled entries #[inline] fn count(&self) -> usize { self.inner.count() } - /// Add an entry to the corpus and return its index + /// Returns the number of all disabled entries + fn count_disabled(&self) -> usize { + self.inner.count_disabled() + } + + /// Returns the number of all entries + #[inline] + fn count_all(&self) -> usize { + self.inner.count_all() + } + + /// Add an enabled testcase to the corpus and return its index #[inline] fn add(&mut self, testcase: Testcase) -> Result { self.inner.add(testcase) } + /// Add a disabled testcase to the corpus and return its index + #[inline] + fn add_disabled(&mut self, testcase: Testcase) -> Result { + self.inner.add_disabled(testcase) + } + /// Replaces the testcase at the given idx #[inline] fn replace(&mut self, idx: CorpusId, testcase: Testcase) -> Result, Error> { @@ -95,12 +112,18 @@ where self.inner.remove(idx) } - /// Get by id + /// Get by id; will check the disabled corpus if not available in the enabled #[inline] fn get(&self, idx: CorpusId) -> Result<&RefCell>, Error> { self.inner.get(idx) } + /// Get by id; considers both enabled and disabled testcases + #[inline] + fn get_from_all(&self, idx: CorpusId) -> Result<&RefCell>, Error> { + self.inner.get_from_all(idx) + } + /// Current testcase scheduled #[inline] fn current(&self) -> &Option { @@ -133,10 +156,16 @@ where self.inner.last() } + /// Get the nth corpus id; considers only enabled testcases #[inline] fn nth(&self, nth: usize) -> CorpusId { self.inner.nth(nth) } + /// Get the nth corpus id; considers both enabled and disabled testcases + #[inline] + fn nth_from_all(&self, nth: usize) -> CorpusId { + self.inner.nth_from_all(nth) + } #[inline] fn load_input_into(&self, testcase: &mut Testcase) -> Result<(), Error> { diff --git a/libafl/src/corpus/testcase.rs b/libafl/src/corpus/testcase.rs index 096f91c38e..cb5422ea9f 100644 --- a/libafl/src/corpus/testcase.rs +++ b/libafl/src/corpus/testcase.rs @@ -63,6 +63,8 @@ where scheduled_count: usize, /// Parent [`CorpusId`], if known parent_id: Option, + /// If the testcase is "disabled" + disabled: bool, } impl HasMetadata for Testcase @@ -195,6 +197,18 @@ where self.scheduled_count = scheduled_count; } + /// Get `disabled` + #[inline] + pub fn disabled(&mut self) -> bool { + self.disabled + } + + /// Set the testcase as disabled + #[inline] + pub fn set_disabled(&mut self, disabled: bool) { + self.disabled = disabled; + } + /// Create a new Testcase instance given an input #[inline] pub fn new(mut input: I) -> Self { @@ -212,6 +226,7 @@ where executions: 0, scheduled_count: 0, parent_id: None, + disabled: false, } } @@ -232,6 +247,7 @@ where executions: 0, scheduled_count: 0, parent_id: Some(parent_id), + disabled: false, } } @@ -252,6 +268,7 @@ where executions: 0, scheduled_count: 0, parent_id: None, + disabled: false, } } @@ -272,6 +289,7 @@ where executions, scheduled_count: 0, parent_id: None, + disabled: false, } } @@ -312,6 +330,7 @@ where file_path: None, #[cfg(feature = "std")] metadata_path: None, + disabled: false, } } } diff --git a/libafl/src/events/centralized.rs b/libafl/src/events/centralized.rs index eb4b55b297..42feafcee1 100644 --- a/libafl/src/events/centralized.rs +++ b/libafl/src/events/centralized.rs @@ -683,7 +683,7 @@ where { state.scalability_monitor_mut().testcase_with_observers += 1; } - fuzzer.process_execution( + fuzzer.execute_and_process( state, self, input.clone(), diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index 73d976af58..fa445dd3c5 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -681,7 +681,7 @@ where { state.scalability_monitor_mut().testcase_with_observers += 1; } - fuzzer.process_execution(state, self, input, &observers, &exit_kind, false)? + fuzzer.execute_and_process(state, self, input, &observers, &exit_kind, false)? } else { #[cfg(feature = "scalability_introspection")] { diff --git a/libafl/src/events/tcp.rs b/libafl/src/events/tcp.rs index 3e699597bf..ed98184673 100644 --- a/libafl/src/events/tcp.rs +++ b/libafl/src/events/tcp.rs @@ -651,7 +651,7 @@ where { state.scalability_monitor_mut().testcase_with_observers += 1; } - fuzzer.process_execution(state, self, input, &observers, &exit_kind, false)? + fuzzer.execute_and_process(state, self, input, &observers, &exit_kind, false)? } else { #[cfg(feature = "scalability_introspection")] { diff --git a/libafl/src/fuzzer/mod.rs b/libafl/src/fuzzer/mod.rs index d91f3c4a3c..8eac544ea6 100644 --- a/libafl/src/fuzzer/mod.rs +++ b/libafl/src/fuzzer/mod.rs @@ -68,7 +68,34 @@ pub trait HasObjective: UsesState { /// Evaluates if an input is interesting using the feedback pub trait ExecutionProcessor: UsesState { /// Evaluate if a set of observation channels has an interesting state + fn execute_no_process( + &mut self, + state: &mut Self::State, + manager: &mut EM, + input: &::Input, + observers: &OT, + exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer; + + /// Process `ExecuteInputResult`. Add to corpus, solution or ignore + #[allow(clippy::too_many_arguments)] fn process_execution( + &mut self, + state: &mut Self::State, + manager: &mut EM, + input: ::Input, + exec_res: &ExecuteInputResult, + observers: &OT, + exit_kind: &ExitKind, + send_events: bool, + ) -> Result, Error> + where + EM: EventFirer; + + /// Evaluate if a set of observation channels has an interesting state + fn execute_and_process( &mut self, state: &mut Self::State, manager: &mut EM, @@ -140,6 +167,17 @@ where manager: &mut EM, input: ::Input, ) -> Result; + + /// Adds the input to the corpus as disabled a input. + /// Used during initial corpus loading. + /// Disabled testcases are only used for splicing + /// Returns the `index` of the new testcase in the corpus. + /// Usually, you want to use [`Evaluator::evaluate_input`], unless you know what you are doing. + fn add_disabled_input( + &mut self, + state: &mut Self::State, + input: ::Input, + ) -> Result; } /// The main fuzzer trait. @@ -324,8 +362,50 @@ where OT: ObserversTuple + Serialize + DeserializeOwned, CS::State: HasCorpus + HasSolutions + HasExecutions + HasCorpus + HasImported, { - /// Evaluate if a set of observation channels has an interesting state - fn process_execution( + fn execute_no_process( + &mut self, + state: &mut Self::State, + manager: &mut EM, + input: &::Input, + observers: &OT, + exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + { + let mut res = ExecuteInputResult::None; + + #[cfg(not(feature = "introspection"))] + let is_solution = self + .objective_mut() + .is_interesting(state, manager, input, observers, exit_kind)?; + + #[cfg(feature = "introspection")] + let is_solution = self + .objective_mut() + .is_interesting_introspection(state, manager, input, observers, exit_kind)?; + + if is_solution { + res = ExecuteInputResult::Solution; + } else { + #[cfg(not(feature = "introspection"))] + let corpus_worthy = self + .feedback_mut() + .is_interesting(state, manager, input, observers, exit_kind)?; + + #[cfg(feature = "introspection")] + let corpus_worthy = self + .feedback_mut() + .is_interesting_introspection(state, manager, input, observers, exit_kind)?; + + if corpus_worthy { + res = ExecuteInputResult::Corpus; + } + } + Ok(res) + } + + fn execute_and_process( &mut self, state: &mut Self::State, manager: &mut EM, @@ -337,41 +417,38 @@ where where EM: EventFirer, { - let mut res = ExecuteInputResult::None; + let exec_res = self.execute_no_process(state, manager, &input, observers, exit_kind)?; + let corpus_idx = self.process_execution( + state, + manager, + input, + &exec_res, + observers, + exit_kind, + send_events, + )?; + Ok((exec_res, corpus_idx)) + } - #[cfg(not(feature = "introspection"))] - let is_solution = self - .objective_mut() - .is_interesting(state, manager, &input, observers, exit_kind)?; - - #[cfg(feature = "introspection")] - let is_solution = self - .objective_mut() - .is_interesting_introspection(state, manager, &input, observers, exit_kind)?; - - if is_solution { - res = ExecuteInputResult::Solution; - } else { - #[cfg(not(feature = "introspection"))] - let is_corpus = self - .feedback_mut() - .is_interesting(state, manager, &input, observers, exit_kind)?; - - #[cfg(feature = "introspection")] - let is_corpus = self - .feedback_mut() - .is_interesting_introspection(state, manager, &input, observers, exit_kind)?; - - if is_corpus { - res = ExecuteInputResult::Corpus; - } - } - - match res { + /// Evaluate if a set of observation channels has an interesting state + fn process_execution( + &mut self, + state: &mut Self::State, + manager: &mut EM, + input: ::Input, + exec_res: &ExecuteInputResult, + observers: &OT, + exit_kind: &ExitKind, + send_events: bool, + ) -> Result, Error> + where + EM: EventFirer, + { + match exec_res { ExecuteInputResult::None => { self.feedback_mut().discard_metadata(state, &input)?; self.objective_mut().discard_metadata(state, &input)?; - Ok((res, None)) + Ok(None) } ExecuteInputResult::Corpus => { // Not a solution @@ -408,7 +485,7 @@ where // This testcase is from the other fuzzers. *state.imported_mut() += 1; } - Ok((res, Some(idx))) + Ok(Some(idx)) } ExecuteInputResult::Solution => { // Not interesting @@ -433,7 +510,7 @@ where )?; } - Ok((res, None)) + Ok(None) } } } @@ -466,7 +543,7 @@ where self.scheduler.on_evaluation(state, &input, observers)?; - self.process_execution(state, manager, input, observers, &exit_kind, send_events) + self.execute_and_process(state, manager, input, observers, &exit_kind, send_events) } } @@ -492,7 +569,17 @@ where ) -> Result<(ExecuteInputResult, Option), Error> { self.evaluate_input_with_observers(state, executor, manager, input, send_events) } - + fn add_disabled_input( + &mut self, + state: &mut Self::State, + input: ::Input, + ) -> Result { + let mut testcase = Testcase::with_executions(input.clone(), *state.executions()); + testcase.set_disabled(true); + // Add the disabled input to the main corpus + let idx = state.corpus_mut().add_disabled(testcase)?; + Ok(idx) + } /// Adds an input, even if it's not considered `interesting` by any of the executors fn add_input( &mut self, @@ -540,12 +627,12 @@ where // several is_interesting implementations collect some data about the run, later used in // append_metadata; we *must* invoke is_interesting here to collect it #[cfg(not(feature = "introspection"))] - let _is_corpus = self + let _corpus_worthy = self .feedback_mut() .is_interesting(state, manager, &input, observers, &exit_kind)?; #[cfg(feature = "introspection")] - let _is_corpus = self + let _corpus_worthy = self .feedback_mut() .is_interesting_introspection(state, manager, &input, observers, &exit_kind)?; diff --git a/libafl/src/mutators/encoded_mutations.rs b/libafl/src/mutators/encoded_mutations.rs index 9a29945bff..a7dfac9716 100644 --- a/libafl/src/mutators/encoded_mutations.rs +++ b/libafl/src/mutators/encoded_mutations.rs @@ -15,7 +15,7 @@ use crate::{ mutations::{buffer_copy, buffer_self_copy, ARITH_MAX}, MutationResult, Mutator, Named, }, - random_corpus_id, + random_corpus_id_with_disabled, state::{HasCorpus, HasMaxSize, HasRand}, Error, }; @@ -286,7 +286,7 @@ where let size = input.codes().len(); // We don't want to use the testcase we're already using for splicing - let idx = random_corpus_id!(state.corpus(), state.rand_mut()); + let idx = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut()); if let Some(cur) = state.corpus().current() { if idx == *cur { return Ok(MutationResult::Skipped); @@ -294,7 +294,7 @@ where } let other_size = { - let mut other_testcase = state.corpus().get(idx)?.borrow_mut(); + let mut other_testcase = state.corpus().get_from_all(idx)?.borrow_mut(); other_testcase.load_input(state.corpus())?.codes().len() }; @@ -315,7 +315,7 @@ where } } - let other_testcase = state.corpus().get(idx)?.borrow_mut(); + let other_testcase = state.corpus().get_from_all(idx)?.borrow_mut(); // no need to `load_input` again - we did that above already. let other = other_testcase.input().as_ref().unwrap(); @@ -358,7 +358,7 @@ where } // We don't want to use the testcase we're already using for splicing - let idx = random_corpus_id!(state.corpus(), state.rand_mut()); + let idx = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut()); if let Some(cur) = state.corpus().current() { if idx == *cur { return Ok(MutationResult::Skipped); @@ -367,7 +367,7 @@ where let other_size = { // new scope to make the borrow checker happy - let mut other_testcase = state.corpus().get(idx)?.borrow_mut(); + let mut other_testcase = state.corpus().get_from_all(idx)?.borrow_mut(); other_testcase.load_input(state.corpus())?.codes().len() }; @@ -379,7 +379,7 @@ where let len = state.rand_mut().below(min(other_size - from, size) as u64) as usize; let to = state.rand_mut().below((size - len) as u64) as usize; - let other_testcase = state.corpus().get(idx)?.borrow_mut(); + let other_testcase = state.corpus().get_from_all(idx)?.borrow_mut(); // no need to load the input again, it'll already be present at this point. let other = other_testcase.input().as_ref().unwrap(); diff --git a/libafl/src/mutators/mutations.rs b/libafl/src/mutators/mutations.rs index eeb6b9af7b..0b02e55019 100644 --- a/libafl/src/mutators/mutations.rs +++ b/libafl/src/mutators/mutations.rs @@ -9,7 +9,7 @@ use crate::{ corpus::Corpus, inputs::{HasBytesVec, Input}, mutators::{MutationResult, Mutator}, - random_corpus_id, + random_corpus_id_with_disabled, state::{HasCorpus, HasMaxSize, HasRand}, Error, }; @@ -1053,7 +1053,7 @@ where } // We don't want to use the testcase we're already using for splicing - let idx = random_corpus_id!(state.corpus(), state.rand_mut()); + let idx = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut()); if let Some(cur) = state.corpus().current() { if idx == *cur { @@ -1062,7 +1062,7 @@ where } let other_size = { - let mut other_testcase = state.corpus().get(idx)?.borrow_mut(); + let mut other_testcase = state.corpus().get_from_all(idx)?.borrow_mut(); other_testcase.load_input(state.corpus())?.bytes().len() }; @@ -1073,7 +1073,7 @@ where let range = rand_range(state, other_size, min(other_size, max_size - size)); let target = state.rand_mut().below(size as u64) as usize; - let other_testcase = state.corpus().get(idx)?.borrow_mut(); + let other_testcase = state.corpus().get_from_all(idx)?.borrow_mut(); // No need to load the input again, it'll still be cached. let other = other_testcase.input().as_ref().unwrap(); @@ -1135,7 +1135,7 @@ where } // We don't want to use the testcase we're already using for splicing - let idx = random_corpus_id!(state.corpus(), state.rand_mut()); + let idx = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut()); if let Some(cur) = state.corpus().current() { if idx == *cur { return Ok(MutationResult::Skipped); @@ -1143,7 +1143,7 @@ where } let other_size = { - let mut testcase = state.corpus().get(idx)?.borrow_mut(); + let mut testcase = state.corpus().get_from_all(idx)?.borrow_mut(); testcase.load_input(state.corpus())?.bytes().len() }; @@ -1154,7 +1154,7 @@ where let target = state.rand_mut().below(size as u64) as usize; let range = rand_range(state, other_size, min(other_size, size - target)); - let other_testcase = state.corpus().get(idx)?.borrow_mut(); + let other_testcase = state.corpus().get_from_all(idx)?.borrow_mut(); // No need to load the input again, it'll still be cached. let other = other_testcase.input().as_ref().unwrap(); @@ -1207,7 +1207,7 @@ where #[allow(clippy::cast_sign_loss)] fn mutate(&mut self, state: &mut S, input: &mut S::Input) -> Result { // We don't want to use the testcase we're already using for splicing - let idx = random_corpus_id!(state.corpus(), state.rand_mut()); + let idx = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut()); if let Some(cur) = state.corpus().current() { if idx == *cur { return Ok(MutationResult::Skipped); @@ -1215,7 +1215,7 @@ where } let (first_diff, last_diff) = { - let mut other_testcase = state.corpus().get(idx)?.borrow_mut(); + let mut other_testcase = state.corpus().get_from_all(idx)?.borrow_mut(); let other = other_testcase.load_input(state.corpus())?; let (f, l) = locate_diffs(input.bytes(), other.bytes()); @@ -1229,7 +1229,7 @@ where let split_at = state.rand_mut().between(first_diff, last_diff) as usize; - let other_testcase = state.corpus().get(idx)?.borrow_mut(); + let other_testcase = state.corpus().get_from_all(idx)?.borrow_mut(); // Input will already be loaded. let other = other_testcase.input().as_ref().unwrap(); diff --git a/libafl/src/stages/push/mutational.rs b/libafl/src/stages/push/mutational.rs index e91239fbd0..065aff2072 100644 --- a/libafl/src/stages/push/mutational.rs +++ b/libafl/src/stages/push/mutational.rs @@ -169,7 +169,7 @@ where ) -> Result<(), Error> { // todo: is_interesting, etc. - fuzzer.process_execution(state, event_mgr, last_input, observers, &exit_kind, true)?; + fuzzer.execute_and_process(state, event_mgr, last_input, observers, &exit_kind, true)?; start_timer!(state); self.mutator.post_exec(state, self.current_corpus_idx)?; diff --git a/libafl/src/stages/tmin.rs b/libafl/src/stages/tmin.rs index 9fe63fdb86..fc21c42f35 100644 --- a/libafl/src/stages/tmin.rs +++ b/libafl/src/stages/tmin.rs @@ -126,7 +126,7 @@ where // TODO replace if process_execution adds a return value for solution index let solution_count = state.solutions().count(); let corpus_count = state.corpus().count(); - let (_, corpus_idx) = fuzzer.process_execution( + let (_, corpus_idx) = fuzzer.execute_and_process( state, manager, input.clone(), diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index 1794b526b5..a8e4001d17 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -685,9 +685,10 @@ where if forced { let _: CorpusId = fuzzer.add_input(self, executor, manager, input)?; } else { - let (res, _) = fuzzer.evaluate_input(self, executor, manager, input)?; + let (res, _) = fuzzer.evaluate_input(self, executor, manager, input.clone())?; if res == ExecuteInputResult::None { - log::warn!("File {:?} was not interesting, skipped.", &path); + fuzzer.add_disabled_input(self, input)?; + log::warn!("input {:?} was not interesting, adding as disabled.", &path); } } Ok(()) diff --git a/libafl_libfuzzer/libafl_libfuzzer_runtime/src/corpus.rs b/libafl_libfuzzer/libafl_libfuzzer_runtime/src/corpus.rs index 64f6bace30..8ac02e541b 100644 --- a/libafl_libfuzzer/libafl_libfuzzer_runtime/src/corpus.rs +++ b/libafl_libfuzzer/libafl_libfuzzer_runtime/src/corpus.rs @@ -7,7 +7,10 @@ use std::{ }; use libafl::{ - corpus::{inmemory::TestcaseStorage, Corpus, CorpusId, Testcase}, + corpus::{ + inmemory::{TestcaseStorage, TestcaseStorageMap}, + Corpus, CorpusId, Testcase, + }, inputs::{Input, UsesInput}, }; use libafl_bolts::Error; @@ -51,7 +54,7 @@ where } /// Touch this index and maybe evict an entry if we have touched an input which was unloaded. - fn touch(&self, idx: CorpusId) -> Result<(), Error> { + fn touch(&self, idx: CorpusId, corpus: &TestcaseStorageMap) -> Result<(), Error> { let mut loaded_mapping = self.loaded_mapping.borrow_mut(); let mut loaded_entries = self.loaded_entries.borrow_mut(); match loaded_mapping.entry(idx) { @@ -71,7 +74,7 @@ where } if loaded_entries.len() > self.max_len { let idx = loaded_entries.pop_first().unwrap().1; // cannot panic - let cell = self.mapping.get(idx).ok_or_else(|| { + let cell = corpus.get(idx).ok_or_else(|| { Error::key_not_found(format!("Tried to evict non-existent entry {idx}")) })?; let mut tc = cell.try_borrow_mut()?; @@ -79,27 +82,32 @@ where } Ok(()) } -} - -impl UsesInput for LibfuzzerCorpus -where - I: Input + Serialize + for<'de> Deserialize<'de>, -{ - type Input = I; -} - -impl Corpus for LibfuzzerCorpus -where - I: Input + Serialize + for<'de> Deserialize<'de>, -{ - fn count(&self) -> usize { - self.mapping.map.len() + #[inline] + fn _get<'a>( + &'a self, + id: CorpusId, + corpus: &'a TestcaseStorageMap, + ) -> Result<&RefCell>, Error> { + self.touch(id, corpus)?; + corpus.map.get(&id).map(|item| &item.testcase).ok_or_else(|| Error::illegal_state("Nonexistent corpus entry {id} requested (present in loaded entries, but not the mapping?)")) } - fn add(&mut self, testcase: Testcase) -> Result { - let idx = self.mapping.insert(RefCell::new(testcase)); - let mut testcase = self.mapping.get(idx).unwrap().borrow_mut(); - + fn _add( + &mut self, + testcase: RefCell>, + is_disabled: bool, + ) -> Result { + let idx = if is_disabled { + self.mapping.insert_disabled(testcase) + } else { + self.mapping.insert(testcase) + }; + let corpus = if is_disabled { + &self.mapping.disabled + } else { + &self.mapping.enabled + }; + let mut testcase = corpus.get(idx).unwrap().borrow_mut(); match testcase.file_path() { Some(path) if path.canonicalize()?.starts_with(&self.corpus_dir) => { // if it's already in the correct dir, we retain it @@ -126,10 +134,40 @@ where testcase.file_path_mut().replace(path); } }; - - self.touch(idx)?; + self.touch(idx, corpus)?; Ok(idx) } +} + +impl UsesInput for LibfuzzerCorpus +where + I: Input + Serialize + for<'de> Deserialize<'de>, +{ + type Input = I; +} + +impl Corpus for LibfuzzerCorpus +where + I: Input + Serialize + for<'de> Deserialize<'de>, +{ + #[inline] + fn count(&self) -> usize { + self.mapping.enabled.map.len() + } + #[inline] + fn count_disabled(&self) -> usize { + self.mapping.disabled.map.len() + } + #[inline] + fn count_all(&self) -> usize { + self.count_disabled().saturating_add(self.count_disabled()) + } + fn add(&mut self, testcase: Testcase) -> Result { + self._add(RefCell::new(testcase), false) + } + fn add_disabled(&mut self, testcase: Testcase) -> Result { + self._add(RefCell::new(testcase), true) + } fn replace( &mut self, @@ -144,10 +182,18 @@ where } fn get(&self, id: CorpusId) -> Result<&RefCell>, Error> { - self.touch(id)?; - self.mapping.map.get(&id).map(|item| &item.testcase).ok_or_else(|| Error::illegal_state("Nonexistent corpus entry {id} requested (present in loaded entries, but not the mapping?)")) + self._get(id, &self.mapping.enabled) } + fn get_from_all(&self, id: CorpusId) -> Result<&RefCell>, Error> { + match self._get(id, &self.mapping.enabled) { + Ok(input) => Ok(input), + Err(Error::KeyNotFound(..)) => return self._get(id, &self.mapping.disabled), + Err(e) => { + Err(e) + } + } + } fn current(&self) -> &Option { &self.current } @@ -157,19 +203,29 @@ where } fn next(&self, id: CorpusId) -> Option { - self.mapping.next(id) + self.mapping.enabled.next(id) } fn prev(&self, id: CorpusId) -> Option { - self.mapping.prev(id) + self.mapping.enabled.prev(id) } fn first(&self) -> Option { - self.mapping.first() + self.mapping.enabled.first() } fn last(&self) -> Option { - self.mapping.last() + self.mapping.enabled.last() + } + + /// Get the nth corpus id; considers both enabled and disabled testcases + #[inline] + fn nth_from_all(&self, nth: usize) -> CorpusId { + let enabled_count = self.count(); + if nth >= enabled_count { + return self.mapping.disabled.keys[nth.saturating_sub(enabled_count)]; + } + self.mapping.enabled.keys[nth] } fn load_input_into(&self, testcase: &mut Testcase) -> Result<(), Error> { @@ -239,6 +295,16 @@ where self.count } + // ArtifactCorpus disregards disabled entries + fn count_disabled(&self) -> usize { + 0 + } + + fn count_all(&self) -> usize { + // count_disabled will always return 0 + self.count() + self.count_disabled() + } + fn add(&mut self, testcase: Testcase) -> Result { let idx = self.count; self.count += 1; @@ -262,6 +328,10 @@ where Ok(CorpusId::from(idx)) } + fn add_disabled(&mut self, _testcase: Testcase) -> Result { + unimplemented!("ArtifactCorpus disregards disabled inputs") + } + fn replace( &mut self, _idx: CorpusId, @@ -288,6 +358,16 @@ where maybe_last.ok_or_else(|| Error::illegal_argument("Can only get the last corpus ID.")) } + // This just calls Self::get as ArtifactCorpus disregards disabled entries + fn get_from_all(&self, id: CorpusId) -> Result<&RefCell>, Error> { + self.get(id) + } + + // This just calls Self::nth as ArtifactCorpus disregards disabled entries + fn nth_from_all(&self, nth: usize) -> CorpusId { + self.nth(nth) + } + fn current(&self) -> &Option { unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.") } diff --git a/libafl_targets/src/libfuzzer/mutators.rs b/libafl_targets/src/libfuzzer/mutators.rs index d0ebd2b617..62fbb57db4 100644 --- a/libafl_targets/src/libfuzzer/mutators.rs +++ b/libafl_targets/src/libfuzzer/mutators.rs @@ -12,7 +12,7 @@ use libafl::{ mutators::{ ComposedByMutations, MutationId, MutationResult, Mutator, MutatorsTuple, ScheduledMutator, }, - random_corpus_id, + random_corpus_id_with_disabled, state::{HasCorpus, HasMaxSize, HasRand}, Error, }; @@ -392,14 +392,14 @@ where input: &mut S::Input, ) -> Result { // We don't want to use the testcase we're already using for splicing - let idx = random_corpus_id!(state.corpus(), state.rand_mut()); + let idx = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut()); if let Some(cur) = state.corpus().current() { if idx == *cur { return Ok(MutationResult::Skipped); } } - let mut other_testcase = state.corpus().get(idx)?.borrow_mut(); + let mut other_testcase = state.corpus().get_from_all(idx)?.borrow_mut(); let other = other_testcase.load_input(state.corpus())?; let data2 = Vec::from(other.bytes()); drop(other_testcase);