use alloc::borrow::Cow;
use core::hash::Hash;
use fastbloom::BloomFilter;
use libafl_bolts::{
Error, Named, impl_serdeany,
tuples::{Handle, MatchNameRef},
};
use serde::{Deserialize, Serialize};
use crate::{
HasNamedMetadata,
executors::ExitKind,
feedbacks::{Feedback, StateInitializer},
observers::{ObserversTuple, ValueObserver},
};
impl_serdeany!(ValueBloomFeedbackMetadata);
#[derive(Debug, Serialize, Deserialize)]
struct ValueBloomFeedbackMetadata {
bloom: BloomFilter,
}
#[derive(Debug)]
pub struct ValueBloomFeedback<'a, T> {
name: Cow<'static, str>,
observer_hnd: Handle<ValueObserver<'a, T>>,
#[cfg(feature = "track_hit_feedbacks")]
last_result: Option<bool>,
}
impl<'a, T> ValueBloomFeedback<'a, T> {
#[must_use]
pub fn new(observer_hnd: &Handle<ValueObserver<'a, T>>) -> Self {
Self::with_name(observer_hnd.name().clone(), observer_hnd)
}
#[must_use]
pub fn with_name(name: Cow<'static, str>, observer_hnd: &Handle<ValueObserver<'a, T>>) -> Self {
Self {
name,
observer_hnd: observer_hnd.clone(),
#[cfg(feature = "track_hit_feedbacks")]
last_result: None,
}
}
}
impl<T> Named for ValueBloomFeedback<'_, T> {
fn name(&self) -> &Cow<'static, str> {
&self.name
}
}
impl<S: HasNamedMetadata, T> StateInitializer<S> for ValueBloomFeedback<'_, T> {
fn init_state(&mut self, state: &mut S) -> Result<(), Error> {
let _ =
state.named_metadata_or_insert_with::<ValueBloomFeedbackMetadata>(&self.name, || {
ValueBloomFeedbackMetadata {
bloom: BloomFilter::with_false_pos(0.001).expected_items(1024),
}
});
Ok(())
}
}
impl<EM, I, OT: ObserversTuple<I, S>, S: HasNamedMetadata, T: Hash> Feedback<EM, I, OT, S>
for ValueBloomFeedback<'_, T>
{
fn is_interesting(
&mut self,
state: &mut S,
_manager: &mut EM,
_input: &I,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error> {
let Some(observer) = observers.get(&self.observer_hnd) else {
return Err(Error::illegal_state(format!(
"Observer {:?} not found",
self.observer_hnd
)));
};
let val = observer.value.as_ref();
let metadata = state.named_metadata_mut::<ValueBloomFeedbackMetadata>(&self.name)?;
let res = if metadata.bloom.contains(val) {
false
} else {
metadata.bloom.insert(val);
true
};
#[cfg(feature = "track_hit_feedbacks")]
{
self.last_result = Some(true);
}
Ok(res)
}
#[cfg(feature = "track_hit_feedbacks")]
fn last_result(&self) -> Result<bool, Error> {
self.last_result.ok_or_else(|| Error::illegal_state("No last result set in `ValueBloomFeedback`. Either `is_interesting` has never been called or the fuzzer restarted in the meantime."))
}
}
#[cfg(test)]
mod test {
use core::{cell::UnsafeCell, ptr::write_volatile};
use libafl_bolts::{ownedref::OwnedRef, tuples::Handled};
use tuple_list::tuple_list;
use super::ValueBloomFeedback;
use crate::{
executors::ExitKind,
feedbacks::{Feedback, StateInitializer},
observers::ValueObserver,
state::NopState,
};
#[test]
fn test_value_bloom_feedback() {
let value: UnsafeCell<u32> = 0_u32.into();
#[cfg(any(not(feature = "serdeany_autoreg"), miri))]
unsafe {
super::ValueBloomFeedbackMetadata::register();
}
let value_ptr = unsafe { OwnedRef::from_ptr(value.get()) };
let observer = ValueObserver::new("test_value", value_ptr);
let mut vbf = ValueBloomFeedback::new(&observer.handle());
let observers = tuple_list!(observer);
let mut state: NopState<()> = NopState::new();
let mut mgr = ();
let input = ();
let exit_ok = ExitKind::Ok;
vbf.init_state(&mut state).unwrap();
let first_eval = vbf
.is_interesting(&mut state, &mut mgr, &input, &observers, &exit_ok)
.unwrap();
assert!(first_eval);
let second_eval = vbf
.is_interesting(&mut state, &mut mgr, &input, &observers, &exit_ok)
.unwrap();
assert!(!second_eval);
unsafe {
write_volatile(value.get(), 1234_u32);
}
let next_eval = vbf
.is_interesting(&mut state, &mut mgr, &input, &observers, &exit_ok)
.unwrap();
assert!(next_eval);
}
}