MultiMapObserver and sancov 8bit-counters instrumentation (#343)

* MultiMapObserver and 8bit instrumentation

* fix test

* clippy

* fix

* fix tutorial

* sancov_8bit targets feature
This commit is contained in:
Andrea Fioraldi 2021-11-04 10:08:50 +01:00 committed by GitHub
parent e46bb8643a
commit eca605bf01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 337 additions and 70 deletions

View File

@ -1,6 +1,6 @@
use libafl::{
bolts::ownedref::OwnedSlice,
inputs::{HasLen, HasTargetBytes, Input},
bolts::{ownedref::OwnedSlice, HasLen},
inputs::{HasTargetBytes, Input},
};
use lain::prelude::*;

View File

@ -68,6 +68,7 @@ ctor = "0.1.20"
num_enum = { version = "0.5.1", default-features = false }
typed-builder = "0.9.0" # Implement the builder pattern at compiletime
ahash = { version = "0.7", default-features=false, features=["compile-time-rng"] } # The hash function already used in hashbrown
intervaltree = { git = "https://github.com/andreafioraldi/rust-intervaltree", version = "0.2.6", default-features = false, features = ["serde"] }
libafl_derive = { version = "0.6.1", optional = true, path = "../libafl_derive" }
serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } # an easy way to debug print SerdeAnyMap

View File

@ -27,6 +27,17 @@ pub trait AsSlice<T> {
fn as_slice(&self) -> &[T];
}
/// Has a length field
pub trait HasLen {
/// The length
fn len(&self) -> usize;
/// Returns `true` if it has no elements.
fn is_empty(&self) -> bool {
self.len() == 0
}
}
/// Current time
#[cfg(feature = "std")]
#[must_use]

View File

