Add BoolValueFeedback (#2815)

* Add BoolValueFeedback

* No_std

* clippy

* Fix tests

* More clip

* fix no_std tests
This commit is contained in:
Dominik Maier 2025-01-06 04:25:36 +01:00 committed by GitHub
parent 4b4a22bc44
commit 742773bc17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 223 additions and 44 deletions

View File

@ -737,6 +737,13 @@ mod tests {
#[serial]
#[cfg_attr(miri, ignore)]
fn test_mgr_state_restore() {
// # Safety
// The same testcase doesn't usually run twice
#[cfg(any(not(feature = "serdeany_autoreg"), miri))]
unsafe {
crate::stages::RetryCountRestartHelper::register();
}
let rand = StdRand::with_seed(0);
let time = TimeObserver::new("time");

View File

@ -0,0 +1,154 @@
//! The [`BoolValueFeedback`] is a [`Feedback`] returning `true` or `false` as the `is_interesting` value.
use alloc::borrow::Cow;
use libafl_bolts::{
tuples::{Handle, MatchNameRef},
Error, Named,
};
use crate::{
feedbacks::{Feedback, StateInitializer},
observers::{ObserversTuple, ValueObserver},
HasNamedMetadata,
};
/// This feedback returns `true` or `false` as the `is_interesting` value.
#[derive(Debug)]
pub struct BoolValueFeedback<'a> {
name: Cow<'static, str>,
observer_hnd: Handle<ValueObserver<'a, bool>>,
#[cfg(feature = "track_hit_feedbacks")]
last_result: Option<bool>,
}
impl<'a> BoolValueFeedback<'a> {
/// Create a new [`BoolValueFeedback`]
#[must_use]
pub fn new(observer_hnd: &Handle<ValueObserver<'a, bool>>) -> Self {
Self::with_name(observer_hnd.name().clone(), observer_hnd)
}
/// Create a new [`BoolValueFeedback`] with a given name
#[must_use]
pub fn with_name(
name: Cow<'static, str>,
observer_hnd: &Handle<ValueObserver<'a, bool>>,
) -> Self {
Self {
name,
observer_hnd: observer_hnd.clone(),
#[cfg(feature = "track_hit_feedbacks")]
last_result: None,
}
}
}
impl Named for BoolValueFeedback<'_> {
fn name(&self) -> &Cow<'static, str> {
&self.name
}
}
impl<S> StateInitializer<S> for BoolValueFeedback<'_> {
fn init_state(&mut self, _state: &mut S) -> Result<(), Error> {
Ok(())
}
}
impl<EM, I, OT, S> Feedback<EM, I, OT, S> for BoolValueFeedback<'_>
where
OT: ObserversTuple<I, S>,
S: HasNamedMetadata,
{
fn is_interesting(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &I,
observers: &OT,
_exit_kind: &crate::executors::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();
Ok(*val)
}
fn append_metadata(
&mut self,
_state: &mut S,
_manager: &mut EM,
_observers: &OT,
_testcase: &mut crate::corpus::Testcase<I>,
) -> Result<(), Error> {
Ok(())
}
fn discard_metadata(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
Ok(())
}
#[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 `BoolValuefeedback`. 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 crate::{
executors::ExitKind,
feedbacks::{BoolValueFeedback, Feedback, StateInitializer},
observers::ValueObserver,
state::NopState,
};
#[test]
fn test_bool_value_feedback() {
let value: UnsafeCell<bool> = false.into();
// # Safety
// The value is only read from in the feedback, not while we change the value.
let value_ptr = unsafe { OwnedRef::from_ptr(value.get()) };
let observer = ValueObserver::new("test_value", value_ptr);
let mut bool_feedback = BoolValueFeedback::new(&observer.handle());
let mut state: NopState<()> = NopState::new();
bool_feedback.init_state(&mut state).unwrap();
let observers = tuple_list!(observer);
let mut mgr = ();
let input = ();
let exit_ok = ExitKind::Ok;
let false_eval = bool_feedback
.is_interesting(&mut state, &mut mgr, &input, &observers, &exit_ok)
.unwrap();
assert!(!false_eval);
// # Safety
// The feedback is not keeping a borrow around, only the pointer.
unsafe {
write_volatile(value.get(), true);
}
let true_eval = bool_feedback
.is_interesting(&mut state, &mut mgr, &input, &observers, &exit_ok)
.unwrap();
assert!(true_eval);
}
}

View File

