Skip to content

Commit 462cf2d

Browse files
authored
Fix AFLStatsStage (#3371)
* refactor * type puzzle
1 parent 7e082e7 commit 462cf2d

File tree

10 files changed

+75
-45
lines changed

10 files changed

+75
-45
lines changed

crates/libafl/src/common/nautilus/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Nautilus 2.0 LibAFL Mutator
1+
# Nautilus 2.0 `LibAFL` Mutator
22

33
Nautilus is a coverage guided, grammar-based mutator. You can use it to improve your test coverage and find more bugs. By specifying the grammar of semi-valid inputs, Nautilus is able to perform complex mutation and to uncover more interesting test cases. Many of the ideas behind the original fuzzer are documented in a paper published at NDSS 2019.
44

@@ -7,7 +7,7 @@ Nautilus is a coverage guided, grammar-based mutator. You can use it to improve
77
</p>
88

99
Version 2.0 has added many improvements to this early prototype.
10-
Features from version 2.0 we support in LibAFL:
10+
Features from version 2.0 we support in `LibAFL`:
1111

1212
* Support for grammars specified in python
1313
* Support for non-context free grammars using python scripts to generate inputs from the structure

crates/libafl/src/common/nautilus/grammartec/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ impl Context {
123123
*self
124124
.names_to_nt_id
125125
.get(nt)
126-
.expect(&("no such nonterminal: ".to_owned() + nt))
126+
.unwrap_or_else(|| panic!("{}", ("no such nonterminal: ".to_owned() + nt)))
127127
}
128128

129129
#[must_use]

crates/libafl/src/common/nautilus/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! LibAFL version of the [`Nautilus`](https://github.com/nautilus-fuzz/nautilus) grammar fuzzer
1+
//! `LibAFL` version of the [`Nautilus`](https://github.com/nautilus-fuzz/nautilus) grammar fuzzer
22
#![doc = include_str!("README.md")]
33

44
#[allow(missing_docs)]

crates/libafl/src/common/nautilus/regex_mutator/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub struct RegexScript {
1111

1212
impl RegexScript {
1313
pub fn new<R: Rand>(rand: &mut R) -> Self {
14-
let len = if rand.next() % 256 == 0 {
14+
let len = if rand.next().is_multiple_of(256) {
1515
rand.next() % 0xffff
1616
} else {
1717
let len = 1 << (rand.next() % 8);

crates/libafl/src/executors/command.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ pub struct PTraceCommandConfigurator {
236236

237237
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
238238
impl CommandConfigurator<Pid> for PTraceCommandConfigurator {
239+
#[allow(unreachable_code)]
239240
fn spawn_child(&mut self, target_bytes: OwnedSlice<'_, u8>) -> Result<Pid, Error> {
240241
use nix::{
241242
sys::{

crates/libafl/src/stages/afl_stats.rs

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
//! Stage to compute and report AFL++ stats
22
use alloc::{borrow::Cow, string::String, vec::Vec};
3-
use core::{fmt::Display, marker::PhantomData, time::Duration};
3+
use core::{
4+
fmt::{Debug, Display},
5+
marker::PhantomData,
6+
time::Duration,
7+
};
48
use std::{
59
fs::{File, OpenOptions},
610
io::{BufRead, BufReader, Write},
@@ -14,7 +18,7 @@ use libafl_bolts::{
1418
Named,
1519
core_affinity::CoreId,
1620
current_time,
17-
tuples::{Handle, Handled, MatchNameRef},
21+
tuples::{Handle, Handled, MatchName},
1822
};
1923
use serde::{Deserialize, Serialize};
2024

@@ -25,6 +29,7 @@ use crate::{
2529
corpus::{Corpus, HasCurrentCorpusId, SchedulerTestcaseMetadata, Testcase},
2630
events::{Event, EventFirer, EventWithStats},
2731
executors::HasObservers,
32+
feedbacks::MapFeedbackMetadata,
2833
monitors::stats::{AggregatorOps, UserStats, UserStatsValue},
2934
mutators::Tokens,
3035
observers::MapObserver,
@@ -73,8 +78,9 @@ libafl_bolts::impl_serdeany!(FuzzTime);
7378
/// The [`AflStatsStage`] is a Stage that calculates and writes
7479
/// AFL++'s `fuzzer_stats` and `plot_data` information.
7580
#[derive(Debug, Clone)]
76-
pub struct AflStatsStage<C, E, EM, I, O, S, Z> {
81+
pub struct AflStatsStage<C, I, O> {
7782
map_observer_handle: Handle<C>,
83+
map_name: Cow<'static, str>,
7884
stats_file_path: Option<PathBuf>,
7985
plot_file_path: Option<PathBuf>,
8086
start_time: u64,
@@ -112,7 +118,7 @@ pub struct AflStatsStage<C, E, EM, I, O, S, Z> {
112118
autotokens_enabled: bool,
113119
/// The core we are bound to
114120
core_id: CoreId,
115-
phantom_data: PhantomData<(E, EM, I, O, S, Z)>,
121+
phantom: PhantomData<(I, O)>,
116122
}
117123

118124
/// AFL++'s `fuzzer_stats`
@@ -190,9 +196,9 @@ pub struct AflFuzzerStats<'a> {
190196
/// TODO
191197
cpu_affinity: usize,
192198
/// how many edges have been found
193-
edges_found: u64,
199+
edges_found: usize,
194200
/// Size of our edges map
195-
total_edges: u64,
201+
total_edges: usize,
196202
/// how many edges are non-deterministic
197203
var_byte_count: usize,
198204
/// TODO:
@@ -224,20 +230,21 @@ pub struct AFLPlotData<'a> {
224230
pending_total: &'a usize,
225231
pending_favs: &'a usize,
226232
/// Note: renamed `map_size` -> `total_edges` for consistency with `fuzzer_stats`
227-
total_edges: &'a u64,
233+
total_edges: &'a usize,
228234
saved_crashes: &'a u64,
229235
saved_hangs: &'a u64,
230236
max_depth: &'a u64,
231237
execs_per_sec: &'a u64,
232238
/// Note: renamed `total_execs` -> `execs_done` for consistency with `fuzzer_stats`
233239
execs_done: &'a u64,
234-
edges_found: &'a u64,
240+
edges_found: &'a usize,
235241
}
236242

237-
impl<C, E, EM, I, O, S, Z> Stage<E, EM, S, Z> for AflStatsStage<C, E, EM, I, O, S, Z>
243+
impl<C, E, EM, I, O, S, Z> Stage<E, EM, S, Z> for AflStatsStage<C, I, O>
238244
where
239245
C: AsRef<O> + Named,
240246
E: HasObservers,
247+
<E as HasObservers>::Observers: MatchName,
241248
EM: EventFirer<I, S>,
242249
Z: HasScheduler<I, S>,
243250
S: HasImported
@@ -248,8 +255,8 @@ where
248255
+ HasNamedMetadata
249256
+ Stoppable
250257
+ HasCurrentCorpusId,
251-
E::Observers: MatchNameRef,
252258
O: MapObserver,
259+
for<'de> <O as MapObserver>::Entry: Serialize + Deserialize<'de> + 'static + Debug,
253260
C: AsRef<O> + Named,
254261
Z::Scheduler: HasQueueCycles,
255262
{
@@ -299,13 +306,16 @@ where
299306
self.maybe_update_cycles(queue_cycles);
300307
self.maybe_update_cycles_wo_finds(queue_cycles);
301308

309+
let map_feedback = state
310+
.named_metadata_map()
311+
.get::<MapFeedbackMetadata<<O as MapObserver>::Entry>>(&self.map_name)
312+
.unwrap();
313+
314+
let filled_entries_in_map = map_feedback.num_covered_map_indexes;
302315
let observers = executor.observers();
303-
let map_observer = observers
304-
.get(&self.map_observer_handle)
305-
.ok_or_else(|| Error::key_not_found("invariant: MapObserver not found".to_string()))?
306-
.as_ref();
307-
let filled_entries_in_map = map_observer.count_bytes();
308-
let map_size = map_observer.usable_count();
316+
let map = observers[&self.map_observer_handle].as_ref();
317+
let map_size = map.usable_count();
318+
309319
// Since we do not calibrate when using `QueueScheduler`; we cannot calculate unstable entries.
310320
let unstable_entries_in_map = state
311321
.metadata_map()
@@ -370,7 +380,7 @@ where
370380
#[cfg(not(unix))]
371381
peak_rss_mb: 0, // TODO for Windows
372382
cpu_affinity: self.core_id.0,
373-
total_edges: map_size as u64,
383+
total_edges: map_size,
374384
edges_found: filled_entries_in_map,
375385
var_byte_count: unstable_entries_in_map,
376386
havoc_expansion: 0, // TODO
@@ -435,7 +445,7 @@ where
435445
}
436446
}
437447

438-
impl<C, E, EM, I, O, S, Z> Restartable<S> for AflStatsStage<C, E, EM, I, O, S, Z> {
448+
impl<C, I, O, S> Restartable<S> for AflStatsStage<C, I, O> {
439449
fn should_restart(&mut self, _state: &mut S) -> Result<bool, Error> {
440450
Ok(true)
441451
}
@@ -445,17 +455,13 @@ impl<C, E, EM, I, O, S, Z> Restartable<S> for AflStatsStage<C, E, EM, I, O, S, Z
445455
}
446456
}
447457

448-
impl<C, E, EM, I, O, S, Z> AflStatsStage<C, E, EM, I, O, S, Z>
458+
impl<C, I, O> AflStatsStage<C, I, O>
449459
where
450-
E: HasObservers,
451-
EM: EventFirer<I, S>,
452-
S: HasImported + HasMetadata + HasExecutions,
453-
C: AsRef<O> + Named,
454-
O: MapObserver,
460+
C: Named,
455461
{
456462
/// Builder for `AflStatsStage`
457463
#[must_use]
458-
pub fn builder() -> AflStatsStageBuilder<C, E, EM, I, O, S, Z> {
464+
pub fn builder() -> AflStatsStageBuilder<C, I, O> {
459465
AflStatsStageBuilder::new()
460466
}
461467

@@ -514,7 +520,10 @@ where
514520
}
515521

516522
#[cfg(feature = "track_hit_feedbacks")]
517-
fn maybe_update_last_crash(&mut self, testcase: &Testcase<I>, state: &S) {
523+
fn maybe_update_last_crash<S>(&mut self, testcase: &Testcase<I>, state: &S)
524+
where
525+
S: HasExecutions,
526+
{
518527
#[cfg(feature = "track_hit_feedbacks")]
519528
if testcase
520529
.hit_objectives()
@@ -526,7 +535,10 @@ where
526535
}
527536

528537
#[cfg(feature = "track_hit_feedbacks")]
529-
fn maybe_update_last_hang(&mut self, testcase: &Testcase<I>, state: &S) {
538+
fn maybe_update_last_hang<S>(&mut self, testcase: &Testcase<I>, state: &S)
539+
where
540+
S: HasExecutions,
541+
{
530542
if testcase
531543
.hit_objectives()
532544
.contains(&Cow::Borrowed(TIMEOUT_FEEDBACK_NAME))
@@ -558,7 +570,7 @@ where
558570

559571
#[expect(clippy::cast_precision_loss)]
560572
#[expect(clippy::unused_self)]
561-
fn calculate_stability(&self, unstable_entries: usize, filled_entries: u64) -> f64 {
573+
fn calculate_stability(&self, unstable_entries: usize, filled_entries: usize) -> f64 {
562574
((filled_entries as f64 - unstable_entries as f64) / filled_entries as f64) * 100.0
563575
}
564576
}
@@ -648,28 +660,25 @@ pub fn get_run_cmdline() -> Cow<'static, str> {
648660

649661
/// The Builder for `AflStatsStage`
650662
#[derive(Debug)]
651-
pub struct AflStatsStageBuilder<C, E, EM, I, O, S, Z> {
663+
pub struct AflStatsStageBuilder<C, I, O> {
652664
stats_file_path: Option<PathBuf>,
653665
plot_file_path: Option<PathBuf>,
654666
core_id: Option<CoreId>,
655667
map_observer_handle: Option<Handle<C>>,
668+
map_name: Option<String>,
656669
uses_autotokens: bool,
657670
report_interval: Duration,
658671
dict_count: usize,
659672
exec_timeout: u64,
660673
banner: String,
661674
version: String,
662675
target_mode: String,
663-
phantom_data: PhantomData<(E, EM, I, O, S, Z)>,
676+
phantom_data: PhantomData<(I, O)>,
664677
}
665678

666-
impl<C, E, EM, I, O, S, Z> AflStatsStageBuilder<C, E, EM, I, O, S, Z>
679+
impl<C, I, O> AflStatsStageBuilder<C, I, O>
667680
where
668-
C: AsRef<O> + Named,
669-
E: HasObservers,
670-
EM: EventFirer<I, S>,
671-
O: MapObserver,
672-
S: HasImported + HasMetadata + HasExecutions,
681+
C: Named,
673682
{
674683
fn new() -> Self {
675684
Self {
@@ -678,6 +687,7 @@ where
678687
plot_file_path: None,
679688
core_id: None,
680689
map_observer_handle: None,
690+
map_name: None,
681691
uses_autotokens: false,
682692
dict_count: 0,
683693
exec_timeout: 0,
@@ -718,6 +728,17 @@ where
718728
self.map_observer_handle = Some(map_observer.handle());
719729
self
720730
}
731+
732+
/// map name to check the filled count
733+
#[must_use]
734+
pub fn map_name<F>(mut self, map_feedback: &F) -> Self
735+
where
736+
F: Named,
737+
{
738+
self.map_name = Some(map_feedback.name().to_string());
739+
self
740+
}
741+
721742
/// If we use autotokens provided by the target
722743
#[must_use]
723744
pub fn uses_autotokens(mut self, uses: bool) -> Self {
@@ -782,10 +803,13 @@ where
782803
/// No `MapObserver` supplied to the builder
783804
/// No `stats_file_path` provieded
784805
#[allow(clippy::type_complexity)]
785-
pub fn build(self) -> Result<AflStatsStage<C, E, EM, I, O, S, Z>, Error> {
806+
pub fn build(self) -> Result<AflStatsStage<C, I, O>, Error> {
786807
if self.map_observer_handle.is_none() {
787808
return Err(Error::illegal_argument("Must set `map_observer`"));
788809
}
810+
let Some(map_name) = self.map_name else {
811+
return Err(Error::illegal_argument("Must set `map_name`"));
812+
};
789813
if let Some(ref plot_file) = self.plot_file_path {
790814
Self::create_plot_data_file(plot_file)?;
791815
}
@@ -794,6 +818,7 @@ where
794818
}
795819
Ok(AflStatsStage {
796820
stats_file_path: self.stats_file_path,
821+
map_name: Cow::Owned(map_name),
797822
plot_file_path: self.plot_file_path,
798823
map_observer_handle: self.map_observer_handle.unwrap(),
799824
start_time: current_time().as_secs(),
@@ -820,7 +845,7 @@ where
820845
dict_count: self.dict_count,
821846
core_id: self.core_id.unwrap_or(CoreId(0)),
822847
autotokens_enabled: self.uses_autotokens,
823-
phantom_data: PhantomData,
848+
phantom: PhantomData,
824849
})
825850
}
826851
}

crates/libafl_bolts/src/rands/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -567,9 +567,9 @@ pub mod pybind {
567567
#[pyclass(unsendable, name = "StdRand")]
568568
#[expect(clippy::unsafe_derive_deserialize)]
569569
#[derive(Serialize, Deserialize, Debug, Clone)]
570-
/// Python class for StdRand
570+
/// Python class for `StdRand`
571571
pub struct PythonStdRand {
572-
/// Rust wrapped StdRand object
572+
/// Rust wrapped `StdRand` object
573573
pub inner: StdRand,
574574
}
575575

fuzzers/baby/baby_fuzzer_custom_executor/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ pub fn main() {
8888
let calibration_stage = CalibrationStage::new(&feedback);
8989
let stats_stage = AflStatsStage::builder()
9090
.map_observer(&observer)
91+
.map_name(&feedback)
9192
.build()
9293
.unwrap();
9394

fuzzers/binary_only/qemu_launcher/src/instance.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,12 +188,14 @@ where
188188
|_, _, _, _| Ok(self.options.tui),
189189
tuple_list!(AflStatsStage::builder()
190190
.map_observer(&edges_observer)
191+
.map_name(&map_feedback)
191192
.build()?),
192193
);
193194
let stats_stage_cmplog = IfStage::new(
194195
|_, _, _, _| Ok(self.options.tui),
195196
tuple_list!(AflStatsStage::builder()
196197
.map_observer(&edges_observer)
198+
.map_name(&map_feedback)
197199
.build()?),
198200
);
199201

fuzzers/forkserver/libafl-fuzz/src/fuzzer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ define_run_client!(state, mgr, fuzzer_dir, core_id, opt, is_main_node, {
186186
.core_id(core_id)
187187
.report_interval(Duration::from_secs(opt.stats_interval))
188188
.map_observer(&edges_observer)
189+
.map_name(&map_feedback)
189190
.uses_autotokens(!opt.no_autodict)
190191
.tokens(&tokens)
191192
.banner(opt.executable.display().to_string())

0 commit comments

Comments
 (0)