Add disable/enable to Corpus (#3193)
* Add enable and disable methods for Corpus * Add insert_inner_with_id to fix disable/enable & test Since we need to insert an 'existing' testcase with a certain id, let's use a private inner function for it. It's not the most posh way to keep consistency, but as showed in the test it works 'good enough'. * Implement disable/enable for libafl_libfuzzer/corpus * fix clippy issues and fix cfg[not"corpus_btreemap"] * Move enable/disable from Corpus to a trait * Rename HasCorpusEnablementOperations to EnableDisableCorpus Unless we come up with a better idea. Naming is hard. * fmt the changes
This commit is contained in:
parent
c0e32cdbba
commit
1f91420cd3
@ -9,8 +9,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::{
|
use crate::{
|
||||||
Error,
|
Error,
|
||||||
corpus::{
|
corpus::{
|
||||||
Corpus, CorpusId, HasTestcase, Testcase, inmemory_ondisk::InMemoryOnDiskCorpus,
|
Corpus, CorpusId, EnableDisableCorpus, HasTestcase, Testcase,
|
||||||
ondisk::OnDiskMetadataFormat,
|
inmemory_ondisk::InMemoryOnDiskCorpus, ondisk::OnDiskMetadataFormat,
|
||||||
},
|
},
|
||||||
inputs::Input,
|
inputs::Input,
|
||||||
};
|
};
|
||||||
@ -191,6 +191,23 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<I> EnableDisableCorpus for CachedOnDiskCorpus<I>
|
||||||
|
where
|
||||||
|
I: Input,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn disable(&mut self, id: CorpusId) -> Result<(), Error> {
|
||||||
|
self.cached_indexes.borrow_mut().retain(|e| *e != id);
|
||||||
|
self.inner.disable(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn enable(&mut self, id: CorpusId) -> Result<(), Error> {
|
||||||
|
self.cached_indexes.borrow_mut().retain(|e| *e != id);
|
||||||
|
self.inner.enable(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<I> CachedOnDiskCorpus<I> {
|
impl<I> CachedOnDiskCorpus<I> {
|
||||||
/// Creates the [`CachedOnDiskCorpus`].
|
/// Creates the [`CachedOnDiskCorpus`].
|
||||||
///
|
///
|
||||||
|
@ -5,7 +5,7 @@ use core::cell::{Ref, RefCell, RefMut};
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::HasTestcase;
|
use super::{EnableDisableCorpus, HasTestcase};
|
||||||
use crate::{
|
use crate::{
|
||||||
Error,
|
Error,
|
||||||
corpus::{Corpus, CorpusId, Testcase},
|
corpus::{Corpus, CorpusId, Testcase},
|
||||||
@ -285,6 +285,67 @@ impl<I> TestcaseStorage<I> {
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "corpus_btreemap"))]
|
||||||
|
fn insert_inner_with_id(
|
||||||
|
&mut self,
|
||||||
|
testcase: RefCell<Testcase<I>>,
|
||||||
|
is_disabled: bool,
|
||||||
|
id: CorpusId,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if self.progressive_id < id.into() {
|
||||||
|
return Err(Error::illegal_state(
|
||||||
|
"trying to insert a testcase with an id bigger than the internal Id counter",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let corpus = if is_disabled {
|
||||||
|
&mut self.disabled
|
||||||
|
} else {
|
||||||
|
&mut self.enabled
|
||||||
|
};
|
||||||
|
let prev = if let Some(last_id) = corpus.last_id {
|
||||||
|
corpus.map.get_mut(&last_id).unwrap().next = Some(id);
|
||||||
|
Some(last_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if corpus.first_id.is_none() {
|
||||||
|
corpus.first_id = Some(id);
|
||||||
|
}
|
||||||
|
corpus.last_id = Some(id);
|
||||||
|
corpus.insert_key(id);
|
||||||
|
corpus.map.insert(
|
||||||
|
id,
|
||||||
|
TestcaseStorageItem {
|
||||||
|
testcase,
|
||||||
|
prev,
|
||||||
|
next: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "corpus_btreemap")]
|
||||||
|
fn insert_inner_with_id(
|
||||||
|
&mut self,
|
||||||
|
testcase: RefCell<Testcase<I>>,
|
||||||
|
is_disabled: bool,
|
||||||
|
id: CorpusId,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if self.progressive_id < id.into() {
|
||||||
|
return Err(Error::illegal_state(
|
||||||
|
"trying to insert a testcase with an id bigger than the internal Id counter",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let corpus = if is_disabled {
|
||||||
|
&mut self.disabled
|
||||||
|
} else {
|
||||||
|
&mut self.enabled
|
||||||
|
};
|
||||||
|
corpus.insert_key(id);
|
||||||
|
corpus.map.insert(id, testcase);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Insert a testcase assigning a `CorpusId` to it
|
/// Insert a testcase assigning a `CorpusId` to it
|
||||||
#[cfg(feature = "corpus_btreemap")]
|
#[cfg(feature = "corpus_btreemap")]
|
||||||
fn insert_inner(&mut self, testcase: RefCell<Testcase<I>>, is_disabled: bool) -> CorpusId {
|
fn insert_inner(&mut self, testcase: RefCell<Testcase<I>>, is_disabled: bool) -> CorpusId {
|
||||||
@ -456,6 +517,30 @@ impl<I> Corpus<I> for InMemoryCorpus<I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<I> EnableDisableCorpus for InMemoryCorpus<I> {
|
||||||
|
#[inline]
|
||||||
|
fn disable(&mut self, id: CorpusId) -> Result<(), Error> {
|
||||||
|
if let Some(testcase) = self.storage.enabled.remove(id) {
|
||||||
|
self.storage.insert_inner_with_id(testcase, true, id)
|
||||||
|
} else {
|
||||||
|
Err(Error::key_not_found(format!(
|
||||||
|
"Index {id} not found in enabled testcases"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn enable(&mut self, id: CorpusId) -> Result<(), Error> {
|
||||||
|
if let Some(testcase) = self.storage.disabled.remove(id) {
|
||||||
|
self.storage.insert_inner_with_id(testcase, false, id)
|
||||||
|
} else {
|
||||||
|
Err(Error::key_not_found(format!(
|
||||||
|
"Index {id} not found in disabled testcases"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<I> HasTestcase<I> for InMemoryCorpus<I> {
|
impl<I> HasTestcase<I> for InMemoryCorpus<I> {
|
||||||
fn testcase(&self, id: CorpusId) -> Result<Ref<Testcase<I>>, Error> {
|
fn testcase(&self, id: CorpusId) -> Result<Ref<Testcase<I>>, Error> {
|
||||||
Ok(self.get(id)?.borrow())
|
Ok(self.get(id)?.borrow())
|
||||||
@ -477,3 +562,157 @@ impl<I> InMemoryCorpus<I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[cfg(not(feature = "corpus_btreemap"))]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
Error,
|
||||||
|
corpus::Testcase,
|
||||||
|
inputs::{HasMutatorBytes, bytes::BytesInput},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Helper function to create a corpus with predefined test cases
|
||||||
|
#[cfg(not(feature = "corpus_btreemap"))]
|
||||||
|
fn setup_corpus() -> (InMemoryCorpus<BytesInput>, Vec<CorpusId>) {
|
||||||
|
let mut corpus = InMemoryCorpus::<BytesInput>::new();
|
||||||
|
let mut ids = Vec::new();
|
||||||
|
|
||||||
|
// Add initial test cases with distinct byte patterns ([1,2,3],[2,3,4],[3,4,5])
|
||||||
|
for i in 0..3u8 {
|
||||||
|
let input = BytesInput::new(vec![i + 1, i + 2, i + 3]);
|
||||||
|
let tc_id = corpus.add(Testcase::new(input)).unwrap();
|
||||||
|
ids.push(tc_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
(corpus, ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to verify corpus counts
|
||||||
|
#[cfg(not(feature = "corpus_btreemap"))]
|
||||||
|
fn assert_corpus_counts(corpus: &InMemoryCorpus<BytesInput>, enabled: usize, disabled: usize) {
|
||||||
|
let total = enabled + disabled; // if a testcase is not in the enabled map, then it's in the disabled one.
|
||||||
|
assert_eq!(corpus.count(), enabled, "Wrong number of enabled testcases");
|
||||||
|
assert_eq!(
|
||||||
|
corpus.count_disabled(),
|
||||||
|
disabled,
|
||||||
|
"Wrong number of disabled testcases"
|
||||||
|
);
|
||||||
|
assert_eq!(corpus.count_all(), total, "Wrong total number of testcases");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(feature = "corpus_btreemap"))]
|
||||||
|
fn test_corpus_basic_operations() {
|
||||||
|
let (corpus, ids) = setup_corpus();
|
||||||
|
assert_corpus_counts(&corpus, 3, 0);
|
||||||
|
|
||||||
|
for id in &ids {
|
||||||
|
assert!(corpus.get(*id).is_ok(), "Failed to get testcase {id:?}");
|
||||||
|
assert!(
|
||||||
|
corpus.get_from_all(*id).is_ok(),
|
||||||
|
"Failed to get testcase from all {id:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-existent ID should fail
|
||||||
|
let invalid_id = CorpusId(999);
|
||||||
|
assert!(corpus.get(invalid_id).is_err());
|
||||||
|
assert!(corpus.get_from_all(invalid_id).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(feature = "corpus_btreemap"))]
|
||||||
|
fn test_corpus_disable_enable() -> Result<(), Error> {
|
||||||
|
let (mut corpus, ids) = setup_corpus();
|
||||||
|
let invalid_id = CorpusId(999);
|
||||||
|
|
||||||
|
corpus.disable(ids[1])?;
|
||||||
|
assert_corpus_counts(&corpus, 2, 1);
|
||||||
|
|
||||||
|
// Verify disabled testcase is not in enabled list but is in all list
|
||||||
|
assert!(
|
||||||
|
corpus.get(ids[1]).is_err(),
|
||||||
|
"Disabled testcase should not be accessible via get()"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
corpus.get_from_all(ids[1]).is_ok(),
|
||||||
|
"Disabled testcase should be accessible via get_from_all()"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Other testcases are still accessible
|
||||||
|
assert!(corpus.get(ids[0]).is_ok());
|
||||||
|
assert!(corpus.get(ids[2]).is_ok());
|
||||||
|
|
||||||
|
corpus.enable(ids[1])?;
|
||||||
|
assert_corpus_counts(&corpus, 3, 0);
|
||||||
|
|
||||||
|
// Verify all testcases are accessible from the enabled map again
|
||||||
|
for id in &ids {
|
||||||
|
assert!(corpus.get(*id).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Corner cases
|
||||||
|
assert!(
|
||||||
|
corpus.disable(ids[1]).is_ok(),
|
||||||
|
"Should be able to disable testcase"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
corpus.disable(ids[1]).is_err(),
|
||||||
|
"Should not be able to disable already disabled testcase"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
corpus.enable(ids[0]).is_err(),
|
||||||
|
"Should not be able to enable already enabled testcase"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
corpus.disable(invalid_id).is_err(),
|
||||||
|
"Should not be able to disable non-existent testcase"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
corpus.enable(invalid_id).is_err(),
|
||||||
|
"Should not be able to enable non-existent testcase"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(feature = "corpus_btreemap"))]
|
||||||
|
fn test_corpus_operations_after_disabled() -> Result<(), Error> {
|
||||||
|
let (mut corpus, ids) = setup_corpus();
|
||||||
|
|
||||||
|
corpus.disable(ids[0])?;
|
||||||
|
assert_corpus_counts(&corpus, 2, 1);
|
||||||
|
|
||||||
|
let removed = corpus.remove(ids[0])?;
|
||||||
|
let removed_data = removed.input().as_ref().unwrap().mutator_bytes();
|
||||||
|
assert_eq!(
|
||||||
|
removed_data,
|
||||||
|
&vec![1, 2, 3],
|
||||||
|
"Removed testcase has incorrect data"
|
||||||
|
);
|
||||||
|
assert_corpus_counts(&corpus, 2, 0);
|
||||||
|
|
||||||
|
let removed = corpus.remove(ids[1])?;
|
||||||
|
let removed_data = removed.input().as_ref().unwrap().mutator_bytes();
|
||||||
|
assert_eq!(
|
||||||
|
removed_data,
|
||||||
|
&vec![2, 3, 4],
|
||||||
|
"Removed testcase has incorrect data"
|
||||||
|
);
|
||||||
|
assert_corpus_counts(&corpus, 1, 0);
|
||||||
|
|
||||||
|
// Not possible to get removed testcases
|
||||||
|
assert!(corpus.get(ids[0]).is_err());
|
||||||
|
assert!(corpus.get_from_all(ids[0]).is_err());
|
||||||
|
assert!(corpus.get(ids[1]).is_err());
|
||||||
|
assert!(corpus.get_from_all(ids[1]).is_err());
|
||||||
|
|
||||||
|
// Only the third testcase should remain
|
||||||
|
assert!(corpus.get(ids[2]).is_ok());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,7 +20,7 @@ use libafl_bolts::compress::GzipCompressor;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
HasTestcase,
|
EnableDisableCorpus, HasTestcase,
|
||||||
ondisk::{OnDiskMetadata, OnDiskMetadataFormat},
|
ondisk::{OnDiskMetadata, OnDiskMetadataFormat},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -214,6 +214,29 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<I> EnableDisableCorpus for InMemoryOnDiskCorpus<I>
|
||||||
|
where
|
||||||
|
I: Input,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn disable(&mut self, id: CorpusId) -> Result<(), Error> {
|
||||||
|
self.inner.disable(id)?;
|
||||||
|
// Ensure testcase is saved to disk correctly with its new status
|
||||||
|
let testcase_cell = &mut self.get_from_all(id).unwrap().borrow_mut();
|
||||||
|
self.save_testcase(testcase_cell, Some(id))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn enable(&mut self, id: CorpusId) -> Result<(), Error> {
|
||||||
|
self.inner.enable(id)?;
|
||||||
|
// Ensure testcase is saved to disk correctly with its new status
|
||||||
|
let testcase_cell = &mut self.get_from_all(id).unwrap().borrow_mut();
|
||||||
|
self.save_testcase(testcase_cell, Some(id))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<I> HasTestcase<I> for InMemoryOnDiskCorpus<I>
|
impl<I> HasTestcase<I> for InMemoryOnDiskCorpus<I>
|
||||||
where
|
where
|
||||||
I: Input,
|
I: Input,
|
||||||
|
@ -195,6 +195,15 @@ pub trait Corpus<I>: Sized {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Marker trait for corpus implementations that actually support enable/disable functionality
|
||||||
|
pub trait EnableDisableCorpus {
|
||||||
|
/// Disables a testcase, moving it to the disabled map
|
||||||
|
fn disable(&mut self, id: CorpusId) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Enables a testcase, moving it to the enabled map
|
||||||
|
fn enable(&mut self, id: CorpusId) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Trait for types which track the current corpus index
|
/// Trait for types which track the current corpus index
|
||||||
pub trait HasCurrentCorpusId {
|
pub trait HasCurrentCorpusId {
|
||||||
/// Set the current corpus index; we have started processing this corpus entry
|
/// Set the current corpus index; we have started processing this corpus entry
|
||||||
|
@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Error,
|
Error,
|
||||||
corpus::{CachedOnDiskCorpus, Corpus, CorpusId, HasTestcase, Testcase},
|
corpus::{CachedOnDiskCorpus, Corpus, CorpusId, EnableDisableCorpus, HasTestcase, Testcase},
|
||||||
inputs::Input,
|
inputs::Input,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -188,6 +188,21 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<I> EnableDisableCorpus for OnDiskCorpus<I>
|
||||||
|
where
|
||||||
|
I: Input,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn disable(&mut self, id: CorpusId) -> Result<(), Error> {
|
||||||
|
self.inner.disable(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn enable(&mut self, id: CorpusId) -> Result<(), Error> {
|
||||||
|
self.inner.enable(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<I> OnDiskCorpus<I> {
|
impl<I> OnDiskCorpus<I> {
|
||||||
/// Creates an [`OnDiskCorpus`].
|
/// Creates an [`OnDiskCorpus`].
|
||||||
///
|
///
|
||||||
|
Loading…
x
Reference in New Issue
Block a user