@ -31,6 +31,9 @@ use crate::{corpus::Testcase, executors::ExitKind, observers::TimeObserver, Erro
#[cfg(feature = "std")]
pub mod capture_feedback;
pub mod bool;
pub use bool::BoolValueFeedback;
#[cfg(feature = "std")]
pub mod concolic;
#[cfg(feature = "std")]

View File

@ -1,4 +1,4 @@
//! The ``NewHashFeedback`` uses the backtrace hash and a hashset to only keep novel cases
//! The [`NewHashFeedback`] uses the backtrace hash and a hashset to only keep novel cases
use alloc::{borrow::Cow, string::ToString};
use std::fmt::Debug;

View File

@ -12,9 +12,9 @@ use libafl_bolts::{
};
use serde::{Deserialize, Serialize};
use super::{Feedback, StateInitializer};
use crate::{
executors::ExitKind,
feedbacks::{Feedback, StateInitializer},
observers::{ObserversTuple, ValueObserver},
HasNamedMetadata,
};
@ -117,73 +117,66 @@ impl<EM, I, OT: ObserversTuple<I, S>, S: HasNamedMetadata, T: Hash> Feedback<EM,
#[cfg(test)]
mod test {
use core::ptr::write_volatile;
use core::{cell::UnsafeCell, ptr::write_volatile};
use libafl_bolts::{ownedref::OwnedRef, serdeany::NamedSerdeAnyMap, tuples::Handled};
use libafl_bolts::{ownedref::OwnedRef, tuples::Handled};
use tuple_list::tuple_list;
use super::ValueBloomFeedback;
use crate::{
events::NopEventManager,
executors::ExitKind,
feedbacks::{Feedback, StateInitializer},
inputs::NopInput,
observers::ValueObserver,
HasNamedMetadata,
state::NopState,
};
static mut VALUE: u32 = 0;
struct NamedMetadataState {
map: NamedSerdeAnyMap,
}
impl HasNamedMetadata for NamedMetadataState {
fn named_metadata_map(&self) -> &NamedSerdeAnyMap {
&self.map
}
fn named_metadata_map_mut(&mut self) -> &mut NamedSerdeAnyMap {
&mut self.map
}
}
#[test]
fn test_value_bloom_feedback() {
let value_ptr = unsafe { OwnedRef::from_ptr(&raw mut VALUE) };
let value: UnsafeCell<u32> = 0_u32.into();
// # Safety
// The same testcase doesn't usually run twice
#[cfg(any(not(feature = "serdeany_autoreg"), miri))]
unsafe {
super::ValueBloomFeedbackMetadata::register();
}
// # Safety
// The value is only read from in the feedback, not while we change the value.
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 mut state = NamedMetadataState {
map: NamedSerdeAnyMap::new(),
};
vbf.init_state(&mut state).unwrap();
let observers = tuple_list!(observer);
let mut mgr = NopEventManager::<NamedMetadataState>::new();
let input = NopInput {};
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_eq!(first_eval, true);
assert!(first_eval);
let second_eval = vbf
.is_interesting(&mut state, &mut mgr, &input, &observers, &exit_ok)
.unwrap();
assert_ne!(first_eval, second_eval);
assert!(!second_eval);
// # Safety
// The feedback is not keeping a borrow around, only the pointer.
unsafe {
write_volatile(&raw mut VALUE, 1234_u32);
write_volatile(value.get(), 1234_u32);
}
let next_eval = vbf
.is_interesting(&mut state, &mut mgr, &input, &observers, &exit_ok)
.unwrap();
assert_eq!(next_eval, true);
assert!(next_eval);
}
}

View File

@ -116,13 +116,23 @@ macro_rules! none_input_converter {
}
/// An input for tests, mainly. There is no real use much else.
#[derive(Copy, Clone, Serialize, Deserialize, Debug, Hash)]
#[derive(Copy, Clone, Serialize, Deserialize, Debug, Default, Hash)]
pub struct NopInput {}
impl NopInput {
/// Creates a new [`NopInput`]
#[must_use]
pub fn new() -> Self {
Self {}
}
}
impl Input for NopInput {
fn generate_name(&self, _id: Option<CorpusId>) -> String {
"nop-input".to_string()
}
}
impl HasTargetBytes for NopInput {
fn target_bytes(&self) -> OwnedSlice<u8> {
OwnedSlice::from(vec![0])

View File

@ -612,13 +612,6 @@ mod test {
/// Test to test retries in stages
#[test]
fn test_tries_progress() -> Result<(), Error> {
// # Safety
// No concurrency per testcase
#[cfg(any(not(feature = "serdeany_autoreg"), miri))]
unsafe {
RetryCountRestartHelper::register();
}
struct StageWithOneTry;
impl Named for StageWithOneTry {
@ -628,6 +621,13 @@ mod test {
}
}
// # Safety
// No concurrency per testcase
#[cfg(any(not(feature = "serdeany_autoreg"), miri))]
unsafe {
RetryCountRestartHelper::register();
}
let mut state = StdState::nop()?;
let stage = StageWithOneTry;

View File

@ -1242,6 +1242,7 @@ impl<I, C, R, SC> HasScalabilityMonitor for StdState<I, C, R, SC> {
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct NopState<I> {
metadata: SerdeAnyMap,
named_metadata: NamedSerdeAnyMap,
execution: u64,
stop_requested: bool,
rand: StdRand,
@ -1254,6 +1255,7 @@ impl<I> NopState<I> {
pub fn new() -> Self {
NopState {
metadata: SerdeAnyMap::new(),
named_metadata: NamedSerdeAnyMap::new(),
execution: 0,
rand: StdRand::default(),
stop_requested: false,
@ -1323,6 +1325,16 @@ impl<I> HasMetadata for NopState<I> {
}
}
impl<I> HasNamedMetadata for NopState<I> {
fn named_metadata_map(&self) -> &NamedSerdeAnyMap {
&self.named_metadata
}
fn named_metadata_map_mut(&mut self) -> &mut NamedSerdeAnyMap {
&mut self.named_metadata
}
}
impl<I> HasRand for NopState<I> {
type Rand = StdRand;