More usable ListFeedback (#1959)

* real list feedback

* add

* fox

* obs

* fix

* FMT

* more

* fmt

* rev
This commit is contained in:
Dongjia "toka" Zhang 2024-03-21 16:21:15 +01:00 committed by GitHub
parent 7abc26ebc9
commit 15f4613894
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 261 additions and 151 deletions

View File

@ -40,7 +40,7 @@ fn main() {
// let args = vec!["test.exe".to_string(), "-f".to_string(), "@@".to_string()];
let observer = unsafe { ListObserver::new("cov", &mut COVERAGE) };
let mut feedback = ListFeedback::with_observer(&observer);
let mut feedback = ListFeedback::new(&observer);
#[cfg(windows)]
let mut shmem_provider = Win32ShMemProvider::new().unwrap();

View File

@ -0,0 +1,172 @@
use alloc::string::{String, ToString};
use core::{fmt::Debug, hash::Hash, marker::PhantomData};
use hashbrown::HashSet;
use libafl_bolts::{Error, HasRefCnt, Named};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use crate::{
events::EventFirer,
executors::ExitKind,
feedbacks::Feedback,
observers::{ListObserver, ObserversTuple},
state::{HasNamedMetadata, State},
};
/// The metadata to remember past observed value
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "T: DeserializeOwned")]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)]
pub struct ListFeedbackMetadata<T>
where
T: Default + Copy + 'static + Serialize + Eq + Hash,
{
/// Contains the information of past observed set of values.
pub set: HashSet<T>,
/// A refcount used to know when we can remove this metadata
pub tcref: isize,
}
impl<T> ListFeedbackMetadata<T>
where
T: Default + Copy + 'static + Serialize + Eq + Hash,
{
/// The constructor
#[must_use]
pub fn new() -> Self {
Self {
set: HashSet::<T>::new(),
tcref: 0,
}
}
/// Reset the inner hashset
pub fn reset(&mut self) -> Result<(), Error> {
self.set.clear();
Ok(())
}
}
impl<T> HasRefCnt for ListFeedbackMetadata<T>
where
T: Default + Copy + 'static + Serialize + Eq + Hash,
{
fn refcnt(&self) -> isize {
self.tcref
}
fn refcnt_mut(&mut self) -> &mut isize {
&mut self.tcref
}
}
/// Consider interesting a testcase if the list in `ListObserver` is not empty.
#[derive(Clone, Debug)]
pub struct ListFeedback<T>
where
T: Hash + Eq,
{
name: String,
observer_name: String,
novelty: HashSet<T>,
phantom: PhantomData<T>,
}
libafl_bolts::impl_serdeany!(
ListFeedbackMetadata<T: Debug + Default + Copy + 'static + Serialize + DeserializeOwned + Eq + Hash>,
<u8>,<u16>,<u32>,<u64>,<i8>,<i16>,<i32>,<i64>,<bool>,<char>,<usize>
);
impl<S, T> Feedback<S> for ListFeedback<T>
where
S: State + HasNamedMetadata,
T: Debug + Serialize + Hash + Eq + DeserializeOwned + Default + Copy + 'static,
{
fn init_state(&mut self, state: &mut S) -> Result<(), Error> {
// eprintln!("self.name {:#?}", &self.name);
state.add_named_metadata(&self.name, ListFeedbackMetadata::<T>::default());
Ok(())
}
#[allow(clippy::wrong_self_convention)]
fn is_interesting<EM, OT>(
&mut self,
state: &mut S,
_manager: &mut EM,
_input: &S::Input,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>,
{
// TODO Replace with match_name_type when stable
let observer = observers
.match_name::<ListObserver<T>>(&self.observer_name)
.unwrap();
// TODO register the list content in a testcase metadata
self.novelty.clear();
// can't fail
let history_set = state
.named_metadata_map_mut()
.get_mut::<ListFeedbackMetadata<T>>(&self.name)
.unwrap();
for v in observer.list() {
if !history_set.set.contains(v) {
self.novelty.insert(*v);
}
}
Ok(!self.novelty.is_empty())
}
fn append_metadata<EM, OT>(
&mut self,
state: &mut S,
_manager: &mut EM,
_observers: &OT,
_testcase: &mut crate::corpus::Testcase<<S>::Input>,
) -> Result<(), Error>
where
OT: ObserversTuple<S>,
EM: EventFirer<State = S>,
{
let history_set = state
.named_metadata_map_mut()
.get_mut::<ListFeedbackMetadata<T>>(&self.name)
.unwrap();
for v in &self.novelty {
history_set.set.insert(*v);
}
Ok(())
}
}
impl<T> Named for ListFeedback<T>
where
T: Debug + Serialize + Hash + Eq + DeserializeOwned,
{
#[inline]
fn name(&self) -> &str {
self.name.as_str()
}
}
impl<T> ListFeedback<T>
where
T: Debug + Serialize + Hash + Eq + DeserializeOwned,
{
/// Creates a new [`ListFeedback`], deciding if the given [`ListObserver`] value of a run is interesting.
#[must_use]
pub fn new(observer: &ListObserver<T>) -> Self {
Self {
name: observer.name().to_string(),
observer_name: observer.name().to_string(),
novelty: HashSet::<T>::new(),
phantom: PhantomData,
}
}
}

View File

@ -28,9 +28,6 @@ use crate::{
Error,
};
/// The prefix of the metadata names
pub const MAPFEEDBACK_PREFIX: &str = "mapfeedback_metadata_";
/// A [`MapFeedback`] that implements the AFL algorithm using an [`OrReducer`] combining the bits for the history map and the bit from ``HitcountsMapObserver``.
pub type AflMapFeedback<O, S, T> = MapFeedback<DifferentIsNovel, O, OrReducer, S, T>;
@ -700,7 +697,7 @@ where
Self {
indexes: false,
novelties: None,
name: MAPFEEDBACK_PREFIX.to_string() + map_observer.name(),
name: map_observer.name().to_string(),
observer_name: map_observer.name().to_string(),
stats_name: create_stats_name(map_observer.name()),
phantom: PhantomData,
@ -713,7 +710,7 @@ where
Self {
indexes: track_indexes,
novelties: if track_novelties { Some(vec![]) } else { None },
name: MAPFEEDBACK_PREFIX.to_string() + map_observer.name(),
name: map_observer.name().to_string(),
observer_name: map_observer.name().to_string(),
stats_name: create_stats_name(map_observer.name()),
phantom: PhantomData,

View File

@ -25,6 +25,8 @@ pub use new_hash_feedback::NewHashFeedbackMetadata;
pub mod nautilus;
pub mod transferred;
/// The module for list feedback
pub mod list;
use alloc::string::{String, ToString};
use core::{
fmt::{self, Debug, Formatter},
@ -32,6 +34,7 @@ use core::{
};
use libafl_bolts::Named;
pub use list::*;
#[cfg(feature = "nautilus")]
pub use nautilus::*;
use serde::{Deserialize, Serialize};
@ -40,7 +43,7 @@ use crate::{
corpus::Testcase,
events::EventFirer,
executors::ExitKind,
observers::{ListObserver, ObserversTuple, TimeObserver},
observers::{ObserversTuple, TimeObserver},
state::State,
Error,
};
@ -972,79 +975,6 @@ impl TimeFeedback {
}
}
/// Consider interesting a testcase if the list in `ListObserver` is not empty.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ListFeedback<T>
where
T: Debug + Serialize + serde::de::DeserializeOwned,
{
name: String,
last_addr: usize,
phantom: PhantomData<T>,
}
impl<S, T> Feedback<S> for ListFeedback<T>
where
S: State,
T: Debug + Serialize + serde::de::DeserializeOwned,
{
#[allow(clippy::wrong_self_convention)]
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &S::Input,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>,
{
// TODO Replace with match_name_type when stable
let observer = observers
.match_name::<ListObserver<T>>(self.name())
.unwrap();
// TODO register the list content in a testcase metadata
Ok(!observer.list().is_empty())
}
}
impl<T> Named for ListFeedback<T>
where
T: Debug + Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn name(&self) -> &str {
self.name.as_str()
}
}
impl<T> ListFeedback<T>
where
T: Debug + Serialize + serde::de::DeserializeOwned,
{
/// Creates a new [`ListFeedback`], deciding if the value of a [`ListObserver`] with the given `name` of a run is interesting.
#[must_use]
pub fn new(name: &'static str) -> Self {
Self {
name: name.to_string(),
last_addr: 0,
phantom: PhantomData,
}
}
/// Creates a new [`TimeFeedback`], deciding if the given [`ListObserver`] value of a run is interesting.
#[must_use]
pub fn with_observer(observer: &ListObserver<T>) -> Self {
Self {
name: observer.name().to_string(),
last_addr: 0,
phantom: PhantomData,
}
}
}
/// The [`ConstFeedback`] reports the same value, always.
/// It can be used to enable or disable feedback results through composition.
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]

View File

@ -0,0 +1,73 @@
use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::fmt::Debug;
use libafl_bolts::{ownedref::OwnedMutPtr, Error, Named};
use serde::{Deserialize, Serialize};
use crate::{inputs::UsesInput, observers::Observer};
/// A simple observer with a list of things.
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "T: serde::de::DeserializeOwned")]
#[allow(clippy::unsafe_derive_deserialize)]
pub struct ListObserver<T>
where
T: Debug + Serialize,
{
name: String,
/// The list
list: OwnedMutPtr<Vec<T>>,
}
impl<T> ListObserver<T>
where
T: Debug + Serialize + serde::de::DeserializeOwned,
{
/// Creates a new [`ListObserver`] with the given name.
///
/// # Safety
/// Will dereference the list.
/// The list may not move in memory.
#[must_use]
pub unsafe fn new(name: &'static str, list: *mut Vec<T>) -> Self {
Self {
name: name.to_string(),
list: OwnedMutPtr::Ptr(list),
}
}
/// Get a list ref
#[must_use]
pub fn list(&self) -> &Vec<T> {
self.list.as_ref()
}
/// Get a list mut
#[must_use]
pub fn list_mut(&mut self) -> &mut Vec<T> {
self.list.as_mut()
}
}
impl<S, T> Observer<S> for ListObserver<T>
where
S: UsesInput,
T: Debug + Serialize + serde::de::DeserializeOwned,
{
fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
self.list.as_mut().clear();
Ok(())
}
}
impl<T> Named for ListObserver<T>
where
T: Debug + Serialize + serde::de::DeserializeOwned,
{
fn name(&self) -> &str {
&self.name
}
}

