Make Stats stage send stats again (#2830)

* Make Stats stage send stats again

* re-remove stats mod

* clp, fmt

* clip
This commit is contained in:
Dominik Maier 2025-01-13 15:06:26 +00:00 committed by GitHub
parent fd06e5ced0
commit 02566b33cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 103 additions and 35 deletions

View File

@ -18,7 +18,7 @@ use libafl::{
mutators::{havoc_mutations::havoc_mutations, scheduled::StdScheduledMutator}, mutators::{havoc_mutations::havoc_mutations, scheduled::StdScheduledMutator},
observers::StdMapObserver, observers::StdMapObserver,
schedulers::QueueScheduler, schedulers::QueueScheduler,
stages::mutational::StdMutationalStage, stages::{mutational::StdMutationalStage, AflStatsStage, CalibrationStage},
state::{HasCorpus, HasExecutions, StdState, UsesState}, state::{HasCorpus, HasExecutions, StdState, UsesState},
}; };
use libafl_bolts::{current_nanos, nonzero, rands::StdRand, tuples::tuple_list, AsSlice}; use libafl_bolts::{current_nanos, nonzero, rands::StdRand, tuples::tuple_list, AsSlice};
@ -86,6 +86,12 @@ pub fn main() {
// Feedback to rate the interestingness of an input // Feedback to rate the interestingness of an input
let mut feedback = MaxMapFeedback::new(&observer); let mut feedback = MaxMapFeedback::new(&observer);
let calibration_stage = CalibrationStage::new(&feedback);
let stats_stage = AflStatsStage::builder()
.map_observer(&observer)
.build()
.unwrap();
// A feedback to choose if an input is a solution or not // A feedback to choose if an input is a solution or not
let mut objective = feedback_and_fast!( let mut objective = feedback_and_fast!(
// Look for crashes. // Look for crashes.
@ -151,7 +157,11 @@ pub fn main() {
// Setup a mutational stage with a basic bytes mutator // Setup a mutational stage with a basic bytes mutator
let mutator = StdScheduledMutator::new(havoc_mutations()); let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdMutationalStage::new(mutator)); let mut stages = tuple_list!(
calibration_stage,
StdMutationalStage::new(mutator),
stats_stage
);
fuzzer fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)

View File

@ -39,6 +39,7 @@ default = [
"regex", "regex",
"serdeany_autoreg", "serdeany_autoreg",
"libafl_bolts/xxh3", "libafl_bolts/xxh3",
"tui_monitor",
] ]
document-features = ["dep:document-features"] document-features = ["dep:document-features"]
@ -200,6 +201,11 @@ nautilus = [
"regex", "regex",
] ]
[[example]]
name = "tui_mock"
path = "./examples/tui_mock/main.rs"
required-features = ["std", "tui_monitor"]
[build-dependencies] [build-dependencies]
rustversion = "1.0.17" rustversion = "1.0.17"

View File

@ -0,0 +1,20 @@
//! An example for TUI that uses the TUI without any real data.
//! This is mainly to fix the UI without having to run a real fuzzer.
use std::{thread::sleep, time::Duration};
use libafl::monitors::{tui::TuiMonitor, ClientStats, Monitor};
use libafl_bolts::ClientId;
pub fn main() {
let mut monitor = TuiMonitor::builder().build();
let client_stats = ClientStats {
corpus_size: 1024,
executions: 512,
..ClientStats::default()
};
monitor.display("Test", ClientId(0));
sleep(Duration::from_secs(10));
}

View File

