Add bolts::math, make functions const, cleanup (#1444)

* Make some functions const

* fix isprint

* more const

* move integer_sqrt to bolts, use binary search, use u128 to handle extreme values

* Technically correct

* clippy

* u64 algo

* More test

* cumulative_distribution to in_place

* move calculate_cumulative_distribution_in_place to bolts

* clippy

* Move math stuff to bolts::math

* actually add math

* math?

* For some reason this fixes things, dunno

* fix builds?

* does that help?

* clippy ignores

* more clean clippy

* more cfg_attr
This commit is contained in:
Dominik Maier 2023-08-23 21:12:39 +02:00 committed by GitHub
parent d338b30c08
commit 454142c29e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 411 additions and 202 deletions

View File

@ -326,6 +326,10 @@ where
/// The Metadata for each testcase used in power schedules.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct SchedulerTestcaseMetadata {
/// Number of bits set in bitmap, updated in calibrate_case
bitmap_size: u64,

View File

@ -47,6 +47,10 @@ use crate::{
/// How an execution finished.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub enum ExitKind {
/// The run exited normally.
Ok,
@ -69,6 +73,10 @@ pub enum ExitKind {
/// How one of the diffing executions finished.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub enum DiffExitKind {
/// The run exited normally.
Ok,

View File

@ -222,6 +222,10 @@ where
/// A testcase metadata holding a list of indexes of a map
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct MapIndexesMetadata {
/// The list of indexes.
pub list: Vec<usize>,
@ -266,6 +270,10 @@ impl MapIndexesMetadata {
/// A testcase metadata holding a list of indexes of a map
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct MapNoveltiesMetadata {
/// A `list` of novelties.
pub list: Vec<usize>,
@ -300,6 +308,10 @@ impl MapNoveltiesMetadata {
/// The state of [`MapFeedback`]
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "T: DeserializeOwned")]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct MapFeedbackMetadata<T>
where
T: Default + Copy + 'static + Serialize,

View File

@ -109,7 +109,7 @@ impl From<BytesInput> for Vec<u8> {
impl BytesInput {
/// Creates a new bytes input using the given bytes
#[must_use]
pub fn new(bytes: Vec<u8>) -> Self {
pub const fn new(bytes: Vec<u8>) -> Self {
Self { bytes }
}
}

View File

@ -24,6 +24,10 @@ pub enum GeneralizedItem {
/// Metadata regarding the generalised content of an input
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct GeneralizedInputMetadata {
generalized: Vec<GeneralizedItem>,
}

View File

@ -72,6 +72,10 @@ where
/// The metadata used for `gramatron`
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct GramatronIdxMapMetadata {
/// The map containing a vec for each terminal
pub map: HashMap<usize, Vec<usize>>,

View File

@ -28,6 +28,10 @@ use crate::{
/// On the other hand, in the core fuzzing mode, the fuzzer chooses the best `swarms`, which was determined during the pilot fuzzing mode, to compute the probability to choose the operation operator.
/// With the current implementation we are always in the pacemaker fuzzing mode.
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct MOpt {
/// Random number generator
pub rand: StdRand,

View File

@ -24,6 +24,10 @@ use crate::{
/// The metadata placed in a [`crate::corpus::Testcase`] by a [`LoggerScheduledMutator`].
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct LogMutationMetadata {
/// A list of logs
pub list: Vec<String>,

View File

@ -34,8 +34,8 @@ use crate::{
};
/// A state metadata holding a list of tokens
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[allow(clippy::unsafe_derive_deserialize)]
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Tokens {
// We keep a vec and a set, set for faster deduplication, vec for access
tokens_vec: Vec<Vec<u8>>,
@ -1759,18 +1759,22 @@ impl TextType {
}
}
/// Returns `true` if the given `u8` char is
/// in the valid ascii range (`<= 0x7F`)
#[inline]
fn isascii(c: u8) -> bool {
const fn isascii(c: u8) -> bool {
c <= 0x7F
}
/// Returns `true` if the given `u8` char is
/// a valid printable character (between `0x20` and `0x7E`)
#[inline]
fn isprint(c: u8) -> bool {
(0x20..=0x7e).contains(&c)
const fn isprint(c: u8) -> bool {
c >= 0x20 && c <= 0x7E
}
#[inline]
fn strlen(buf: &[u8]) -> usize {
const fn strlen(buf: &[u8]) -> usize {
let mut count = 0;
while count < buf.len() {
if buf[count] == 0x0 {

View File

@ -8,7 +8,9 @@ use core::{
marker::PhantomData,
};
use libafl_bolts::{calculate_cumulative_sum_in_place, impl_serdeany, rands::Rand, Named};
use libafl_bolts::{
impl_serdeany, math::calculate_cumulative_distribution_in_place, rands::Rand, Named,
};
use serde::{Deserialize, Serialize};
pub use crate::mutators::{mutations::*, token_mutations::*};
@ -22,6 +24,10 @@ use crate::{
/// Metadata in the state, that controls the behavior of the [`TuneableScheduledMutator`] at runtime
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct TuneableScheduledMutatorMetadata {
/// The offsets of mutators to run, in order. Clear to fall back to random,
/// or use `mutation_probabilities`
@ -40,6 +46,8 @@ pub struct TuneableScheduledMutatorMetadata {
pub iter_probabilities_pow_cumulative: Vec<f32>,
}
impl_serdeany!(TuneableScheduledMutatorMetadata);
impl Default for TuneableScheduledMutatorMetadata {
fn default() -> Self {
Self {
@ -70,8 +78,6 @@ impl TuneableScheduledMutatorMetadata {
}
}
impl_serdeany!(TuneableScheduledMutatorMetadata);
/// A [`Mutator`] that schedules one of the embedded mutations on each call.
/// The index of the next mutation can be set.
pub struct TuneableScheduledMutator<I, MT, S>
@ -247,36 +253,6 @@ where
}
}
// Returns the cumulative distribution function for a discrete distribution.
fn calculate_cumulative_distribution(probabilities: Vec<f32>) -> Result<Vec<f32>, Error> {
if probabilities.is_empty() {
return Err(Error::illegal_argument("empty list of probabilities"));
}
if !probabilities.iter().all(|&p| (0.0..=1.0).contains(&p)) {
return Err(Error::illegal_argument(format!(
"invalid probability distribution: {probabilities:?}"
)));
}
let mut cumulative = probabilities;
calculate_cumulative_sum_in_place(&mut cumulative);
// The cumulative sum should be roughly equal to 1.
let last = cumulative.last_mut().unwrap();
if num_traits::abs(*last - 1.0_f32) > 1.0e-4 {
return Err(Error::illegal_argument(format!(
"sum of probabilities ({}) is not 1",
*last
)));
}
// Clamp the end of the vector to account for floating point errors.
*last = 1.0_f32;
Ok(cumulative)
}
impl<S> TuneableScheduledMutator<(), (), S>
where
S: HasRand + HasMetadata,
@ -317,7 +293,7 @@ where
/// Setting this function will unset everything previously set in `set_iters`.
pub fn set_iter_probabilities_pow(
state: &mut S,
iter_probabilities_pow: Vec<f32>,
mut iter_probabilities_pow: Vec<f32>,
) -> Result<(), Error> {
if iter_probabilities_pow.len() >= 32 {
return Err(Error::illegal_argument(
@ -328,8 +304,8 @@ where
metadata.iters = None;
// we precalculate the cumulative probability to be faster when sampling later.
metadata.iter_probabilities_pow_cumulative =
calculate_cumulative_distribution(iter_probabilities_pow)?;
calculate_cumulative_distribution_in_place(&mut iter_probabilities_pow)?;
metadata.iter_probabilities_pow_cumulative = iter_probabilities_pow;
Ok(())
}
@ -353,15 +329,15 @@ where
/// Setting the probabilities will remove the value set through `set_mutation_ids`.
pub fn set_mutation_probabilities(
state: &mut S,
mutation_probabilities: Vec<f32>,
mut mutation_probabilities: Vec<f32>,
) -> Result<(), Error> {
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
metadata.mutation_ids.clear();
metadata.next_id = 0.into();
// we precalculate the cumulative probability to be faster when sampling later.
metadata.mutation_probabilities_cumulative =
calculate_cumulative_distribution(mutation_probabilities)?;
calculate_cumulative_distribution_in_place(&mut mutation_probabilities)?;
metadata.mutation_probabilities_cumulative = mutation_probabilities;
Ok(())
}
@ -395,8 +371,7 @@ mod test {
use libafl_bolts::tuples::tuple_list;
use super::{
calculate_cumulative_distribution, BitFlipMutator, ByteDecMutator,
TuneableScheduledMutator, TuneableScheduledMutatorMetadata,
BitFlipMutator, ByteDecMutator, TuneableScheduledMutator, TuneableScheduledMutatorMetadata,
};
use crate::{
inputs::BytesInput,
@ -406,6 +381,11 @@ mod test {
#[test]
fn test_tuning() {
#[cfg(any(not(feature = "serdeany_autoreg"), miri))]
unsafe {
TuneableScheduledMutatorMetadata::register();
}
let mut state: NopState<BytesInput> = NopState::new();
let mutators = tuple_list!(
BitFlipMutator::new(),
@ -423,28 +403,12 @@ mod test {
}
#[test]
fn get_cdf_fails_with_invalid_probs() {
// Distribution has to sum up to 1.
assert!(calculate_cumulative_distribution(vec![]).is_err());
assert!(calculate_cumulative_distribution(vec![0.0]).is_err());
assert!(calculate_cumulative_distribution(vec![0.9]).is_err());
assert!(calculate_cumulative_distribution(vec![0.9, 0.9]).is_err());
assert!(calculate_cumulative_distribution(vec![f32::NAN]).is_err());
assert!(calculate_cumulative_distribution(vec![f32::INFINITY]).is_err());
assert!(calculate_cumulative_distribution(vec![f32::NEG_INFINITY]).is_err());
// Elements have to be between 0 and 1
assert!(calculate_cumulative_distribution(vec![-0.5, 0.5, 0.5]).is_err());
assert!(calculate_cumulative_distribution(vec![1.0]).is_ok());
assert!(calculate_cumulative_distribution(vec![0.0, 1.0]).is_ok());
assert!(calculate_cumulative_distribution(vec![0.0, 1.0, 0.0]).is_ok());
assert!(calculate_cumulative_distribution(vec![0.5, 0.5]).is_ok());
assert!(calculate_cumulative_distribution(vec![0.2; 5]).is_ok());
fn test_mutation_distribution() {
#[cfg(any(not(feature = "serdeany_autoreg"), miri))]
unsafe {
TuneableScheduledMutatorMetadata::register();
}
#[test]
fn test_mutation_distribution() {
let mut state: NopState<BytesInput> = NopState::new();
let mutators = tuple_list!(
BitFlipMutator::new(),

View File

@ -55,6 +55,10 @@ impl CmpValues {
/// A state metadata holding a list of values logged from comparisons
#[derive(Debug, Default, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct CmpValuesMetadata {
/// A `list` of values.
#[serde(skip)]
@ -562,6 +566,10 @@ where
/// A state metadata holding a list of values logged from comparisons. AFL++ RQ version.
#[derive(Debug, Default, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct AFLppCmpValuesMetadata {
/// The first map of AFLppCmpVals retrieved by running the un-mutated input
#[serde(skip)]

View File

@ -22,6 +22,10 @@ use crate::{
/// A testcase metadata holding a list of indexes of a map
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct AccountingIndexesMetadata {
/// The list of indexes.
pub list: Vec<usize>,
@ -73,6 +77,10 @@ impl AccountingIndexesMetadata {
/// A state metadata holding a map of favoreds testcases for each map entry
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct TopAccountingMetadata {
/// map index -> corpus index
pub map: HashMap<usize, CorpusId>,

View File

@ -3,6 +3,7 @@
use alloc::string::{String, ToString};
use core::marker::PhantomData;
use libafl_bolts::math::integer_sqrt;
use serde::{Deserialize, Serialize};
use crate::{
@ -14,16 +15,6 @@ use crate::{
Error,
};
fn integer_sqrt(val: u64) -> u64 {
let mut i = 0;
let mut r = 0;
while r <= val {
r = i * i;
i += 1;
}
i - 1
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Copy, Default)]
/// The state of the `EcoFuzz` scheduling algorithm
pub enum EcoState {
@ -38,6 +29,10 @@ pub enum EcoState {
/// The testcase Metadata for `EcoScheduler`
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct EcoTestcaseMetadata {
mutation_num: u64,
exec_num: u64,
@ -53,6 +48,10 @@ libafl_bolts::impl_serdeany!(EcoTestcaseMetadata);
/// The state Metadata for `EcoScheduler`
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct EcoMetadata {
state: EcoState,
initial_corpus_count: Option<usize>,
@ -283,7 +282,7 @@ where
let tcmeta = tc.metadata_mut::<EcoTestcaseMetadata>()?;
tcmeta.exec_num = exec_num;
tcmeta.serial = state.corpus().count() as u64 + 1;
tcmeta.serial = (state.corpus().count() as u64).saturating_add(1);
Ok(())
}

View File

@ -23,12 +23,20 @@ pub const DEFAULT_SKIP_NON_FAVORED_PROB: u64 = 95;
/// A testcase metadata saying if a testcase is favored
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct IsFavoredMetadata {}
libafl_bolts::impl_serdeany!(IsFavoredMetadata);
/// A state metadata holding a map of favoreds testcases for each map entry
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct TopRatedsMetadata {
/// map index -> corpus index
pub map: HashMap<usize, CorpusId>,

View File

@ -24,6 +24,10 @@ libafl_bolts::impl_serdeany!(SchedulerMetadata);
/// The metadata used for power schedules
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct SchedulerMetadata {
/// Powerschedule strategy
strat: Option<PowerSchedule>,

View File

@ -27,6 +27,10 @@ where
/// A state metadata holding a map of probability of corpus elements.
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct ProbabilityMetadata {
/// corpus index -> probability
pub map: HashMap<CorpusId, f64>,

View File

@ -18,6 +18,10 @@ use crate::{
};
#[derive(Default, Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
struct TuneableSchedulerMetadata {
next: Option<CorpusId>,
}

View File

@ -24,9 +24,12 @@ use crate::{
Error,
};
#[derive(Serialize, Deserialize, Clone, Debug)]
/// The Metadata for `WeightedScheduler`
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct WeightedScheduleMetadata {
/// The fuzzer execution spent in the current cycles
runs_in_current_cycle: usize,

View File

@ -7,7 +7,7 @@ use alloc::{
use core::{fmt::Debug, marker::PhantomData, time::Duration};
use hashbrown::HashSet;
use libafl_bolts::{current_time, AsIter, Named};
use libafl_bolts::{current_time, impl_serdeany, AsIter, Named};
use num_traits::Bounded;
use serde::{Deserialize, Serialize};
@ -26,15 +26,19 @@ use crate::{
Error,
};
libafl_bolts::impl_serdeany!(UnstableEntriesMetadata);
/// The metadata to keep unstable entries
/// In libafl, the stability is the number of the unstable entries divided by the size of the map
/// This is different from AFL++, which shows the number of the unstable entries divided by the number of filled entries.
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct UnstableEntriesMetadata {
unstable_entries: HashSet<usize>,
map_len: usize,
}
impl_serdeany!(UnstableEntriesMetadata);
impl UnstableEntriesMetadata {
#[must_use]

View File

@ -101,8 +101,12 @@ where
}
}
#[derive(Debug, Serialize, Deserialize)]
/// Store the taint and the input
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
pub struct TaintMetadata {
input_vec: Vec<u8>,
ranges: Vec<Range<usize>>,

View File

@ -16,6 +16,10 @@ use crate::{
};
/// Metadata used to store information about disk dump indexes for names
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct DumpToDiskMetadata {
last_corpus: Option<CorpusId>,

View File

@ -25,7 +25,7 @@ use crate::{
const MAX_GENERALIZED_LEN: usize = 8192;
fn increment_by_offset(_list: &[Option<u8>], idx: usize, off: u8) -> usize {
const fn increment_by_offset(_list: &[Option<u8>], idx: usize, off: u8) -> usize {
idx + 1 + off as usize
}

View File

@ -22,6 +22,10 @@ use crate::{
};
/// Metadata used to store information about disk sync time
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
#[derive(Serialize, Deserialize, Debug)]
pub struct SyncFromDiskMetadata {
/// The last time the sync was done
@ -191,6 +195,10 @@ where
}
/// Metadata used to store information about the last sent testcase with `SyncFromBrokerStage`
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
#[derive(Serialize, Deserialize, Debug)]
pub struct SyncFromBrokerMetadata {
/// The `CorpusId` of the last sent testcase

View File

@ -20,6 +20,10 @@ use crate::{
Error, Evaluator,
};
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
#[derive(Default, Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)]
struct TuneableMutationalStageMetadata {
iters: Option<u64>,

View File

@ -75,6 +75,21 @@ Welcome to `LibAFL`
#![allow(clippy::borrow_as_ptr)]
#![allow(clippy::borrow_deref_ref)]
/// We need some sort of "[`String`]" for errors in `no_alloc`...
/// We can only support `'static` without allocator, so let's do that.
#[cfg(not(feature = "alloc"))]
type String = &'static str;
/// We also need a non-allocating format...
/// This one simply returns the `fmt` string.
/// Good enough for simple errors, for anything else, use the `alloc` feature.
#[cfg(not(feature = "alloc"))]
macro_rules! format {
($fmt:literal) => {{
$fmt
}};
}
#[cfg(feature = "std")]
#[macro_use]
extern crate std;
@ -85,6 +100,55 @@ pub extern crate alloc;
#[cfg(feature = "ctor")]
#[doc(hidden)]
pub use ctor::ctor;
#[cfg(feature = "alloc")]
pub mod anymap;
#[cfg(feature = "std")]
pub mod build_id;
#[cfg(all(
any(feature = "cli", feature = "frida_cli", feature = "qemu_cli"),
feature = "std"
))]
pub mod cli;
#[cfg(feature = "gzip")]
pub mod compress;
#[cfg(feature = "std")]
pub mod core_affinity;
pub mod cpu;
#[cfg(feature = "std")]
pub mod fs;
#[cfg(feature = "alloc")]
pub mod llmp;
pub mod math;
#[cfg(all(feature = "std", unix))]
pub mod minibsod;
pub mod os;
#[cfg(feature = "alloc")]
pub mod ownedref;
pub mod rands;
#[cfg(feature = "alloc")]
pub mod serdeany;
pub mod shmem;
#[cfg(feature = "std")]
pub mod staterestore;
pub mod tuples;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::{iter::Iterator, time};
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
/// The client ID == the sender id.
#[repr(transparent)]
#[derive(
Debug, Default, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
)]
pub struct ClientId(pub u32);
#[cfg(feature = "std")]
use log::{Metadata, Record};
#[deprecated(
since = "0.11.0",
@ -112,21 +176,6 @@ use std::{env::VarError, io};
#[cfg(feature = "libafl_derive")]
pub use libafl_derive::SerdeAny;
/// We need some sort of "[`String`]" for errors in `no_alloc`...
/// We can only support `'static` without allocator, so let's do that.
#[cfg(not(feature = "alloc"))]
type String = &'static str;
/// We also need a non-allocating format...
/// This one simply returns the `fmt` string.
/// Good enough for simple errors, for anything else, use the `alloc` feature.
#[cfg(not(feature = "alloc"))]
macro_rules! format {
($fmt:literal) => {{
$fmt
}};
}
/// We need fixed names for many parts of this lib.
pub trait Named {
/// Provide the name of this element.
@ -467,55 +516,6 @@ pub unsafe extern "C" fn external_current_millis() -> u64 {
1000
}
#[cfg(feature = "alloc")]
pub mod anymap;
#[cfg(feature = "std")]
pub mod build_id;
#[cfg(all(
any(feature = "cli", feature = "frida_cli", feature = "qemu_cli"),
feature = "std"
))]
pub mod cli;
#[cfg(feature = "gzip")]
pub mod compress;
#[cfg(feature = "std")]
pub mod core_affinity;
pub mod cpu;
#[cfg(feature = "std")]
pub mod fs;
#[cfg(feature = "alloc")]
pub mod llmp;
#[cfg(all(feature = "std", unix))]
pub mod minibsod;
pub mod os;
#[cfg(feature = "alloc")]
pub mod ownedref;
pub mod rands;
#[cfg(feature = "alloc")]
pub mod serdeany;
pub mod shmem;
#[cfg(feature = "std")]
pub mod staterestore;
pub mod tuples;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::{iter::Iterator, ops::AddAssign, time};
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
/// The client ID == the sender id.
#[repr(transparent)]
#[derive(
Debug, Default, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
)]
pub struct ClientId(pub u32);
#[cfg(feature = "std")]
use log::{Metadata, Record};
/// Can be converted to a slice
pub trait AsSlice {
/// Type of the entries in this slice
@ -656,23 +656,6 @@ pub fn current_time() -> time::Duration {
time::Duration::from_millis(millis)
}
/// Given a u64 number, return a hashed number using this mixing function
/// This function is used to hash an address into a more random number (used in `libafl_frida`).
/// Mixing function: <http://mostlymangling.blogspot.com/2018/07/on-mixing-functions-in-fast-splittable.html>
#[inline]
#[must_use]
pub fn xxh3_rrmxmx_mixer(v: u64) -> u64 {
let tmp = (v >> 32) + ((v & 0xffffffff) << 32);
let bitflip = 0x1cad21f72c81017c ^ 0xdb979082e96dd4de;
let mut h64 = tmp ^ bitflip;
h64 = h64.rotate_left(49) & h64.rotate_left(24);
h64 = h64.wrapping_mul(0x9FB21C651E98DF25);
h64 ^= (h64 >> 35) + 8;
h64 = h64.wrapping_mul(0x9FB21C651E98DF25);
h64 ^= h64 >> 28;
h64
}
/// Gets current nanoseconds since [`UNIX_EPOCH`]
#[must_use]
#[inline]
@ -695,30 +678,6 @@ pub fn format_duration_hms(duration: &time::Duration) -> String {
format!("{}h-{}m-{}s", (secs / 60) / 60, (secs / 60) % 60, secs % 60)
}
/// Calculates the cumulative sum for a slice, in-place.
/// The values are useful for example for cumulative probabilities.
///
/// So, to give an example:
/// ```rust
/// # extern crate libafl_bolts;
/// use libafl_bolts::calculate_cumulative_sum_in_place;
///
/// let mut value = [2, 4, 1, 3];
/// calculate_cumulative_sum_in_place(&mut value);
/// assert_eq!(&[2, 6, 7, 10], &value);
/// ```
pub fn calculate_cumulative_sum_in_place<T>(mut_slice: &mut [T])
where
T: Default + AddAssign<T> + Copy,
{
let mut acc = T::default();
for val in mut_slice {
acc += *val;
*val = acc;
}
}
/// Stderr logger
#[cfg(feature = "std")]
pub static LIBAFL_STDERR_LOGGER: SimpleStderrLogger = SimpleStderrLogger::new();

View File

@ -521,6 +521,9 @@ fn next_shmem_size(max_alloc: usize) -> usize {
/// Initialize a new `llmp_page`. The size should be relative to
/// `llmp_page->messages`
///
/// # Safety
/// Will write to the raw SHM page header, should be safe for correct [`ShMem`] implementations
unsafe fn llmp_page_init<SHM: ShMem>(shmem: &mut SHM, sender_id: ClientId, allow_reinit: bool) {
#[cfg(feature = "llmp_debug")]
log::trace!("llmp_page_init: shmem {:?}", &shmem);
@ -551,6 +554,9 @@ unsafe fn llmp_page_init<SHM: ShMem>(shmem: &mut SHM, sender_id: ClientId, allow
}
/// Get the next pointer and make sure it's in the current page, and has enough space.
///
/// # Safety
/// Will dereference `last_msg`
#[inline]
unsafe fn llmp_next_msg_ptr_checked<SHM: ShMem>(
map: &mut LlmpSharedMap<SHM>,
@ -576,6 +582,9 @@ unsafe fn llmp_next_msg_ptr_checked<SHM: ShMem>(
/// Pointer to the message behind the last message
/// The messages are padded, so accesses will be aligned properly.
///
/// # Safety
/// Will dereference the `last_msg` ptr
#[inline]
#[allow(clippy::cast_ptr_alignment)]
unsafe fn _llmp_next_msg_ptr(last_msg: *const LlmpMsg) -> *mut LlmpMsg {

145
libafl_bolts/src/math.rs Normal file
View File

@ -0,0 +1,145 @@
//! Math-related functions that we commonly (or at least sometimes) need
use core::ops::AddAssign;
use crate::Error;
/// Returns the cumulative distribution function for a discrete distribution.
pub fn calculate_cumulative_distribution_in_place(probabilities: &mut [f32]) -> Result<(), Error> {
if probabilities.is_empty() {
return Err(Error::illegal_argument("empty list of probabilities"));
}
if !probabilities.iter().all(|&p| (0.0..=1.0).contains(&p)) {
return Err(Error::illegal_argument(format!(
"invalid probability distribution: {probabilities:?}"
)));
}
let cumulative = probabilities;
calculate_cumulative_sum_in_place(cumulative);
// The cumulative sum should be roughly equal to 1.
let last = cumulative.last_mut().unwrap();
let offset_to_1 = *last - 1.0_f32;
if offset_to_1.is_nan() || offset_to_1 > 1.0e-4 || -offset_to_1 > 1.0e-4 {
return Err(Error::illegal_argument(format!(
"sum of probabilities ({last}) is not 1"
)));
}
// Clamp the end of the vector to account for floating point errors.
*last = 1.0_f32;
Ok(())
}
/// Calculates the integer square root using binary search
/// Algorithm from
/// <https://en.wikipedia.org/wiki/Integer_square_root#Algorithm_using_binary_search>.
#[must_use]
pub const fn integer_sqrt(val: u64) -> u64 {
if val == u64::MAX {
return 2_u64.pow(32) - 1;
}
let mut ret = 0;
let mut i = val + 1;
let mut m;
while ret != i - 1 {
m = (ret + i) / 2;
if m.saturating_mul(m) <= val {
ret = m;
} else {
i = m;
}
}
ret
}
/// Given a u64 number, return a hashed number using this mixing function
/// This function is used to hash an address into a more random number (used in `libafl_frida`).
/// Mixing function: <http://mostlymangling.blogspot.com/2018/07/on-mixing-functions-in-fast-splittable.html>
#[inline]
#[must_use]
pub const fn xxh3_rrmxmx_mixer(v: u64) -> u64 {
let tmp = (v >> 32) + ((v & 0xffffffff) << 32);
let bitflip = 0x1cad21f72c81017c ^ 0xdb979082e96dd4de;
let mut h64 = tmp ^ bitflip;
h64 = h64.rotate_left(49) & h64.rotate_left(24);
h64 = h64.wrapping_mul(0x9FB21C651E98DF25);
h64 ^= (h64 >> 35) + 8;
h64 = h64.wrapping_mul(0x9FB21C651E98DF25);
h64 ^= h64 >> 28;
h64
}
/// Calculates the cumulative sum for a slice, in-place.
/// The values are useful for example for cumulative probabilities.
///
/// So, to give an example:
/// ```rust
/// # extern crate libafl_bolts;
/// use libafl_bolts::math::calculate_cumulative_sum_in_place;
///
/// let mut value = [2, 4, 1, 3];
/// calculate_cumulative_sum_in_place(&mut value);
/// assert_eq!(&[2, 6, 7, 10], &value);
/// ```
pub fn calculate_cumulative_sum_in_place<T>(mut_slice: &mut [T])
where
T: Default + AddAssign<T> + Copy,
{
let mut acc = T::default();
for val in mut_slice {
acc += *val;
*val = acc;
}
}
#[cfg(test)]
mod test {
use super::{calculate_cumulative_distribution_in_place, integer_sqrt};
#[test]
fn test_integer_sqrt() {
assert_eq!(0, integer_sqrt(0));
assert_eq!(1, integer_sqrt(1));
assert_eq!(2, integer_sqrt(4));
assert_eq!(10, integer_sqrt(120));
assert_eq!(11, integer_sqrt(121));
assert_eq!(11, integer_sqrt(128));
assert_eq!(2_u64.pow(16) - 1, integer_sqrt(u64::from(u32::MAX)));
assert_eq!(2_u64.pow(32) - 1, integer_sqrt(u64::MAX));
assert_eq!(2_u64.pow(32) - 1, integer_sqrt(u64::MAX - 1));
assert_eq!(128, integer_sqrt(128 * 128));
assert_eq!(128, integer_sqrt((128 * 128) + 1));
assert_eq!(128, integer_sqrt((128 * 128) + 127));
assert_eq!(128, integer_sqrt((128 * 128) + 127));
assert_eq!(999999, integer_sqrt((999999 * 999999) + 9));
}
#[test]
fn get_cdf_fails_with_invalid_probs() {
// Distribution has to sum up to 1.
assert!(calculate_cumulative_distribution_in_place(&mut []).is_err());
assert!(calculate_cumulative_distribution_in_place(&mut [0.0]).is_err());
assert!(calculate_cumulative_distribution_in_place(&mut [0.9]).is_err());
assert!(calculate_cumulative_distribution_in_place(&mut [0.9, 0.9]).is_err());
assert!(calculate_cumulative_distribution_in_place(&mut [f32::NAN]).is_err());
assert!(calculate_cumulative_distribution_in_place(&mut [f32::INFINITY]).is_err());
assert!(calculate_cumulative_distribution_in_place(&mut [f32::NEG_INFINITY]).is_err());
// Elements have to be between 0 and 1
assert!(calculate_cumulative_distribution_in_place(&mut [-0.5, 0.5, 0.5]).is_err());
assert!(calculate_cumulative_distribution_in_place(&mut [1.0]).is_ok());
assert!(calculate_cumulative_distribution_in_place(&mut [0.0, 1.0]).is_ok());
assert!(calculate_cumulative_distribution_in_place(&mut [0.0, 1.0, 0.0]).is_ok());
assert!(calculate_cumulative_distribution_in_place(&mut [0.5, 0.5]).is_ok());
assert!(calculate_cumulative_distribution_in_place(&mut [0.2; 5]).is_ok());
}
}

View File

@ -618,15 +618,6 @@ pub use serdeany_registry::*;
macro_rules! create_register {
($struct_type:ty) => {
const _: () = {
/// Manually register this type at a later point in time
///
/// # Safety
/// This may never be called concurrently as it dereferences the `RegistryBuilder` without acquiring a lock.
#[cfg(not(feature = "serdeany_autoreg"))]
pub unsafe fn register() {
$crate::serdeany::RegistryBuilder::register::<$struct_type>();
}
/// Automatically register this type
#[cfg(feature = "serdeany_autoreg")]
#[$crate::ctor]
@ -665,6 +656,18 @@ macro_rules! impl_serdeany {
}
}
#[cfg(any(not(feature = "serdeany_autoreg"), miri))]
impl< $( $lt $( : $clt $(+ $dlt )* )? ),+ > $struct_name < $( $lt ),+ > {
/// Manually register this type at a later point in time
///
/// # Safety
/// This may never be called concurrently as it dereferences the `RegistryBuilder` without acquiring a lock.
pub unsafe fn register() {
$crate::serdeany::RegistryBuilder::register::<$struct_name < $( $lt ),+ >>();
}
}
$(
$crate::create_register!($struct_name < $( $opt ),+ >);
)*
@ -690,6 +693,17 @@ macro_rules! impl_serdeany {
}
}
#[cfg(any(not(feature = "serdeany_autoreg"), miri))]
impl $struct_name {
/// Manually register this type at a later point in time
///
/// # Safety
/// This may never be called concurrently as it dereferences the `RegistryBuilder` without acquiring a lock.
pub unsafe fn register() {
$crate::serdeany::RegistryBuilder::register::<$struct_name>();
}
}
$crate::create_register!($struct_name);
};
}

View File

@ -141,13 +141,13 @@ impl ShMemId {
/// Returns `true` if this `ShMemId` has an empty backing slice.
/// If this is the case something went wrong, and this `ShMemId` may not be read from.
#[must_use]
pub fn is_empty(&self) -> bool {
pub const fn is_empty(&self) -> bool {
self.id[0] == 0
}
/// Get the id as a fixed-length slice
#[must_use]
pub fn as_array(&self) -> &[u8; 20] {
pub const fn as_array(&self) -> &[u8; 20] {
&self.id
}

View File

@ -6,7 +6,7 @@ use std::{cell::RefCell, marker::PhantomPinned, pin::Pin, rc::Rc};
use dynasmrt::DynasmLabelApi;
use dynasmrt::{dynasm, DynasmApi};
use frida_gum::{instruction_writer::InstructionWriter, stalker::StalkerOutput};
use libafl_bolts::xxh3_rrmxmx_mixer;
use libafl_bolts::math::xxh3_rrmxmx_mixer;
use rangemap::RangeMap;
use crate::helper::FridaRuntime;

View File

@ -12,6 +12,10 @@ use crate::{
GuestAddr,
};
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct QemuCmpsMapMetadata {
pub map: HashMap<u64, u64>,

View File

@ -19,6 +19,10 @@ static DRCOV_IDS: Mutex<Option<Vec<u64>>> = Mutex::new(None);
static DRCOV_MAP: Mutex<Option<HashMap<GuestAddr, u64>>> = Mutex::new(None);
static DRCOV_LENGTHS: Mutex<Option<HashMap<GuestAddr, GuestUsize>>> = Mutex::new(None);
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct QemuDrCovMetadata {
pub current_id: u64,

View File

@ -14,6 +14,10 @@ use crate::{
hooks::QemuHooks,
};
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)
)] // for SerdeAny
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct QemuEdgesMapMetadata {
pub map: HashMap<(GuestAddr, GuestAddr), u64>,