add generation based genetic testing

This commit is contained in:
Alwin Berger 2023-03-21 16:34:05 +01:00
parent c548c6bc09
commit c628afaa81
5 changed files with 212 additions and 13 deletions

View File

@ -17,6 +17,7 @@ feed_systemgraph = [ "systemstate" ]
feed_systemtrace = [ "systemstate" ] feed_systemtrace = [ "systemstate" ]
feed_longest = [ ] feed_longest = [ ]
feed_afl = [ ] feed_afl = [ ]
feed_genetic = [ ]
fuzz_int = [ ] fuzz_int = [ ]
[profile.release] [profile.release]

View File

@ -73,6 +73,18 @@ rule build_feedlongest_int:
shell: shell:
"cargo build --target-dir {output} {def_flags},feed_longest,fuzz_int" "cargo build --target-dir {output} {def_flags},feed_longest,fuzz_int"
rule build_feedgeneration:
output:
directory("bins/target_feedlongest_int")
shell:
"cargo build --target-dir {output} {def_flags},feed_genetic"
rule build_feedgeneration_int:
output:
directory("bins/target_feedlongest_int")
shell:
"cargo build --target-dir {output} {def_flags},feed_genetic"
rule run_bench: rule run_bench:
input: input:
"build/{target}.elf", "build/{target}.elf",
@ -114,11 +126,11 @@ rule run_bench:
rule run_showmap: rule run_showmap:
input: input:
"build/{target}.elf", "build/{target}.elf",
"bins/target_showmap", "bins/target_showmap_int",
"timedump/{fuzzer}/{target}.{num}.case" "mnt/timedump/{fuzzer}/{target}.{num}.case"
output: output:
"timedump/{fuzzer}/{target}.{num}.trace.ron", "mnt/timedump/{fuzzer}/{target}.{num}.trace.ron",
"timedump/{fuzzer}/{target}.{num}.case.time", "mnt/timedump/{fuzzer}/{target}.{num}.case.time",
run: run:
with open('target_symbols.csv') as csvfile: with open('target_symbols.csv') as csvfile:
reader = csv.DictReader(csvfile) reader = csv.DictReader(csvfile)
@ -176,7 +188,7 @@ rule all_bins:
rule all_periodic: rule all_periodic:
input: input:
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['random','afl','state'], target=['waters', 'tmr'],num=range(0,10)) expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['random'], target=['tmr'],num=range(0,10))
rule all_compare_afl_longest: rule all_compare_afl_longest:
input: input:
@ -184,4 +196,4 @@ rule all_compare_afl_longest:
rule all_micro: rule all_micro:
input: input:
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['afl_int','state_int','random_int','feedlongest_int'], target=['waters_int','micro_longint'],num=range(0,10)) expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['random_int'], target=['micro_longint'],num=range(0,10))

View File