@ -313,15 +313,15 @@ impl fmt::Display for UserStatsValue {
/// Prettifies float values for human-readable output /// Prettifies float values for human-readable output
fn prettify_float(value: f64) -> String { fn prettify_float(value: f64) -> String {
let (value, suffix) = match value { let (value, suffix) = match value {
value if value >= 1000000.0 => (value / 1000000.0, "M"), value if value >= 1_000_000.0 => (value / 1_000_000.0, "M"),
value if value >= 1000.0 => (value / 1000.0, "k"), value if value >= 1_000.0 => (value / 1_000.0, "k"),
value => (value, ""), value => (value, ""),
}; };
match value { match value {
value if value >= 1000000.0 => { value if value >= 1_000_000.0 => {
format!("{value:.2}{suffix}") format!("{value:.2}{suffix}")
} }
value if value >= 1000.0 => { value if value >= 1_000.0 => {
format!("{value:.1}{suffix}") format!("{value:.1}{suffix}")
} }
value if value >= 100.0 => { value if value >= 100.0 => {

View File

@ -24,9 +24,10 @@ use serde::{Deserialize, Serialize};
use crate::feedbacks::{CRASH_FEEDBACK_NAME, TIMEOUT_FEEDBACK_NAME}; use crate::feedbacks::{CRASH_FEEDBACK_NAME, TIMEOUT_FEEDBACK_NAME};
use crate::{ use crate::{
corpus::{Corpus, HasCurrentCorpusId, SchedulerTestcaseMetadata, Testcase}, corpus::{Corpus, HasCurrentCorpusId, SchedulerTestcaseMetadata, Testcase},
events::EventFirer, events::{Event, EventFirer},
executors::HasObservers, executors::HasObservers,
inputs::UsesInput, inputs::UsesInput,
monitors::{AggregatorOps, UserStats, UserStatsValue},
mutators::Tokens, mutators::Tokens,
observers::MapObserver, observers::MapObserver,
schedulers::{minimizer::IsFavoredMetadata, HasQueueCycles}, schedulers::{minimizer::IsFavoredMetadata, HasQueueCycles},
@ -35,6 +36,7 @@ use crate::{
std::string::ToString, std::string::ToString,
Error, HasMetadata, HasNamedMetadata, HasScheduler, Error, HasMetadata, HasNamedMetadata, HasScheduler,
}; };
/// AFL++'s default stats update interval /// AFL++'s default stats update interval
pub const AFL_FUZZER_STATS_UPDATE_INTERVAL_SECS: u64 = 60; pub const AFL_FUZZER_STATS_UPDATE_INTERVAL_SECS: u64 = 60;
@ -76,7 +78,7 @@ libafl_bolts::impl_serdeany!(FuzzTime);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct AflStatsStage<C, E, EM, O, S, Z> { pub struct AflStatsStage<C, E, EM, O, S, Z> {
map_observer_handle: Handle<C>, map_observer_handle: Handle<C>,
stats_file_path: PathBuf, stats_file_path: Option<PathBuf>,
plot_file_path: Option<PathBuf>, plot_file_path: Option<PathBuf>,
start_time: u64, start_time: u64,
// the number of testcases that have been fuzzed // the number of testcases that have been fuzzed
@ -238,7 +240,7 @@ pub struct AFLPlotData<'a> {
impl<C, E, EM, O, S, Z> Stage<E, EM, S, Z> for AflStatsStage<C, E, EM, O, S, Z> impl<C, E, EM, O, S, Z> Stage<E, EM, S, Z> for AflStatsStage<C, E, EM, O, S, Z>
where where
E: HasObservers, E: HasObservers,
EM: EventFirer, EM: EventFirer<State = S>,
Z: HasScheduler<<S::Corpus as Corpus>::Input, S>, Z: HasScheduler<<S::Corpus as Corpus>::Input, S>,
S: HasImported S: HasImported
+ HasCorpus + HasCorpus
@ -260,7 +262,7 @@ where
fuzzer: &mut Z, fuzzer: &mut Z,
executor: &mut E, executor: &mut E,
state: &mut S, state: &mut S,
_manager: &mut EM, manager: &mut EM,
) -> Result<(), Error> { ) -> Result<(), Error> {
let Some(corpus_idx) = state.current_corpus_id()? else { let Some(corpus_idx) = state.current_corpus_id()? else {
return Err(Error::illegal_state( return Err(Error::illegal_state(
@ -365,6 +367,7 @@ where
execs_since_crash: total_executions - self.execs_at_last_objective, execs_since_crash: total_executions - self.execs_at_last_objective,
exec_timeout: self.exec_timeout, exec_timeout: self.exec_timeout,
slowest_exec_ms: self.slowest_exec.as_millis(), slowest_exec_ms: self.slowest_exec.as_millis(),
// TODO: getting rss_mb may take some extra millis, so might make sense to make this optional
#[cfg(unix)] #[cfg(unix)]
peak_rss_mb: peak_rss_mb_child_processes()?, peak_rss_mb: peak_rss_mb_child_processes()?,
#[cfg(not(unix))] #[cfg(not(unix))]
@ -398,10 +401,36 @@ where
saved_crashes: &stats.saved_crashes, saved_crashes: &stats.saved_crashes,
execs_done: &stats.execs_done, execs_done: &stats.execs_done,
}; };
self.write_fuzzer_stats(&stats)?; self.maybe_write_fuzzer_stats(&stats)?;
if self.plot_file_path.is_some() { if self.plot_file_path.is_some() {
self.write_plot_data(&plot_data)?; self.write_plot_data(&plot_data)?;
} }
drop(testcase);
// We construct this simple json by hand to squeeze out some extra speed.
let json = format!(
"{{\
\"pending\":{},\
\"pending_fav\":{},\
\"own_finds:\"{},\
\"imported\":{}\
}}",
stats.pending_total, stats.pending_favs, stats.corpus_found, stats.corpus_imported
);
manager.fire(
state,
Event::UpdateUserStats {
name: Cow::Borrowed("AflStats"),
value: UserStats::new(
UserStatsValue::String(Cow::Owned(json)),
AggregatorOps::None,
),
phantom: PhantomData,
},
)?;
Ok(()) Ok(())
} }
@ -428,15 +457,17 @@ where
AflStatsStageBuilder::new() AflStatsStageBuilder::new()
} }
fn write_fuzzer_stats(&self, stats: &AFLFuzzerStats) -> Result<(), Error> { /// Writes a stats file, if a `stats_file_path` is set.
let tmp_file = self fn maybe_write_fuzzer_stats(&self, stats: &AFLFuzzerStats) -> Result<(), Error> {
.stats_file_path if let Some(stats_file_path) = &self.stats_file_path {
let tmp_file = stats_file_path
.parent() .parent()
.expect("fuzzer_stats file must have a parent!") .expect("fuzzer_stats file must have a parent!")
.join(".fuzzer_stats_tmp"); .join(".fuzzer_stats_tmp");
std::fs::write(&tmp_file, stats.to_string())?; std::fs::write(&tmp_file, stats.to_string())?;
_ = std::fs::copy(&tmp_file, &self.stats_file_path)?; _ = std::fs::copy(&tmp_file, stats_file_path)?;
std::fs::remove_file(tmp_file)?; std::fs::remove_file(tmp_file)?;
}
Ok(()) Ok(())
} }
@ -557,8 +588,8 @@ impl Display for AFLPlotData<'_> {
} }
} }
impl AFLPlotData<'_> { impl AFLPlotData<'_> {
fn get_header() -> String { fn header() -> &'static str {
"# relative_time, cycles_done, cur_item, corpus_count, pending_total, pending_favs, total_edges, saved_crashes, saved_hangs, max_depth, execs_per_sec, execs_done, edges_found".to_string() "# relative_time, cycles_done, cur_item, corpus_count, pending_total, pending_favs, total_edges, saved_crashes, saved_hangs, max_depth, execs_per_sec, execs_done, edges_found"
} }
} }
impl Display for AFLFuzzerStats<'_> { impl Display for AFLFuzzerStats<'_> {
@ -736,10 +767,10 @@ where
// check if it contains any data // check if it contains any data
let file = File::open(path)?; let file = File::open(path)?;
if BufReader::new(file).lines().next().is_none() { if BufReader::new(file).lines().next().is_none() {
std::fs::write(path, AFLPlotData::get_header())?; std::fs::write(path, AFLPlotData::header())?;
} }
} else { } else {
std::fs::write(path, AFLPlotData::get_header())?; std::fs::write(path, AFLPlotData::header())?;
} }
Ok(()) Ok(())
} }
@ -757,19 +788,17 @@ where
/// No `MapObserver` supplied to the builder /// No `MapObserver` supplied to the builder
/// No `stats_file_path` provieded /// No `stats_file_path` provieded
pub fn build(self) -> Result<AflStatsStage<C, E, EM, O, S, Z>, Error> { pub fn build(self) -> Result<AflStatsStage<C, E, EM, O, S, Z>, Error> {
if self.stats_file_path.is_none() {
return Err(Error::illegal_argument("Must set `stats_file_path`"));
}
let stats_file_path = self.stats_file_path.unwrap();
if self.map_observer_handle.is_none() { if self.map_observer_handle.is_none() {
return Err(Error::illegal_argument("Must set `map_observer`")); return Err(Error::illegal_argument("Must set `map_observer`"));
} }
if let Some(ref plot_file) = self.plot_file_path { if let Some(ref plot_file) = self.plot_file_path {
Self::create_plot_data_file(plot_file)?; Self::create_plot_data_file(plot_file)?;
} }
Self::create_fuzzer_stats_file(&stats_file_path)?; if let Some(stats_file_path) = &self.stats_file_path {
Self::create_fuzzer_stats_file(stats_file_path)?;
}
Ok(AflStatsStage { Ok(AflStatsStage {
stats_file_path, stats_file_path: self.stats_file_path,
plot_file_path: self.plot_file_path, plot_file_path: self.plot_file_path,
map_observer_handle: self.map_observer_handle.unwrap(), map_observer_handle: self.map_observer_handle.unwrap(),
start_time: current_time().as_secs(), start_time: current_time().as_secs(),

View File

@ -27,6 +27,14 @@ use crate::{
Error, HasMetadata, HasNamedMetadata, Error, HasMetadata, HasNamedMetadata,
}; };
/// AFL++'s `CAL_CYCLES_FAST` + 1
const CAL_STAGE_START: usize = 4;
/// AFL++'s `CAL_CYCLES` + 1
const CAL_STAGE_MAX: usize = 8;
/// Default name for `CalibrationStage`; derived from AFL++
pub const CALIBRATION_STAGE_NAME: &str = "calibration";
/// The metadata to keep unstable entries /// The metadata to keep unstable entries
/// Formula is same as AFL++: number of unstable entries divided by the number of filled entries. /// Formula is same as AFL++: number of unstable entries divided by the number of filled entries.
#[cfg_attr( #[cfg_attr(
@ -69,8 +77,6 @@ impl Default for UnstableEntriesMetadata {
} }
} }
/// Default name for `CalibrationStage`; derived from AFL++
pub const CALIBRATION_STAGE_NAME: &str = "calibration";
/// The calibration stage will measure the average exec time and the target's stability for this input. /// The calibration stage will measure the average exec time and the target's stability for this input.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CalibrationStage<C, E, O, OT, S> { pub struct CalibrationStage<C, E, O, OT, S> {
@ -83,9 +89,6 @@ pub struct CalibrationStage<C, E, O, OT, S> {
phantom: PhantomData<(E, O, OT, S)>, phantom: PhantomData<(E, O, OT, S)>,
} }
const CAL_STAGE_START: usize = 4; // AFL++'s CAL_CYCLES_FAST + 1
const CAL_STAGE_MAX: usize = 8; // AFL++'s CAL_CYCLES + 1
impl<C, E, EM, O, OT, S, Z> Stage<E, EM, S, Z> for CalibrationStage<C, E, O, OT, S> impl<C, E, EM, O, OT, S, Z> Stage<E, EM, S, Z> for CalibrationStage<C, E, O, OT, S>
where where
E: Executor<EM, <S::Corpus as Corpus>::Input, S, Z> + HasObservers<Observers = OT>, E: Executor<EM, <S::Corpus as Corpus>::Input, S, Z> + HasObservers<Observers = OT>,