@ -35,7 +35,7 @@ const fn type_eq<T: ?Sized, U: ?Sized>() -> bool {
}
/// Gets the length of the element
pub trait HasLen {
pub trait HasConstLen {
/// The length as constant `usize`
const LEN: usize;
@ -47,7 +47,7 @@ pub trait HasLen {
}
}
impl HasLen for () {
impl HasConstLen for () {
const LEN: usize = 0;
fn len(&self) -> usize {
@ -55,9 +55,9 @@ impl HasLen for () {
}
}
impl<Head, Tail> HasLen for (Head, Tail)
impl<Head, Tail> HasConstLen for (Head, Tail)
where
Tail: HasLen,
Tail: HasConstLen,
{
const LEN: usize = 1 + Tail::LEN;
@ -78,7 +78,7 @@ pub trait HasNameId {
}
/// Gets the id and `const_name` for the given index in a tuple
pub trait HasNameIdTuple: HasLen {
pub trait HasNameIdTuple: HasConstLen {
/// Gets the `const_name` for the entry at the given index
fn const_name_for(&self, index: usize) -> Option<&'static str>;
@ -199,7 +199,7 @@ pub trait Named {
}
/// A named tuple
pub trait NamedTuple: HasLen {
pub trait NamedTuple: HasConstLen {
/// Gets the name of this tuple
fn name(&self, index: usize) -> Option<&str>;
}

View File

@ -2,10 +2,10 @@
// with testcases only from a subset of the total corpus.
use crate::{
bolts::{rands::Rand, serdeany::SerdeAny, AsSlice},
bolts::{rands::Rand, serdeany::SerdeAny, AsSlice, HasLen},
corpus::{Corpus, CorpusScheduler, Testcase},
feedbacks::MapIndexesMetadata,
inputs::{HasLen, Input},
inputs::Input,
state::{HasCorpus, HasMetadata, HasRand},
Error,
};

View File

@ -6,8 +6,8 @@ use core::{convert::Into, default::Default, option::Option, time::Duration};
use serde::{Deserialize, Serialize};
use crate::{
bolts::serdeany::SerdeAnyMap,
inputs::{HasLen, Input},
bolts::{serdeany::SerdeAnyMap, HasLen},
inputs::Input,
state::HasMetadata,
Error,
};

View File

@ -171,7 +171,7 @@ where
O: MapObserver<T>,
{
Self {
history_map: vec![T::default(); map_observer.map().len()],
history_map: vec![T::default(); map_observer.len()],
name: map_observer.name().to_string(),
}
}
@ -245,12 +245,12 @@ where
if size > map_state.history_map.len() {
panic!("The size of the associated map observer cannot exceed the size of the history map of the feedback. If you are running multiple instances of slightly different fuzzers (e.g. one with ASan and another without) synchronized using LLMP please check the `configuration` field of the LLMP manager.");
}
assert!(size <= observer.map().len());
assert!(size <= observer.len());
if self.novelties.is_some() {
for i in 0..size {
let history = map_state.history_map[i];
let item = observer.map()[i];
let item = *observer.get(i);
let reduced = R::reduce(history, item);
if history != reduced {
@ -262,7 +262,7 @@ where
} else {
for i in 0..size {
let history = map_state.history_map[i];
let item = observer.map()[i];
let item = *observer.get(i);
let reduced = R::reduce(history, item);
if history != reduced {
@ -457,7 +457,7 @@ where
let mut hit_target: bool = false;
//check if we've hit any targets.
for i in 0..size {
if observer.map()[i] > 0 {
if *observer.get(i) > 0 {
self.target_idx.push(i);
hit_target = true;
}

View File

@ -12,8 +12,8 @@ use std::{fs::File, io::Read, path::Path};
#[cfg(feature = "std")]
use crate::{bolts::fs::write_file_atomic, Error};
use crate::{
bolts::ownedref::OwnedSlice,
inputs::{HasBytesVec, HasLen, HasTargetBytes, Input},
bolts::{ownedref::OwnedSlice, HasLen},
inputs::{HasBytesVec, HasTargetBytes, Input},
};
/// A bytes input is the basic input

View File

@ -13,10 +13,7 @@ use hashbrown::HashMap;
use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::{
inputs::{HasLen, Input},
Error,
};
use crate::{bolts::HasLen, inputs::Input, Error};
pub trait InputEncoder<T>
where

View File

@ -5,10 +5,7 @@ use alloc::{rc::Rc, string::String, vec::Vec};
use core::{cell::RefCell, convert::From};
use serde::{Deserialize, Serialize};
use crate::{
inputs::{HasLen, Input},
Error,
};
use crate::{bolts::HasLen, inputs::Input, Error};
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
pub struct Terminal {

View File

@ -90,14 +90,3 @@ pub trait HasBytesVec {
/// The internal bytes map (as mutable borrow)
fn bytes_mut(&mut self) -> &mut Vec<u8>;
}
/// Has a length field
pub trait HasLen {
/// The length
fn len(&self) -> usize;
/// Returns `true` if it has no elements.
fn is_empty(&self) -> bool {
self.len() == 0
}
}

View File

@ -14,7 +14,7 @@ pub mod gramatron;
pub use gramatron::*;
use crate::{
bolts::tuples::{HasLen, Named},
bolts::tuples::{HasConstLen, Named},
inputs::Input,
Error,
};
@ -59,7 +59,7 @@ where
}
/// A `Tuple` of `Mutators` that can execute multiple `Mutators` in a row.
pub trait MutatorsTuple<I, S>: HasLen
pub trait MutatorsTuple<I, S>: HasConstLen
where
I: Input,
{

View File

@ -1579,7 +1579,7 @@ mod tests {
use crate::{
bolts::{
rands::StdRand,
tuples::{tuple_list, HasLen},
tuples::{tuple_list, HasConstLen},
},
corpus::{Corpus, InMemoryCorpus},
inputs::BytesInput,

View File

@ -9,6 +9,7 @@ use core::{
hash::Hasher,
slice::{from_raw_parts, from_raw_parts_mut},
};
use intervaltree::IntervalTree;
use num::Integer;
use serde::{Deserialize, Serialize};
@ -16,25 +17,38 @@ use crate::{
bolts::{
ownedref::{OwnedRefMut, OwnedSliceMut},
tuples::Named,
HasLen,
},
observers::Observer,
Error,
};
/// A [`MapObserver`] observes the static map, as oftentimes used for afl-like coverage information
pub trait MapObserver<T>: Named + serde::Serialize + serde::de::DeserializeOwned
pub trait MapObserver<T>: HasLen + Named + serde::Serialize + serde::de::DeserializeOwned
where
T: Integer + Default + Copy,
{
/// Get the map
fn map(&self) -> &[T];
/// Get the map if the observer can be represented with a slice
fn map(&self) -> Option<&[T]>;
/// Get the map (mutable)
fn map_mut(&mut self) -> &mut [T];
/// Get the map (mutable) if the observer can be represented with a slice
fn map_mut(&mut self) -> Option<&mut [T]>;
fn get(&self, idx: usize) -> &T {
&self
.map()
.expect("Cannot get a map that cannot be represented as slice")[idx]
}
fn get_mut(&mut self, idx: usize) -> &mut T {
&mut self
.map_mut()
.expect("Cannot get a map that cannot be represented as slice")[idx]
}
/// Get the number of usable entries in the map (all by default)
fn usable_count(&self) -> usize {
self.map().len()
self.len()
}
/// Count the set bytes in the map
@ -42,8 +56,8 @@ where
let initial = self.initial();
let cnt = self.usable_count();
let mut res = 0;
for x in self.map()[0..cnt].iter() {
if *x != initial {
for i in 0..cnt {
if *self.get(i) != initial {
res += 1;
}
}
@ -53,12 +67,14 @@ where
/// Compute the hash of the map
fn hash(&self) -> u64 {
let mut hasher = AHasher::new_with_keys(0, 0);
let ptr = self.map().as_ptr() as *const u8;
let map_size = self.map().len() / core::mem::size_of::<T>();
let slice = self
.map()
.expect("Cannot hash a map that cannot be represented as slice");
let ptr = slice.as_ptr() as *const u8;
let map_size = slice.len() / core::mem::size_of::<T>();
unsafe {
hasher.write(from_raw_parts(ptr, map_size));
}
hasher.finish()
}
@ -77,8 +93,8 @@ where
// Normal memset, see https://rust.godbolt.org/z/Trs5hv
let initial = self.initial();
let cnt = self.usable_count();
for x in self.map_mut()[0..cnt].iter_mut() {
*x = initial;
for i in 0..cnt {
*self.get_mut(i) = initial;
}
Ok(())
}
@ -120,18 +136,28 @@ where
}
}
impl<'a, T> HasLen for StdMapObserver<'a, T>
where
T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn len(&self) -> usize {
self.map.as_slice().len()
}
}
impl<'a, T> MapObserver<T> for StdMapObserver<'a, T>
where
T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn map(&self) -> &[T] {
self.map.as_slice()
fn map(&self) -> Option<&[T]> {
Some(self.map.as_slice())
}
#[inline]
fn map_mut(&mut self) -> &mut [T] {
self.map.as_mut_slice()
fn map_mut(&mut self) -> Option<&mut [T]> {
Some(self.map.as_mut_slice())
}
#[inline]
@ -225,6 +251,16 @@ where
}
}
impl<'a, T, const N: usize> HasLen for ConstMapObserver<'a, T, N>
where
T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn len(&self) -> usize {
N
}
}
impl<'a, T, const N: usize> MapObserver<T> for ConstMapObserver<'a, T, N>
where
T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
@ -235,13 +271,13 @@ where
}
#[inline]
fn map(&self) -> &[T] {
self.map.as_slice()
fn map(&self) -> Option<&[T]> {
Some(self.map.as_slice())
}
#[inline]
fn map_mut(&mut self) -> &mut [T] {
self.map.as_mut_slice()
fn map_mut(&mut self) -> Option<&mut [T]> {
Some(self.map.as_mut_slice())
}
#[inline]
@ -337,18 +373,28 @@ where
}
}
impl<'a, T> HasLen for VariableMapObserver<'a, T>
where
T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn len(&self) -> usize {
self.map.as_slice().len()
}
}
impl<'a, T> MapObserver<T> for VariableMapObserver<'a, T>
where
T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn map(&self) -> &[T] {
self.map.as_slice()
fn map(&self) -> Option<&[T]> {
Some(self.map.as_slice())
}
#[inline]
fn map_mut(&mut self) -> &mut [T] {
self.map.as_mut_slice()
fn map_mut(&mut self) -> Option<&mut [T]> {
Some(self.map.as_mut_slice())
}
#[inline]
@ -444,8 +490,8 @@ where
#[inline]
fn post_exec(&mut self, state: &mut S, input: &I) -> Result<(), Error> {
let cnt = self.usable_count();
for x in self.map_mut()[0..cnt].iter_mut() {
*x = COUNT_CLASS_LOOKUP[*x as usize];
for i in 0..cnt {
*self.get_mut(i) = COUNT_CLASS_LOOKUP[*self.get(i) as usize];
}
self.base.post_exec(state, input)
}
@ -461,17 +507,27 @@ where
}
}
impl<M> HasLen for HitcountsMapObserver<M>
where
M: MapObserver<u8>,
{
#[inline]
fn len(&self) -> usize {
self.base.len()
}
}
impl<M> MapObserver<u8> for HitcountsMapObserver<M>
where
M: MapObserver<u8>,
{
#[inline]
fn map(&self) -> &[u8] {
fn map(&self) -> Option<&[u8]> {
self.base.map()
}
#[inline]
fn map_mut(&mut self) -> &mut [u8] {
fn map_mut(&mut self) -> Option<&mut [u8]> {
self.base.map_mut()
}
@ -505,3 +561,196 @@ where
Self { base }
}
}
/// The Multi Map Observer merge different maps into one observer
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "T: serde::de::DeserializeOwned")]
#[allow(clippy::unsafe_derive_deserialize)]
pub struct MultiMapObserver<'a, T>
where
T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{
maps: Vec<OwnedSliceMut<'a, T>>,
intervals: IntervalTree<usize, usize>,
len: usize,
initial: T,
name: String,
}
impl<'a, I, S, T> Observer<I, S> for MultiMapObserver<'a, T>
where
T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
Self: MapObserver<T>,
{
#[inline]
fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
self.reset_map()
}
}
impl<'a, T> Named for MultiMapObserver<'a, T>
where
T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn name(&self) -> &str {
self.name.as_str()
}
}
impl<'a, T> HasLen for MultiMapObserver<'a, T>
where
T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn len(&self) -> usize {
self.len
}
}
impl<'a, T> MapObserver<T> for MultiMapObserver<'a, T>
where
T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn map(&self) -> Option<&[T]> {
None
}
#[inline]
fn map_mut(&mut self) -> Option<&mut [T]> {
None
}
#[inline]
fn get(&self, idx: usize) -> &T {
let elem = self.intervals.query_point(idx).next().unwrap();
let i = elem.value;
let j = idx - elem.range.start;
&self.maps[i].as_slice()[j]
}
#[inline]
fn get_mut(&mut self, idx: usize) -> &mut T {
let elem = self.intervals.query_point(idx).next().unwrap();
let i = elem.value;
let j = idx - elem.range.start;
&mut self.maps[i].as_mut_slice()[j]
}
#[inline]
fn initial(&self) -> T {
self.initial
}
#[inline]
fn initial_mut(&mut self) -> &mut T {
&mut self.initial
}
#[inline]
fn set_initial(&mut self, initial: T) {
self.initial = initial;
}
fn count_bytes(&self) -> u64 {
let initial = self.initial();
let mut res = 0;
for map in &self.maps {
for x in map.as_slice() {
if *x != initial {
res += 1;
}
}
}
res
}
fn hash(&self) -> u64 {
let mut hasher = AHasher::new_with_keys(0, 0);
for map in &self.maps {
let slice = map.as_slice();
let ptr = slice.as_ptr() as *const u8;
let map_size = slice.len() / core::mem::size_of::<T>();
unsafe {
hasher.write(from_raw_parts(ptr, map_size));
}
}
hasher.finish()
}
fn reset_map(&mut self) -> Result<(), Error> {
let initial = self.initial();
for map in &mut self.maps {
for x in map.as_mut_slice() {
*x = initial;
}
}
Ok(())
}
}
impl<'a, T> MultiMapObserver<'a, T>
where
T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{
/// Creates a new [`MultiMapObserver`]
#[must_use]
pub fn new(name: &'static str, maps: &'a mut [&'a mut [T]]) -> Self {
let mut idx = 0;
let mut v = 0;
let mut initial = T::default();
let mut builder = vec![];
let maps: Vec<_> = maps
.iter_mut()
.map(|x| {
if !x.is_empty() {
initial = x[0];
}
let l = x.len();
let r = (idx..(idx + l), v);
idx += l;
builder.push(r);
v += 1;
OwnedSliceMut::Ref(x)
})
.collect();
Self {
maps,
intervals: builder.into_iter().collect::<IntervalTree<usize, usize>>(),
len: idx,
name: name.to_string(),
initial,
}
}
/// Creates a new [`MultiMapObserver`] with an owned map
#[must_use]
pub fn new_owned(name: &'static str, maps: Vec<Vec<T>>) -> Self {
let mut idx = 0;
let mut v = 0;
let mut initial = T::default();
let mut builder = vec![];
let maps: Vec<_> = maps
.into_iter()
.map(|x| {
if !x.is_empty() {
initial = x[0];
}
let l = x.len();
let r = (idx..(idx + l), v);
idx += l;
builder.push(r);
v += 1;
OwnedSliceMut::Owned(x)
})
.collect();
Self {
maps,
intervals: builder.into_iter().collect::<IntervalTree<usize, usize>>(),
len: idx,
name: name.to_string(),
initial,
}
}
}

View File

@ -18,6 +18,7 @@ sancov_pcguard_hitcounts = []
sancov_pcguard_edges_ptr = []
sancov_pcguard_hitcounts_ptr = []
sancov_value_profile = []
sancov_8bit = []
sancov_cmplog = []
sancov_pcguard = ["sancov_pcguard_hitcounts"]
clippy = [] # Ignore compiler warnings during clippy

View File

@ -1,5 +1,8 @@
//! `libafl_targets` contains runtime code, injected in the target itself during compilation.
#[macro_use]
extern crate alloc;
include!(concat!(env!("OUT_DIR"), "/constants.rs"));
#[cfg(any(
@ -27,6 +30,11 @@ pub mod libfuzzer;
#[cfg(feature = "libfuzzer")]
pub use libfuzzer::*;
#[cfg(feature = "sancov_8bit")]
pub mod sancov_8bit;
#[cfg(feature = "sancov_8bit")]
pub use sancov_8bit::*;
pub mod coverage;
pub use coverage::*;

View File

@ -0,0 +1,14 @@
//! [`LLVM` `8-bi-counters`](https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards) runtime for `LibAFL`.
use alloc::vec::Vec;
use core::slice::from_raw_parts_mut;
pub static mut COUNTERS_MAPS: Vec<&'static mut [u8]> = Vec::new();
/// Initialize the sancov `8-bit-counters` - usually called by `llvm`.
///
/// # Safety
/// Set up our coverage maps.
#[no_mangle]
pub fn __sanitizer_cov_8bit_counters_init(start: *mut u8, stop: *mut u8) {
unsafe { COUNTERS_MAPS.push(from_raw_parts_mut(start, stop.offset_from(start) as usize)) }
}