use anyhow::Result;
use libafl::prelude::*;
use libafl_bolts::prelude::*;
use super::utils::{
choose, choose_iter, choose_term, choose_term_filtered_mut, choose_term_mut,
choose_term_path_filtered, find_term_mut, Choosable, TermConstraints,
};
use crate::algebra::atoms::Function;
use crate::algebra::signature::Signature;
use crate::algebra::{DYTerm, Subterms, Term, TermType};
use crate::fuzzer::term_zoo::TermZoo;
use crate::protocol::{ProtocolBehavior, ProtocolTypes};
use crate::put_registry::PutRegistry;
use crate::trace::Trace;
#[derive(Clone, Copy, Debug)]
pub struct MutationConfig {
pub fresh_zoo_after: u64,
pub max_trace_length: usize,
pub min_trace_length: usize,
pub term_constraints: TermConstraints,
pub with_bit_level: bool,
pub with_dy: bool,
}
impl Default for MutationConfig {
fn default() -> Self {
Self {
fresh_zoo_after: 100000,
max_trace_length: 15,
min_trace_length: 2,
term_constraints: TermConstraints::default(),
with_bit_level: true,
with_dy: true,
}
}
}
pub fn trace_mutations<'harness, S, PT: ProtocolTypes, PB>(
min_trace_length: usize,
max_trace_length: usize,
constraints: TermConstraints,
fresh_zoo_after: u64,
_with_bit_level: bool,
with_dy: bool,
signature: &'static Signature<PT>,
_put_registry: &'harness PutRegistry<PB>,
) -> tuple_list_type!(
RepeatMutator<S>,
SkipMutator<S>,
ReplaceReuseMutator<S>,
ReplaceMatchMutator<S, PT>,
RemoveAndLiftMutator<S>,
GenerateMutator<S, PT>,
SwapMutator<S>
)
where
S: HasCorpus + HasMetadata + HasMaxSize + HasRand,
PB: ProtocolBehavior,
{
tuple_list!(
RepeatMutator::new(max_trace_length, with_dy),
SkipMutator::new(min_trace_length, with_dy),
ReplaceReuseMutator::new(constraints, with_dy),
ReplaceMatchMutator::new(constraints, signature, with_dy),
RemoveAndLiftMutator::new(constraints, with_dy),
GenerateMutator::new(0, fresh_zoo_after, constraints, None, signature, with_dy), SwapMutator::new(constraints, with_dy),
)
}
pub struct SwapMutator<S>
where
S: HasRand,
{
constraints: TermConstraints,
phantom_s: std::marker::PhantomData<S>,
with_dy: bool,
}
impl<S> SwapMutator<S>
where
S: HasRand,
{
#[must_use]
pub const fn new(constraints: TermConstraints, with_dy: bool) -> Self {
Self {
constraints,
phantom_s: std::marker::PhantomData,
with_dy,
}
}
}
impl<S, PT: ProtocolTypes> Mutator<Trace<PT>, S> for SwapMutator<S>
where
S: HasRand,
{
fn mutate(
&mut self,
state: &mut S,
trace: &mut Trace<PT>,
_stage_idx: i32,
) -> Result<MutationResult, Error> {
if !self.with_dy {
return Ok(MutationResult::Skipped);
}
let _a = BytesInsertMutator;
let rand = state.rand_mut();
if let Some((term_a, trace_path_a)) = choose(trace, self.constraints, rand) {
if let Some(trace_path_b) = choose_term_path_filtered(
trace,
|term: &Term<PT>| term.get_type_shape() == term_a.get_type_shape(),
self.constraints,
rand,
) {
let term_a_cloned = term_a.clone();
if let Some(term_b_mut) = find_term_mut(trace, &trace_path_b) {
log::debug!(
"[Mutation] Mutate SwapMutator on terms\n{} and\n {}",
term_a_cloned,
term_b_mut
);
let term_b_cloned = term_b_mut.clone();
term_b_mut.mutate(term_a_cloned);
if let Some(trace_a_mut) = find_term_mut(trace, &trace_path_a) {
trace_a_mut.mutate(term_b_cloned);
}
return Ok(MutationResult::Mutated);
}
}
}
Ok(MutationResult::Skipped)
}
}
impl<S> Named for SwapMutator<S>
where
S: HasRand,
{
fn name(&self) -> &str {
std::any::type_name::<Self>()
}
}
pub struct RemoveAndLiftMutator<S>
where
S: HasRand,
{
constraints: TermConstraints,
phantom_s: std::marker::PhantomData<S>,
with_dy: bool,
}
impl<S> RemoveAndLiftMutator<S>
where
S: HasRand,
{
#[must_use]
pub const fn new(constraints: TermConstraints, with_dy: bool) -> Self {
Self {
constraints,
phantom_s: std::marker::PhantomData,
with_dy,
}
}
}
impl<S, PT: ProtocolTypes> Mutator<Trace<PT>, S> for RemoveAndLiftMutator<S>
where
S: HasRand,
{
fn mutate(
&mut self,
state: &mut S,
trace: &mut Trace<PT>,
_stage_idx: i32,
) -> Result<MutationResult, Error> {
if !self.with_dy {
return Ok(MutationResult::Skipped);
}
let rand = state.rand_mut();
let filter = |term: &Term<PT>| match &term.term {
DYTerm::Variable(_) => false,
DYTerm::Application(_, subterms) =>
{
subterms
.find_subterm(|subterm| match &subterm.term {
DYTerm::Variable(_) => false,
DYTerm::Application(_, grand_subterms) => {
grand_subterms.find_subterm_same_shape(subterm).is_some()
}
})
.is_some()
}
};
if let Some(to_mutate) = choose_term_filtered_mut(trace, filter, self.constraints, rand) {
log::debug!(
"[Mutation] Mutate RemoveAndLiftMutator on term\n{}",
to_mutate
);
match &mut to_mutate.term {
DYTerm::Variable(_) => Ok(MutationResult::Skipped),
DYTerm::Application(_, ref mut subterms) => {
if let Some(((subterm_index, _), grand_subterm)) = choose_iter(
subterms.filter_grand_subterms(|subterm, grand_subterm| {
subterm.get_type_shape() == grand_subterm.get_type_shape()
}),
rand,
) {
let grand_subterm_cloned = grand_subterm.clone();
subterms.push(grand_subterm_cloned);
subterms.swap_remove(subterm_index);
return Ok(MutationResult::Mutated);
}
Ok(MutationResult::Skipped)
}
}
} else {
Ok(MutationResult::Skipped)
}
}
}
impl<S> Named for RemoveAndLiftMutator<S>
where
S: HasRand,
{
fn name(&self) -> &str {
std::any::type_name::<Self>()
}
}
pub struct ReplaceMatchMutator<S, PT: ProtocolTypes>
where
S: HasRand,
{
constraints: TermConstraints,
signature: &'static Signature<PT>,
phantom_s: std::marker::PhantomData<S>,
with_dy: bool,
}
impl<S, PT: ProtocolTypes> ReplaceMatchMutator<S, PT>
where
S: HasRand,
{
#[must_use]
pub const fn new(
constraints: TermConstraints,
signature: &'static Signature<PT>,
with_dy: bool,
) -> Self {
Self {
constraints,
signature,
phantom_s: std::marker::PhantomData,
with_dy,
}
}
}
impl<S, PT: ProtocolTypes> Mutator<Trace<PT>, S> for ReplaceMatchMutator<S, PT>
where
S: HasRand,
{
fn mutate(
&mut self,
state: &mut S,
trace: &mut Trace<PT>,
_stage_idx: i32,
) -> Result<MutationResult, Error> {
if !self.with_dy {
return Ok(MutationResult::Skipped);
}
let rand = state.rand_mut();
if let Some(to_mutate) = choose_term_mut(trace, self.constraints, rand) {
log::debug!("[Mutation] ReplaceMatchMutator on term\n{}", to_mutate);
match &mut to_mutate.term {
DYTerm::Variable(variable) => {
if let Some((shape, dynamic_fn)) = self.signature.functions.choose_filtered(
|(shape, _)| variable.typ == shape.return_type && shape.is_constant(),
rand,
) {
to_mutate.mutate(Term::from(DYTerm::Application(
Function::new(shape.clone(), dynamic_fn.clone()),
Vec::new(),
)));
Ok(MutationResult::Mutated)
} else {
Ok(MutationResult::Skipped)
}
}
DYTerm::Application(func_mut, _) => {
if let Some((shape, dynamic_fn)) = self.signature.functions.choose_filtered(
|(shape, _)| {
func_mut.shape() != shape
&& func_mut.shape().return_type == shape.return_type
&& func_mut.shape().argument_types == shape.argument_types
},
rand,
) {
func_mut.change_function(shape.clone(), dynamic_fn.clone());
Ok(MutationResult::Mutated)
} else {
Ok(MutationResult::Skipped)
}
}
}
} else {
Ok(MutationResult::Skipped)
}
}
}
impl<S, PT: ProtocolTypes> Named for ReplaceMatchMutator<S, PT>
where
S: HasRand,
{
fn name(&self) -> &str {
std::any::type_name::<Self>()
}
}
pub struct ReplaceReuseMutator<S>
where
S: HasRand,
{
constraints: TermConstraints,
phantom_s: std::marker::PhantomData<S>,
with_dy: bool,
}
impl<S> ReplaceReuseMutator<S>
where
S: HasRand,
{
#[must_use]
pub const fn new(constraints: TermConstraints, with_dy: bool) -> Self {
Self {
constraints,
phantom_s: std::marker::PhantomData,
with_dy,
}
}
}
impl<S, PT: ProtocolTypes> Mutator<Trace<PT>, S> for ReplaceReuseMutator<S>
where
S: HasRand,
{
fn mutate(
&mut self,
state: &mut S,
trace: &mut Trace<PT>,
_stage_idx: i32,
) -> Result<MutationResult, Error> {
if !self.with_dy {
return Ok(MutationResult::Skipped);
}
let rand = state.rand_mut();
if let Some(replacement) = choose_term(trace, self.constraints, rand).cloned() {
if let Some(to_replace) = choose_term_filtered_mut(
trace,
|term: &Term<PT>| term.get_type_shape() == replacement.get_type_shape(),
self.constraints,
rand,
) {
log::debug!(
"[Mutation] Mutate ReplaceReuseMutator on terms\n {} and\n{}",
to_replace,
replacement
);
to_replace.mutate(replacement);
return Ok(MutationResult::Mutated);
}
}
Ok(MutationResult::Skipped)
}
}
impl<S> Named for ReplaceReuseMutator<S>
where
S: HasRand,
{
fn name(&self) -> &str {
std::any::type_name::<Self>()
}
}
pub struct SkipMutator<S>
where
S: HasRand,
{
min_trace_length: usize,
phantom_s: std::marker::PhantomData<S>,
with_dy: bool,
}
impl<S> SkipMutator<S>
where
S: HasRand,
{
#[must_use]
pub const fn new(min_trace_length: usize, with_dy: bool) -> Self {
Self {
min_trace_length,
phantom_s: std::marker::PhantomData,
with_dy,
}
}
}
impl<S, PT: ProtocolTypes> Mutator<Trace<PT>, S> for SkipMutator<S>
where
S: HasRand,
{
fn mutate(
&mut self,
state: &mut S,
trace: &mut Trace<PT>,
_stage_idx: i32,
) -> Result<MutationResult, Error> {
if !self.with_dy {
return Ok(MutationResult::Skipped);
}
let steps = &mut trace.steps;
let length = steps.len();
if length <= self.min_trace_length {
return Ok(MutationResult::Skipped);
}
if length == 0 {
return Ok(MutationResult::Skipped);
}
let remove_index = state.rand_mut().between(0, (length - 1) as u64) as usize;
log::debug!("[Mutation] Mutate SkipMutator on step {remove_index}");
steps.remove(remove_index);
Ok(MutationResult::Mutated)
}
}
impl<S> Named for SkipMutator<S>
where
S: HasRand,
{
fn name(&self) -> &str {
std::any::type_name::<Self>()
}
}
pub struct RepeatMutator<S>
where
S: HasRand,
{
max_trace_length: usize,
phantom_s: std::marker::PhantomData<S>,
with_dy: bool,
}
impl<S> RepeatMutator<S>
where
S: HasRand,
{
#[must_use]
pub const fn new(max_trace_length: usize, with_dy: bool) -> Self {
Self {
max_trace_length,
phantom_s: std::marker::PhantomData,
with_dy,
}
}
}
impl<S, PT: ProtocolTypes> Mutator<Trace<PT>, S> for RepeatMutator<S>
where
S: HasRand,
{
fn mutate(
&mut self,
state: &mut S,
trace: &mut Trace<PT>,
_stage_idx: i32,
) -> Result<MutationResult, Error> {
if !self.with_dy {
return Ok(MutationResult::Skipped);
}
let steps = &trace.steps;
let length = steps.len();
if length >= self.max_trace_length {
return Ok(MutationResult::Skipped);
}
if length == 0 {
return Ok(MutationResult::Skipped);
}
let insert_index = state.rand_mut().between(0, length as u64) as usize;
let step = state.rand_mut().choose(steps).clone();
log::debug!("[Mutation] Mutate RepeatMutator on step {insert_index}");
trace.steps.insert(insert_index, step);
Ok(MutationResult::Mutated)
}
}
impl<S> Named for RepeatMutator<S>
where
S: HasRand,
{
fn name(&self) -> &str {
std::any::type_name::<Self>()
}
}
pub struct GenerateMutator<S, PT: ProtocolTypes>
where
S: HasRand,
{
mutation_counter: u64,
refresh_zoo_after: u64,
constraints: TermConstraints,
zoo: Option<TermZoo<PT>>,
signature: &'static Signature<PT>,
phantom_s: std::marker::PhantomData<S>,
with_dy: bool,
}
impl<S, PT: ProtocolTypes> GenerateMutator<S, PT>
where
S: HasRand,
{
#[must_use]
pub const fn new(
mutation_counter: u64,
refresh_zoo_after: u64,
constraints: TermConstraints,
zoo: Option<TermZoo<PT>>,
signature: &'static Signature<PT>,
with_dy: bool,
) -> Self {
Self {
mutation_counter,
refresh_zoo_after,
constraints,
zoo,
signature,
phantom_s: std::marker::PhantomData,
with_dy,
}
}
}
impl<S, PT: ProtocolTypes> Mutator<Trace<PT>, S> for GenerateMutator<S, PT>
where
S: HasRand,
{
fn mutate(
&mut self,
state: &mut S,
trace: &mut Trace<PT>,
_stage_idx: i32,
) -> Result<MutationResult, Error> {
if !self.with_dy {
return Ok(MutationResult::Skipped);
}
let rand = state.rand_mut();
if let Some(to_mutate) = choose_term_mut(trace, self.constraints, rand) {
log::debug!("[Mutation] Mutate GenerateMutator on term\n{}", to_mutate);
self.mutation_counter += 1;
let zoo = if self.mutation_counter % self.refresh_zoo_after == 0 {
self.zoo.insert(TermZoo::generate(self.signature, rand))
} else {
self.zoo
.get_or_insert_with(|| TermZoo::generate(self.signature, rand))
};
if let Some(term) = zoo.choose_filtered(
|term| to_mutate.get_type_shape() == term.get_type_shape(),
rand,
) {
to_mutate.mutate(term.clone());
Ok(MutationResult::Mutated)
} else {
Ok(MutationResult::Skipped)
}
} else {
Ok(MutationResult::Skipped)
}
}
}
impl<S, PT: ProtocolTypes> Named for GenerateMutator<S, PT>
where
S: HasRand,
{
fn name(&self) -> &str {
std::any::type_name::<Self>()
}
}
#[cfg(test)]
mod tests {
use std::collections::{HashMap, HashSet};
use libafl::corpus::InMemoryCorpus;
use libafl::mutators::{MutationResult, Mutator};
use libafl::state::StdState;
use libafl_bolts::rands::{RomuDuoJrRand, StdRand};
use super::*;
use crate::agent::AgentName;
use crate::algebra::dynamic_function::DescribableFunction;
use crate::algebra::test_signature::{TestTrace, *};
use crate::algebra::DYTerm;
use crate::fuzzer::utils::{choose_term_path, TracePath};
use crate::trace::{Action, Step};
fn create_state(
) -> StdState<TestTrace, InMemoryCorpus<TestTrace>, RomuDuoJrRand, InMemoryCorpus<TestTrace>>
{
let rand = StdRand::with_seed(1235);
let corpus: InMemoryCorpus<TestTrace> = InMemoryCorpus::new();
StdState::new(rand, corpus, InMemoryCorpus::new(), &mut (), &mut ()).unwrap()
}
#[test_log::test]
fn test_repeat_mutator() {
let mut state = create_state();
let mut mutator = RepeatMutator::new(15, true);
fn check_is_encrypt12(step: &Step<TestProtocolTypes>) -> bool {
if let Action::Input(input) = &step.action {
if input.recipe.name() == fn_encrypt12.name() {
return true;
}
}
false
}
loop {
let mut trace = setup_simple_trace();
mutator.mutate(&mut state, &mut trace, 0).unwrap();
let length = trace.steps.len();
if let Some(last) = trace.steps.get(length - 1) {
if check_is_encrypt12(last) {
if let Some(step) = trace.steps.get(length - 2) {
if check_is_encrypt12(step) {
break;
}
}
}
}
}
}
#[test_log::test]
fn test_replace_match_mutator() {
let _server = AgentName::first();
let mut state = create_state();
let mut mutator =
ReplaceMatchMutator::new(TermConstraints::default(), &TEST_SIGNATURE, true);
loop {
let mut trace = setup_simple_trace();
mutator.mutate(&mut state, &mut trace, 0).unwrap();
if let Some(last) = trace.steps.iter().last() {
match &last.action {
Action::Input(input) => match &input.recipe.term {
DYTerm::Variable(_) => {}
DYTerm::Application(_, subterms) => {
if let Some(last_subterm) = subterms.iter().last() {
if last_subterm.name() == fn_seq_1.name() {
break;
}
}
}
},
Action::Output(_) => {}
}
}
}
}
#[test_log::test]
fn test_remove_lift_mutator() {
let mut state = create_state();
let _server = AgentName::first();
let mut mutator = RemoveAndLiftMutator::new(TermConstraints::default(), true);
fn sum_extension_appends(trace: &TestTrace) -> usize {
trace.count_functions_by_name(fn_client_extensions_append.name())
}
loop {
let mut trace = setup_simple_trace();
let before_mutation = sum_extension_appends(&trace);
let result = mutator.mutate(&mut state, &mut trace, 0).unwrap();
if result == MutationResult::Mutated {
let after_mutation = sum_extension_appends(&trace);
if after_mutation < before_mutation {
break;
}
}
}
}
#[test_log::test]
fn test_replace_reuse_mutator() {
let mut state = create_state();
let _server = AgentName::first();
let mut mutator = ReplaceReuseMutator::new(TermConstraints::default(), true);
fn count_client_hello(trace: &TestTrace) -> usize {
trace.count_functions_by_name(fn_client_hello.name())
}
fn count_finished(trace: &TestTrace) -> usize {
trace.count_functions_by_name(fn_finished.name())
}
loop {
let mut trace = setup_simple_trace();
let result = mutator.mutate(&mut state, &mut trace, 0).unwrap();
if result == MutationResult::Mutated {
let client_hellos = count_client_hello(&trace);
let finishes = count_finished(&trace);
if client_hellos == 2 && finishes == 0 {
break;
}
}
}
}
#[test_log::test]
fn test_skip_mutator() {
let mut state = create_state();
let _server = AgentName::first();
let mut mutator = SkipMutator::new(2, true);
loop {
let mut trace = setup_simple_trace();
let before_len = trace.steps.len();
mutator.mutate(&mut state, &mut trace, 0).unwrap();
if before_len - 1 == trace.steps.len() {
break;
}
}
}
#[test_log::test]
fn test_swap_mutator() {
let mut state = create_state();
let mut mutator = SwapMutator::new(TermConstraints::default(), true);
loop {
let mut trace = setup_simple_trace();
mutator.mutate(&mut state, &mut trace, 0).unwrap();
let is_first_not_ch = if let Some(first) = trace.steps.first() {
match &first.action {
Action::Input(input) => Some(input.recipe.name() != fn_client_hello.name()),
Action::Output(_) => None,
}
} else {
None
};
let is_next_not_fn_client_key_exchange = if let Some(next) = trace.steps.get(1) {
match &next.action {
Action::Input(input) => {
Some(input.recipe.name() != fn_client_key_exchange.name())
}
Action::Output(_) => None,
}
} else {
None
};
if let Some(first) = is_first_not_ch {
if let Some(second) = is_next_not_fn_client_key_exchange {
if first && second {
break;
}
}
}
}
}
#[test_log::test]
fn test_find_term() {
let mut rand = StdRand::with_seed(45);
let mut trace = setup_simple_trace();
let term_size = trace.count_functions();
let mut stats: HashSet<TracePath> = HashSet::new();
for _ in 0..10000 {
let path = choose_term_path(&trace, TermConstraints::default(), &mut rand).unwrap();
find_term_mut(&mut trace, &path).unwrap();
stats.insert(path);
}
assert_eq!(term_size, stats.len());
}
#[test_log::test]
fn test_reservoir_sample_randomness() {
fn std_deviation(data: &[u32]) -> Option<f32> {
fn mean(data: &[u32]) -> Option<f32> {
let sum = data.iter().sum::<u32>() as f32;
let count = data.len();
match count {
positive if positive > 0 => Some(sum / count as f32),
_ => None,
}
}
match (mean(data), data.len()) {
(Some(data_mean), count) if count > 0 => {
let variance = data
.iter()
.map(|value| {
let diff = data_mean - (*value as f32);
diff * diff
})
.sum::<f32>()
/ count as f32;
Some(variance.sqrt())
}
_ => None,
}
}
let trace = setup_simple_trace();
let term_size = trace.count_functions();
let mut rand = StdRand::with_seed(45);
let mut stats: HashMap<u32, u32> = HashMap::new();
for _ in 0..10000 {
let term = choose(&trace, TermConstraints::default(), &mut rand).unwrap();
let id = term.0.resistant_id();
let count: u32 = *stats.get(&id).unwrap_or(&0);
stats.insert(id, count + 1);
}
let std_dev =
std_deviation(stats.values().cloned().collect::<Vec<u32>>().as_slice()).unwrap();
assert!(std_dev < 30.0);
assert_eq!(term_size, stats.len());
}
}