@ -39,7 +39,7 @@ use rand::{SeedableRng, StdRng, Rng};
use crate::{ use crate::{
clock::{QemuClockObserver, ClockTimeFeedback, QemuClockIncreaseFeedback, IcHist}, clock::{QemuClockObserver, ClockTimeFeedback, QemuClockIncreaseFeedback, IcHist},
qemustate::QemuStateRestoreHelper, qemustate::QemuStateRestoreHelper,
systemstate::{helpers::QemuSystemStateHelper, observers::QemuSystemStateObserver, feedbacks::{DumpSystraceFeedback, NovelSystemStateFeedback}, graph::{SysMapFeedback, SysGraphFeedbackState, GraphMaximizerCorpusScheduler}, schedulers::LongestTraceScheduler}, worst::{TimeMaximizerCorpusScheduler, ExecTimeIncFeedback, TimeStateMaximizerCorpusScheduler}, systemstate::{helpers::QemuSystemStateHelper, observers::QemuSystemStateObserver, feedbacks::{DumpSystraceFeedback, NovelSystemStateFeedback}, graph::{SysMapFeedback, SysGraphFeedbackState, GraphMaximizerCorpusScheduler}, schedulers::{LongestTraceScheduler, GenerationScheduler}}, worst::{TimeMaximizerCorpusScheduler, ExecTimeIncFeedback, TimeStateMaximizerCorpusScheduler, AlwaysTrueFeedback},
}; };
pub static mut RNG_SEED: u64 = 1; pub static mut RNG_SEED: u64 = 1;
@ -245,6 +245,11 @@ pub fn fuzz() {
// Time feedback, this one does not need a feedback state // Time feedback, this one does not need a feedback state
ClockTimeFeedback::new_with_observer(&clock_time_observer) ClockTimeFeedback::new_with_observer(&clock_time_observer)
); );
#[cfg(feature = "feed_genetic")]
let mut feedback = feedback_or!(
feedback,
AlwaysTrueFeedback::new()
);
#[cfg(feature = "feed_afl")] #[cfg(feature = "feed_afl")]
let mut feedback = feedback_or!( let mut feedback = feedback_or!(
feedback, feedback,
@ -297,7 +302,7 @@ pub fn fuzz() {
}); });
// A minimization+queue policy to get testcasess from the corpus // A minimization+queue policy to get testcasess from the corpus
#[cfg(not(any(feature = "feed_afl",feature = "feed_systemgraph",feature = "feed_systemtrace")))] #[cfg(not(any(feature = "feed_afl",feature = "feed_systemgraph",feature = "feed_systemtrace", feature = "feed_genetic")))]
let scheduler = QueueScheduler::new(); let scheduler = QueueScheduler::new();
#[cfg(all(feature = "feed_afl",not(any(feature = "feed_systemgraph",feature = "feed_systemtrace"))))] #[cfg(all(feature = "feed_afl",not(any(feature = "feed_systemgraph",feature = "feed_systemtrace"))))]
let scheduler = TimeMaximizerCorpusScheduler::new(QueueScheduler::new()); let scheduler = TimeMaximizerCorpusScheduler::new(QueueScheduler::new());
@ -305,6 +310,8 @@ pub fn fuzz() {
let scheduler = LongestTraceScheduler::new(TimeStateMaximizerCorpusScheduler::new(QueueScheduler::new())); let scheduler = LongestTraceScheduler::new(TimeStateMaximizerCorpusScheduler::new(QueueScheduler::new()));
#[cfg(feature = "feed_systemgraph")] #[cfg(feature = "feed_systemgraph")]
let scheduler = GraphMaximizerCorpusScheduler::new(QueueScheduler::new()); let scheduler = GraphMaximizerCorpusScheduler::new(QueueScheduler::new());
#[cfg(feature = "feed_genetic")]
let scheduler = GenerationScheduler::new();
// A fuzzer with feedbacks and a corpus scheduler // A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);

View File

@ -2,7 +2,7 @@
//! with testcases only from a subset of the total corpus. //! with testcases only from a subset of the total corpus.
use core::{marker::PhantomData}; use core::{marker::PhantomData};
use std::cmp::max; use std::{cmp::{max, min}, mem::swap, borrow::BorrowMut};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -11,11 +11,13 @@ use libafl::{
corpus::{Corpus, Testcase}, corpus::{Corpus, Testcase},
inputs::UsesInput, inputs::UsesInput,
schedulers::{Scheduler, TestcaseScore, minimizer::DEFAULT_SKIP_NON_FAVORED_PROB }, schedulers::{Scheduler, TestcaseScore, minimizer::DEFAULT_SKIP_NON_FAVORED_PROB },
state::{HasCorpus, HasMetadata, HasRand, UsesState}, state::{HasCorpus, HasMetadata, HasRand, UsesState, State},
Error, SerdeAny, Error, SerdeAny, prelude::HasLen,
}; };
use crate::worst::MaxTimeFavFactor;
use super::FreeRTOSSystemStateMetadata; use super::FreeRTOSSystemStateMetadata;
/// A state metadata holding a map of favoreds testcases for each map entry /// A state metadata holding a map of favoreds testcases for each map entry
@ -132,3 +134,134 @@ where
} }
} }
} }
//==========================================================================================
/// A state metadata holding a map of favoreds testcases for each map entry
#[derive(Debug, Serialize, Deserialize, SerdeAny, Default)]
pub struct GeneticMetadata {
pub current_gen: Vec<(usize, f64)>,
pub current_cursor: usize,
pub next_gen: Vec<(usize, f64)>,
pub gen: usize
}
impl GeneticMetadata {
fn new(current_gen: Vec<(usize, f64)>, next_gen: Vec<(usize, f64)>) -> Self {
Self {current_gen, current_cursor: 0, next_gen, gen: 0}
}
}
#[derive(Debug, Clone)]
pub struct GenerationScheduler<S> {
phantom: PhantomData<S>,
gen_size: usize,
}
impl<S> UsesState for GenerationScheduler<S>
where
S: UsesInput,
{
type State = S;
}
impl<S> Scheduler for GenerationScheduler<S>
where
S: HasCorpus + HasMetadata,
S::Input: HasLen,
{
/// get first element in current gen,
/// if current_gen is empty, swap lists, sort by FavFactor, take top k and return first
fn next(&self, state: &mut Self::State) -> Result<usize, Error> {
let mut to_remove : Vec<(usize, f64)> = vec![];
let mut to_return : usize = 0;
let c = state.corpus().count();
let gm = state.metadata_mut().get_mut::<GeneticMetadata>().expect("Corpus Scheduler empty");
// println!("index: {} curr: {:?} next: {:?} gen: {} corp: {}", gm.current_cursor, gm.current_gen.len(), gm.next_gen.len(), gm.gen,
c);
match gm.current_gen.get(gm.current_cursor) {
Some(c) => {
gm.current_cursor+=1;
// println!("normal next: {}", (*c).0);
return Ok((*c).0)
},
None => {
swap(&mut to_remove, &mut gm.current_gen);
swap(&mut gm.next_gen, &mut gm.current_gen);
gm.current_gen.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
// gm.current_gen.reverse();
if gm.current_gen.len() == 0 {panic!("Corpus is empty");}
let d : Vec<(usize, f64)> = gm.current_gen.drain(min(gm.current_gen.len(), self.gen_size)..).collect();
to_remove.extend(d);
// move all indices to the left, since all other indices will be deleted
gm.current_gen.sort_by(|a,b| a.0.cmp(&(*b).0)); // in order of the corpus index
for i in 0..gm.current_gen.len() {
gm.current_gen[i] = (i, gm.current_gen[i].1);
}
to_return = gm.current_gen.get(0).unwrap().0;
gm.current_cursor=1;
gm.gen+=1;
}
};
// removing these elements will move all indices left by to_remove.len()
to_remove.sort_by(|x,y| x.0.cmp(&(*y).0));
to_remove.reverse();
for i in to_remove {
state.corpus_mut().remove(i.0).unwrap();
}
// println!("switch next: {to_return}");
return Ok(to_return);
}
/// Add the new input to the next generation
fn on_add(
&self,
state: &mut Self::State,
idx: usize
) -> Result<(), Error> {
// println!("On Add {idx}");
let mut tc = state.corpus_mut().get(idx).unwrap().borrow_mut().clone();
let ff = MaxTimeFavFactor::compute(&mut tc, state).unwrap();
if let Some(gm) = state.metadata_mut().get_mut::<GeneticMetadata>() {
gm.next_gen.push((idx,ff));
} else {
state.add_metadata(GeneticMetadata::new(vec![], vec![(idx,ff)]));
}
Ok(())
}
fn on_replace(
&self,
_state: &mut Self::State,
_idx: usize,
_prev: &Testcase<<Self::State as UsesInput>::Input>
) -> Result<(), Error> {
// println!("On Replace {_idx}");
Ok(())
}
fn on_remove(
&self,
state: &mut Self::State,
idx: usize,
_testcase: &Option<Testcase<<Self::State as UsesInput>::Input>>
) -> Result<(), Error> {
// println!("On Remove {idx}");
if let Some(gm) = state.metadata_mut().get_mut::<GeneticMetadata>() {
gm.next_gen = gm.next_gen.drain(..).into_iter().filter(|x| (*x).0 != idx).collect::<Vec<(usize, f64)>>();
gm.current_gen = gm.current_gen.drain(..).into_iter().filter(|x| (*x).0 != idx).collect::<Vec<(usize, f64)>>();
} else {
state.add_metadata(GeneticMetadata::new(vec![], vec![]));
}
Ok(())
}
}
impl<S> GenerationScheduler<S>
{
pub fn new() -> Self {
Self {
phantom: PhantomData,
gen_size: 100,
}
}
}