View File

@ -20,17 +20,17 @@ pub mod concolic;
pub mod value;
use alloc::{
string::{String, ToString},
vec::Vec,
};
/// List observer
pub mod list;
use alloc::string::{String, ToString};
use core::{fmt::Debug, time::Duration};
#[cfg(feature = "std")]
use std::time::Instant;
#[cfg(feature = "no_std")]
use libafl_bolts::current_time;
use libafl_bolts::{ownedref::OwnedMutPtr, tuples::MatchName, Named};
use libafl_bolts::{tuples::MatchName, Named};
pub use list::*;
use serde::{Deserialize, Serialize};
pub use value::*;
@ -526,73 +526,11 @@ where
{
}
/// A simple observer with a list of things.
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "T: serde::de::DeserializeOwned")]
#[allow(clippy::unsafe_derive_deserialize)]
pub struct ListObserver<T>
where
T: Debug + Serialize,
{
name: String,
/// The list
list: OwnedMutPtr<Vec<T>>,
}
impl<T> ListObserver<T>
where
T: Debug + Serialize + serde::de::DeserializeOwned,
{
/// Creates a new [`ListObserver`] with the given name.
///
/// # Safety
/// Will dereference the list.
/// The list may not move in memory.
#[must_use]
pub unsafe fn new(name: &'static str, list: *mut Vec<T>) -> Self {
Self {
name: name.to_string(),
list: OwnedMutPtr::Ptr(list),
}
}
/// Get a list ref
#[must_use]
pub fn list(&self) -> &Vec<T> {
self.list.as_ref()
}
/// Get a list mut
#[must_use]
pub fn list_mut(&mut self) -> &mut Vec<T> {
self.list.as_mut()
}
}
impl<S, T> Observer<S> for ListObserver<T>
where
S: UsesInput,
T: Debug + Serialize + serde::de::DeserializeOwned,
{
fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
self.list.as_mut().clear();
Ok(())
}
}
impl<T> Named for ListObserver<T>
where
T: Debug + Serialize + serde::de::DeserializeOwned,
{
fn name(&self) -> &str {
&self.name
}
}
/// `Observer` Python bindings
#[cfg(feature = "python")]
#[allow(missing_docs)]
pub mod pybind {
use alloc::vec::Vec;
use core::ptr;
use std::cell::UnsafeCell;
@ -603,7 +541,7 @@ pub mod pybind {
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use super::{Debug, Observer, ObserversTuple, String, Vec};
use super::{Debug, Observer, ObserversTuple, String};
use crate::{
executors::{pybind::PythonExitKind, ExitKind},
inputs::{BytesInput, HasBytesVec},

View File

@ -3,7 +3,7 @@ use std::ffi::c_int;
use libafl::{
events::{ProgressReporter, SimpleEventManager},
executors::HasObservers,
feedbacks::{MapFeedbackMetadata, MAPFEEDBACK_PREFIX},
feedbacks::MapFeedbackMetadata,
inputs::UsesInput,
monitors::SimpleMonitor,
stages::{HasCurrentStage, StagesTuple},
@ -35,7 +35,7 @@ where
ST: StagesTuple<E, EM, S, F>,
{
let meta = state
.named_metadata::<MapFeedbackMetadata<u8>>(&(MAPFEEDBACK_PREFIX.to_string() + "edges"))
.named_metadata::<MapFeedbackMetadata<u8>>("edges")
.unwrap();
let observed = meta.history_map.iter().filter(|&&e| e != 0).count();
let total = meta.history_map.len();