Skip to content

Commit 58fad2b

Browse files
R9295domenukk
andauthored
libafl-fuzz: separate frida build + cmplog debug (#2591)
* libafl-fuzz: separate frida build * cmplog debug * update * merge AflStatsStage move time_tracker stage to LibAFL * mandate track_hit_feedbacks feature for AflStatsStage * afl_stats do not hardcode TimeoutFeedback and CrashFeedback names * typo * typo * fix generics order * add verify timeouts stage * libafl: introduce set_timeout func to dynamically set timeouts for executor libafl-fuzz: add verify_timeout stage * add missing set_timeout implementations * libafl-fuzz: move set_timeout and timeout from Executor to HasTimeout * libafl-fuzz: add removed gitignore * remove timeout from libafl_nyx::Executor and move it to NyxHelper * clippy * fix HasTimeout for QemuExecutor * libafl-fuzz: remove observer handle usage in verify_timeouts misc: remove prelude imports * libafl-fuzz: fix foreign_sync_dirs option * fmt && clippy * clippy && fmt * missing doc * clippy * bruh * damned doc build * trait fix * impl HasTimeout for InProcessExecutor only if std * clippy * fix typo * fix nostd build * clippy * remove most HasTimeout implementations for now * typo * remove redundant import * misc * fmt * simplify trait bounds * add old AflStatsStage back and rename it to StatsStage * fix ci * make set_timeout and timeout of HasTimeout inline * fmt * add gitignore * serde_any fix * tmate * misc * remove tmate * test * coordinate between capture_timeout and verify_timeout * makefile * fix * fix * fmt * increase cmplog timeout * semantic * debug * debug * remove dbeug * only test libafl-fuzz on CI for now * better seed for cmplog? * remove preflight check for now * set Input type in forkserver * debug * tmate * fix capture_timeout * revert workflow * run only libafl-fuzz * remove pre-flight * re-enable fuzzers on CI * move capture_timeouts and verify_timeouts to main lib * run fmt * add note for verify timeouts * add note in verify timeouts stage * typo --------- Co-authored-by: Dominik Maier <domenukk@gmail.com>
1 parent 42b306a commit 58fad2b

File tree

26 files changed

+752
-216
lines changed

26 files changed

+752
-216
lines changed

fuzzers/baby/backtrace_baby_fuzzers/command_executor/src/main.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub fn main() {
8484
#[derive(Debug)]
8585
struct MyExecutor {
8686
shmem_id: ShMemId,
87+
timeout: Duration,
8788
}
8889

8990
impl CommandConfigurator<BytesInput> for MyExecutor {
@@ -106,11 +107,16 @@ pub fn main() {
106107
}
107108

108109
fn exec_timeout(&self) -> Duration {
109-
Duration::from_secs(5)
110+
self.timeout
111+
}
112+
fn exec_timeout_mut(&mut self) -> &mut Duration {
113+
&mut self.timeout
110114
}
111115
}
112116

113-
let mut executor = MyExecutor { shmem_id }.into_executor(tuple_list!(observer, bt_observer));
117+
let timeout = Duration::from_secs(5);
118+
let mut executor =
119+
MyExecutor { shmem_id, timeout }.into_executor(tuple_list!(observer, bt_observer));
114120

115121
// Generator of printable bytearrays of max size 32
116122
let mut generator = RandPrintablesGenerator::new(nonzero!(32));

fuzzers/binary_only/frida_libpng/harness_win.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#include <string.h>
55

66
extern "C" __declspec(dllexport) size_t
7-
LLVMFuzzerTestOneInput(const char *data, unsigned int len) {
7+
LLVMFuzzerTestOneInput(const char *data, unsigned int len) {
88
if (data[0] == 'b') {
99
if (data[1] == 'a') {
1010
if (data[2] == 'd') {

fuzzers/binary_only/qemu_launcher/src/instance.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ use libafl::{
2323
powersched::PowerSchedule, IndexesLenTimeMinimizerScheduler, PowerQueueScheduler,
2424
},
2525
stages::{
26-
calibrate::CalibrationStage, power::StdPowerMutationalStage, AflStatsStage, IfStage,
27-
ShadowTracingStage, StagesTuple, StdMutationalStage,
26+
calibrate::CalibrationStage, power::StdPowerMutationalStage, IfStage, ShadowTracingStage,
27+
StagesTuple, StatsStage, StdMutationalStage,
2828
},
2929
state::{HasCorpus, StdState, UsesState},
3030
Error, HasMetadata, NopFuzzer,
@@ -138,7 +138,7 @@ impl<M: Monitor> Instance<'_, M> {
138138

139139
let stats_stage = IfStage::new(
140140
|_, _, _, _| Ok(self.options.tui),
141-
tuple_list!(AflStatsStage::new(Duration::from_secs(5))),
141+
tuple_list!(StatsStage::new(Duration::from_secs(5))),
142142
);
143143

144144
// Feedback to rate the interestingness of an input

fuzzers/forkserver/libafl-fuzz/Makefile.toml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ FUZZER = '${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME}'
1212
LLVM_CONFIG = { value = "llvm-config-18", condition = { env_not_set = [
1313
"LLVM_CONFIG",
1414
] } }
15-
AFL_VERSION = "8b35dd49be5f846e945f6d6a9414623d195a99cb"
15+
AFL_VERSION = "78b7e14c73baacf1d88b3c03955e78f5080d17ba"
1616
AFL_DIR = { value = "${PROJECT_DIR}/AFLplusplus" }
1717
AFL_CC_PATH = { value = "${AFL_DIR}/afl-clang-fast" }
1818
CC = { value = "clang" }
@@ -25,12 +25,16 @@ if [ ! -d "$AFL_DIR" ]; then
2525
cd ${AFL_DIR}
2626
git checkout ${AFL_VERSION}
2727
LLVM_CONFIG=${LLVM_CONFIG} make
28+
fi
29+
'''
30+
[tasks.build_frida_mode]
31+
script_runner = '@shell'
32+
script = '''
33+
cd ${AFL_DIR}
2834
cd frida_mode
2935
LLVM_CONFIG=${LLVM_CONFIG} make
3036
cd ../..
31-
fi
3237
'''
33-
3438
[tasks.build_qemuafl]
3539
script_runner = "@shell"
3640
script = '''
@@ -77,7 +81,7 @@ script = '''
7781
AFL_PATH=${AFL_DIR} ${AFL_CC_PATH} ./test/test-instr.c -o ./test/out-instr
7882
7983
export LIBAFL_DEBUG_OUTPUT=1
80-
export AFL_CORES=1
84+
export AFL_CORES=0
8185
export AFL_STATS_INTERVAL=1
8286
8387
timeout 5 ${FUZZER} -i ./test/seeds -o ./test/output ./test/out-instr || true
@@ -109,7 +113,7 @@ script_runner = "@shell"
109113
script = '''
110114
# cmplog TODO: AFL_BENCH_UNTIL_CRASH=1 instead of timeout 15s
111115
AFL_LLVM_CMPLOG=1 AFL_PATH=${AFL_DIR} ${AFL_CC_PATH} ./test/test-cmplog.c -o ./test/out-cmplog
112-
AFL_CORES=1 timeout 5 ${FUZZER} -Z -l 3 -m 0 -V30 -i ./test/seeds_cmplog -o ./test/output-cmplog -c 0 ./test/out-cmplog || true
116+
LIBAFL_DEBUG_OUTPUT=1 AFL_CORES=0 timeout 10 ${FUZZER} -Z -l 3 -m 0 -V30 -i ./test/seeds_cmplog -o ./test/output-cmplog -c 0 ./test/out-cmplog || true
113117
test -n "$( ls ${PROJECT_DIR}/test/output-cmplog/fuzzer_main/hangs/id:0000* ${PROJECT_DIR}/test/output-cmplog/fuzzer_main/crashes/id:0000*)" || {
114118
echo "No crashes found"
115119
exit 1
@@ -123,7 +127,7 @@ script = '''
123127
${CC} -no-pie ./test/test-instr.c -o ./test/out-frida
124128
125129
export AFL_PATH=${AFL_DIR}
126-
export AFL_CORES=1
130+
export AFL_CORES=0
127131
export AFL_STATS_INTERVAL=1
128132
129133
timeout 5 ${FUZZER} -m 0 -O -i ./test/seeds_frida -o ./test/output-frida -- ./test/out-frida || true
@@ -162,7 +166,7 @@ test -n "$RUNTIME" -a -n "$RUNTIME_PERSISTENT" && {
162166
163167
unset AFL_FRIDA_PERSISTENT_ADDR
164168
'''
165-
dependencies = ["build_afl", "build_libafl_fuzz"]
169+
dependencies = ["build_afl", "build_frida_mode", "build_libafl_fuzz"]
166170

167171
[tasks.test_qemu]
168172
script_runner = "@shell"
@@ -171,7 +175,7 @@ ${CC} -pie -fPIE ./test/test-instr.c -o ./test/out-qemu
171175
${CC} -o ./test/out-qemu-cmpcov ./test/test-cmpcov.c
172176
173177
export AFL_PATH=${AFL_DIR}
174-
export AFL_CORES=1
178+
export AFL_CORES=0
175179
export AFL_STATS_INTERVAL=1
176180
177181
timeout 5 ${FUZZER} -m 0 -Q -i ./test/seeds_qemu -o ./test/output-qemu -- ./test/out-qemu || true
@@ -202,7 +206,7 @@ dependencies = ["build_afl", "build_qemuafl", "build_libafl_fuzz"]
202206
script_runner = "@shell"
203207
script = '''
204208
export AFL_PATH=${AFL_DIR}
205-
export AFL_CORES=1
209+
export AFL_CORES=0
206210
export AFL_STATS_INTERVAL=1
207211
208212
# TODO: test unicorn persistent mode once it's fixed on AFL++

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pub fn check_autoresume(fuzzer_dir: &Path, auto_resume: bool) -> Result<Flock<Fi
128128
}
129129
}
130130
if !auto_resume && last_update.saturating_sub(start_time) > OUTPUT_GRACE * 60 {
131-
return Err(Error::illegal_state("The job output directory already exists and contains results! use AFL_AUTORESUME=true or provide \"-\" for -i "));
131+
return Err(Error::illegal_state("The job output directory already exists and contains results! use AFL_AUTORESUME=1 or provide \"-\" for -i "));
132132
}
133133
}
134134
if !auto_resume {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{collections::HashMap, path::PathBuf, time::Duration};
22

3-
use libafl::Error;
3+
use libafl::{stages::afl_stats::AFL_FUZZER_STATS_UPDATE_INTERVAL_SECS, Error};
44
use libafl_bolts::core_affinity::Cores;
55

66
use crate::Opt;
@@ -73,6 +73,8 @@ pub fn parse_envs(opt: &mut Opt) -> Result<(), Error> {
7373
}
7474
if let Ok(res) = std::env::var("AFL_FUZZER_STATS_UPDATE_INTERVAL") {
7575
opt.stats_interval = res.parse()?;
76+
} else {
77+
opt.stats_interval = AFL_FUZZER_STATS_UPDATE_INTERVAL_SECS;
7678
}
7779
if let Ok(res) = std::env::var("AFL_BROKER_PORT") {
7880
opt.broker_port = Some(res.parse()?);

fuzzers/forkserver/libafl-fuzz/src/feedback/seed.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ where
6565
if !self.ignore_timeouts {
6666
if !self.ignore_seed_issues || self.exit_on_seed_issues {
6767
return Err(Error::invalid_corpus(
68-
"input led to a timeout; use AFL_IGNORE_SEED_ISSUES=true",
68+
"input led to a timeout; use AFL_IGNORE_SEED_ISSUES=1",
6969
));
7070
}
7171
return Ok(false);

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

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use std::{
22
borrow::Cow,
3+
cell::RefCell,
34
marker::PhantomData,
45
path::{Path, PathBuf},
6+
rc::Rc,
57
time::Duration,
68
};
79

@@ -13,7 +15,9 @@ use libafl::{
1315
},
1416
executors::forkserver::{ForkserverExecutor, ForkserverExecutorBuilder},
1517
feedback_and, feedback_or, feedback_or_fast,
16-
feedbacks::{ConstFeedback, CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
18+
feedbacks::{
19+
CaptureTimeoutFeedback, ConstFeedback, CrashFeedback, MaxMapFeedback, TimeFeedback,
20+
},
1721
fuzzer::StdFuzzer,
1822
inputs::{BytesInput, NopTargetBytesConverter},
1923
mutators::{havoc_mutations, tokens_mutations, AFLppRedQueen, StdScheduledMutator, Tokens},
@@ -23,8 +27,11 @@ use libafl::{
2327
IndexesLenTimeMinimizerScheduler, QueueScheduler, StdWeightedScheduler,
2428
},
2529
stages::{
26-
mutational::MultiMutationalStage, CalibrationStage, ColorizationStage, IfStage,
27-
StagesTuple, StdMutationalStage, StdPowerMutationalStage, SyncFromDiskStage,
30+
afl_stats::{AflStatsStage, CalibrationTime, FuzzTime, SyncTime},
31+
mutational::MultiMutationalStage,
32+
time_tracker::TimeTrackingStageWrapper,
33+
CalibrationStage, ColorizationStage, IfStage, StagesTuple, StdMutationalStage,
34+
StdPowerMutationalStage, SyncFromDiskStage, VerifyTimeoutsStage,
2835
},
2936
state::{
3037
HasCorpus, HasCurrentTestcase, HasExecutions, HasLastReportTime, HasStartTime, StdState,
@@ -46,7 +53,6 @@ use libafl_targets::{cmps::AFLppCmpLogMap, AFLppCmpLogObserver, AFLppCmplogTraci
4653
use serde::{Deserialize, Serialize};
4754

4855
use crate::{
49-
afl_stats::{AflStatsStage, CalibrationTime, FuzzTime, SyncTime},
5056
corpus::{set_corpus_filepath, set_solution_filepath},
5157
env_parser::AFL_DEFAULT_MAP_SIZE,
5258
executor::find_afl_binary,
@@ -55,7 +61,7 @@ use crate::{
5561
seed::SeedFeedback,
5662
},
5763
scheduler::SupportedSchedulers,
58-
stages::{mutational_stage::SupportedMutationalStages, time_tracker::TimeTrackingStageWrapper},
64+
stages::mutational_stage::SupportedMutationalStages,
5965
Opt, AFL_DEFAULT_INPUT_LEN_MAX, AFL_DEFAULT_INPUT_LEN_MIN, AFL_HARNESS_FILE_INPUT,
6066
SHMEM_ENV_VAR,
6167
};
@@ -109,17 +115,21 @@ where
109115
let mut tokens = Tokens::new();
110116
tokens = tokens.add_from_files(&opt.dicts)?;
111117

112-
let user_token_count = tokens.len();
113-
114118
// Create a AFLStatsStage;
115-
let afl_stats_stage = AflStatsStage::new(
116-
opt,
117-
fuzzer_dir.to_path_buf(),
118-
&edges_observer,
119-
user_token_count,
120-
!opt.no_autodict,
121-
core_id,
122-
);
119+
let afl_stats_stage = AflStatsStage::builder()
120+
.stats_file(fuzzer_dir.join("fuzzer_stats"))
121+
.plot_file(fuzzer_dir.join("plot_data"))
122+
.core_id(core_id)
123+
.report_interval(Duration::from_secs(opt.stats_interval))
124+
.map_observer(&edges_observer)
125+
.uses_autotokens(!opt.no_autodict)
126+
.tokens(&tokens)
127+
.banner(opt.executable.display().to_string())
128+
.version("0.13.2".to_string())
129+
.exec_timeout(opt.hang_timeout)
130+
.target_mode(fuzzer_target_mode(opt).to_string())
131+
.build()
132+
.expect("invariant; should never occur");
123133

124134
// Create an observation channel to keep track of the execution time.
125135
let time_observer = TimeObserver::new("time");
@@ -140,6 +150,20 @@ where
140150
opt,
141151
);
142152

153+
// We need to share this reference as [`VerifyTimeoutsStage`] will toggle this
154+
// value before re-running the alleged timeouts so we don't keep capturing timeouts infinitely.
155+
let enable_capture_timeouts = Rc::new(RefCell::new(false));
156+
let capture_timeout_feedback = CaptureTimeoutFeedback::new(Rc::clone(&enable_capture_timeouts));
157+
158+
// Like AFL++ we re-run all timeouts with double the timeout to assert that they are not false positives
159+
let timeout_verify_stage = IfStage::new(
160+
|_, _, _, _| Ok(!opt.ignore_timeouts),
161+
tuple_list!(VerifyTimeoutsStage::new(
162+
enable_capture_timeouts,
163+
Duration::from_millis(opt.hang_timeout),
164+
)),
165+
);
166+
143167
/*
144168
* Feedback to decide if the Input is "solution worthy".
145169
* We check if it's a crash or a timeout (if we are configured to consider timeouts)
@@ -153,7 +177,7 @@ where
153177
CrashFeedback::new(),
154178
feedback_and!(
155179
ConstFeedback::new(!opt.ignore_timeouts),
156-
TimeoutFeedback::new()
180+
capture_timeout_feedback,
157181
)
158182
),
159183
MaxMapFeedback::with_name("edges_objective", &edges_observer)
@@ -396,6 +420,7 @@ where
396420
calibration,
397421
cmplog,
398422
mutational_stage,
423+
timeout_verify_stage,
399424
afl_stats_stage,
400425
sync_stage
401426
);
@@ -411,7 +436,13 @@ where
411436
)?;
412437
} else {
413438
// The order of the stages matter!
414-
let mut stages = tuple_list!(calibration, mutational_stage, afl_stats_stage, sync_stage);
439+
let mut stages = tuple_list!(
440+
calibration,
441+
mutational_stage,
442+
timeout_verify_stage,
443+
afl_stats_stage,
444+
sync_stage
445+
);
415446

416447
// Run our fuzzer; NO CmpLog
417448
run_fuzzer_with_stages(

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
)]
6767

6868
use std::{collections::HashMap, path::PathBuf, time::Duration};
69-
mod afl_stats;
7069
mod env_parser;
7170
mod feedback;
7271
mod scheduler;
@@ -188,7 +187,7 @@ struct Opt {
188187
#[arg(short = 'c')]
189188
cmplog: Option<String>,
190189
/// sync to a foreign fuzzer queue directory (requires -M, can be specified up to 32 times)
191-
#[arg(short = 'F', num_args = 32)]
190+
#[arg(short = 'F')]
192191
foreign_sync_dirs: Vec<PathBuf>,
193192
/// fuzzer dictionary (see README.md)
194193
#[arg(short = 'x')]
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
pub mod mutational_stage;
2-
pub mod time_tracker;

0 commit comments

Comments
 (0)