View File

@ -54,7 +54,7 @@ where
S: HasCorpus + HasMetadata, S: HasCorpus + HasMetadata,
S::Input: HasLen, S::Input: HasLen,
{ {
fn compute(entry: &mut Testcase<S::Input>, state: &S) -> Result<f64, Error> { fn compute(entry: &mut Testcase<<S as UsesInput>::Input>, state: &S) -> Result<f64, Error> {
// TODO maybe enforce entry.exec_time().is_some() // TODO maybe enforce entry.exec_time().is_some()
let et = entry.exec_time().expect("testcase.exec_time is needed for scheduler"); let et = entry.exec_time().expect("testcase.exec_time is needed for scheduler");
let tns : i64 = et.as_nanos().try_into().expect("failed to convert time"); let tns : i64 = et.as_nanos().try_into().expect("failed to convert time");
@ -333,3 +333,49 @@ where
Self {longest_time: 0, last_is_longest: false} Self {longest_time: 0, last_is_longest: false}
} }
} }
/// A Noop Feedback which records a list of all execution times
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AlwaysTrueFeedback
{
}
impl<S> Feedback<S> for AlwaysTrueFeedback
where
S: UsesInput + HasClientPerfMonitor,
{
#[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>,
{
Ok(true)
}
}
impl Named for AlwaysTrueFeedback
{
#[inline]
fn name(&self) -> &str {
"AlwaysTrueFeedback"
}
}
impl AlwaysTrueFeedback
where
{
/// Creates a new [`ExecTimeCollectorFeedback`]
#[must_use]
pub fn new() -> Self {
Self {
}
}
}