Introduce libafl-fuzz (#2362)
* Introduce libafl-fuzz * fix corpus file path * simplify SeedFeedback * fix incorrect comment * add clap::ValueEnum to PowerSchedule as an optional derive if clap is enabled * UnixShMemProvider replaced with StdShMemProvider for libafl-fuzz * remove io_error_more feature constraint * libafl-fuzz: make Ok(()) unreachable in CentralizedLauncher * libafl-fuzz: make harness_input_stdin to harness_input_type with &'static * libafl-fuzz: move each feedback to it's own file * make run_fuzzer_with_stage into a function. use CachedOnDiskCorpus instead of OnDiskCorpus for Corpora remove utils file * remove unecessary clone * libafl-fuzz: cleanup AFLStatsStage * move peak_rss_mb to libafl_bolts * parse envs by hand * add sensible defaults for map size and broker port * fix test.sh and corpus_id padding * add Makefile.toml * libafl-fuzz update test suite * libafl-fuzz: clippy * rename peak_rss_mb to peak_rss_mb_children * remove minor version constraint for clap * libafl-fuzz: fix ELF check and instrumentation check in check_binary * libafl-fuzz: improve Makefile.toml * simplify fuzzer and cleanup typos * libafl-fuzz: load corpus entries in a multicore fashion * libafl-fuzz: create output dir if not exists (non-racey) * libafl-fuzz: add sequential scheduling support libafl-fuzz: add cmplog options libafl-fuzz: add test-cmplog.c to CI * rename peak_rss_mb_children to peak_rss_mb_child_processes * fix race condition in SyncFromDiskStage, add interval based checking and support for multiple directories. libafl-fuzz: add support for syncing with foreign fuzzers * update README * implement AflScheduler for QueueScheduler. Add queue_cycles field to AflScheduler * libafl-fuzz: remove dependecy on SchedulerMetadata for AflStatsStage * clippy * remove queue_cycles from AflScheduler into int's own trait. libafl-fuzz: simplify map observer tracking * clippy * libafl-fuzz: disable cmplog check in CI for now * add missing constraints for libafl_qemu executor * clippy * libafl-fuzz: improve Makefile libafl-fuzz: clippy * libafl-fuzz: misc * misc typos, beautify --------- Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com> Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
parent
e8de3591bf
commit
aa21815a97
1
.github/workflows/build_and_test.yml
vendored
1
.github/workflows/build_and_test.yml
vendored
@ -336,6 +336,7 @@ jobs:
|
|||||||
- ./fuzzers/backtrace_baby_fuzzers/forkserver_executor
|
- ./fuzzers/backtrace_baby_fuzzers/forkserver_executor
|
||||||
- ./fuzzers/backtrace_baby_fuzzers/c_code_with_inprocess_executor
|
- ./fuzzers/backtrace_baby_fuzzers/c_code_with_inprocess_executor
|
||||||
- ./fuzzers/backtrace_baby_fuzzers/rust_code_with_fork_executor
|
- ./fuzzers/backtrace_baby_fuzzers/rust_code_with_fork_executor
|
||||||
|
- ./fuzzers/libafl-fuzz
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
19
fuzzers/libafl-fuzz/Cargo.toml
Normal file
19
fuzzers/libafl-fuzz/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "libafl-fuzz"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.5", features = ["derive", "env"] }
|
||||||
|
env_logger = "0.11.3"
|
||||||
|
libafl = { path = "../../libafl", features = ["std", "derive", "track_hit_feedbacks", "clap", "errors_backtrace"]}
|
||||||
|
libafl_bolts = { path = "../../libafl_bolts", features = ["std", "errors_backtrace"]}
|
||||||
|
libafl_targets = { path = "../../libafl_targets"}
|
||||||
|
memmap2 = "0.9.4"
|
||||||
|
nix = {version = "0.29", features = ["fs"]}
|
||||||
|
regex = "1.10.5"
|
||||||
|
serde = { version = "1.0.117", features = ["derive"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["track_hit_feedbacks"]
|
||||||
|
track_hit_feedbacks = ["libafl/track_hit_feedbacks"]
|
66
fuzzers/libafl-fuzz/Makefile.toml
Normal file
66
fuzzers/libafl-fuzz/Makefile.toml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
[env]
|
||||||
|
PROJECT_DIR = { script = ["pwd"] }
|
||||||
|
CARGO_TARGET_DIR = { value = "${PROJECT_DIR}/target", condition = { env_not_set = ["CARGO_TARGET_DIR"] } }
|
||||||
|
PROFILE = { value = "release", condition = {env_not_set = ["PROFILE"]} }
|
||||||
|
PROFILE_DIR = {value = "release", condition = {env_not_set = ["PROFILE_DIR"] }}
|
||||||
|
FUZZER_NAME = 'libafl-fuzz'
|
||||||
|
FUZZER = '${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME}'
|
||||||
|
LLVM_CONFIG = {value = "llvm-config-18", condition = {env_not_set = ["LLVM_CONFIG"] }}
|
||||||
|
AFL_VERSION = "4.21c"
|
||||||
|
AFL_DIR_NAME= {value = "./AFLplusplus-${AFL_VERSION}"}
|
||||||
|
AFL_CC_PATH= {value = "${AFL_DIR_NAME}/afl-clang-fast"}
|
||||||
|
|
||||||
|
|
||||||
|
[tasks.build_afl]
|
||||||
|
script_runner="@shell"
|
||||||
|
script='''
|
||||||
|
if [ ! -d "$AFL_DIR_NAME" ]; then
|
||||||
|
if [ -f "v${AFL_VERSION}.zip" ]; then
|
||||||
|
rm v${AFL_VERSION}.zip
|
||||||
|
fi
|
||||||
|
wget https://github.com/AFLplusplus/AFLplusplus/archive/refs/tags/v${AFL_VERSION}.zip
|
||||||
|
unzip v${AFL_VERSION}.zip
|
||||||
|
cd ${AFL_DIR_NAME}
|
||||||
|
LLVM_CONFIG=${LLVM_CONFIG} make
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
|
'''
|
||||||
|
# Test
|
||||||
|
[tasks.test]
|
||||||
|
linux_alias = "test_unix"
|
||||||
|
mac_alias = "test_unix"
|
||||||
|
windows_alias = "unsupported"
|
||||||
|
|
||||||
|
[tasks.test_unix]
|
||||||
|
script_runner="@shell"
|
||||||
|
script='''
|
||||||
|
cargo build --profile ${PROFILE}
|
||||||
|
AFL_PATH=${AFL_DIR_NAME} ${AFL_CC_PATH} ./test/test-instr.c -o ./test/out-instr
|
||||||
|
AFL_CORES=1 AFL_STATS_INTERVAL=1 timeout 5 ${FUZZER} -i ./test/seeds -o ./test/output ./test/out-instr || true
|
||||||
|
test -n "$( ls ./test/output/fuzzer_main/queue/id:000002* 2>/dev/null )" || exit 1
|
||||||
|
test -n "$( ls ./test/output/fuzzer_main/fuzzer_stats 2>/dev/null )" || exit 1
|
||||||
|
test -n "$( ls ./test/output/fuzzer_main/plot_data 2>/dev/null )" || exit 1
|
||||||
|
test -d "./test/output/fuzzer_main/hangs" || exit 1
|
||||||
|
test -d "./test/output/fuzzer_main/crashes" || exit 1
|
||||||
|
|
||||||
|
# cmplog TODO: AFL_BENCH_UNTIL_CRASH=1 instead of timeout 15s
|
||||||
|
#AFL_LLVM_CMPLOG=1 AFL_PATH=${AFL_DIR_NAME} ${AFL_CC_PATH} ./test/test-cmplog.c -o ./test/out-cmplog
|
||||||
|
#AFL_CORES=1 timeout 15 ${FUZZER} -Z -l 3 -m 0 -V30 -i ./test/seeds_cmplog -o ./test/cmplog-output -c ./test/out-cmplog ./test/out-cmplog >>errors 2>&1
|
||||||
|
#test -n "$( ls ./test/cmplog-output/fuzzer_main/crashes/id:000000* ./test/cmplog-output/hangs/id:000000* 2>/dev/null )" || exit 1
|
||||||
|
'''
|
||||||
|
dependencies = ["build_afl"]
|
||||||
|
|
||||||
|
[tasks.clean]
|
||||||
|
linux_alias = "clean_unix"
|
||||||
|
mac_alias = "clean_unix"
|
||||||
|
windows_alias = "unsupported"
|
||||||
|
|
||||||
|
[tasks.clean_unix]
|
||||||
|
script_runner="@shell"
|
||||||
|
script='''
|
||||||
|
rm -rf AFLplusplus-${AFL_VERSION}
|
||||||
|
rm v${AFL_VERSION}.zip
|
||||||
|
rm -rf ./test/out-instr
|
||||||
|
rm -rf ./test/output
|
||||||
|
'''
|
70
fuzzers/libafl-fuzz/README.md
Normal file
70
fuzzers/libafl-fuzz/README.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
Rewrite of afl-fuzz in Rust.
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
- [x] AFL_HANG_TMOUT
|
||||||
|
- [x] AFL_NO_AUTODICT
|
||||||
|
- [x] AFL_MAP_SIZE
|
||||||
|
- [x] AFL_KILL_SIGNAL
|
||||||
|
- [x] AFL_BENCH_JUST_ONE
|
||||||
|
- [x] AFL_DEBUG_CHILD
|
||||||
|
- [x] AFL_PERSISTENT
|
||||||
|
- [x] AFL_IGNORE_TIMEOUTS
|
||||||
|
- [x] AFL_EXIT_ON_SEED_ISSUES
|
||||||
|
- [x] AFL_BENCH_UNTIL_CRASH
|
||||||
|
- [x] AFL_TMPDIR
|
||||||
|
- [x] AFL_CRASH_EXITCODE
|
||||||
|
- [x] AFL_TARGET_ENV
|
||||||
|
- [x] AFL_IGNORE_SEED_PROBLEMS (renamed to AFL_IGNORE_SEED_ISSUES)
|
||||||
|
- [x] AFL_CRASH_EXITCODE
|
||||||
|
- [x] AFL_INPUT_LEN_MIN
|
||||||
|
- [x] AFL_INPUT_LEN_MAX
|
||||||
|
- [x] AFL_CYCLE_SCHEDULES
|
||||||
|
- [x] AFL_CMPLOG_ONLY_NEW
|
||||||
|
- [x] AFL_PRELOAD
|
||||||
|
- [x] AFL_SKIP_BIN_CHECK
|
||||||
|
- [x] AFL_NO_STARTUP_CALIBRATION (this is default in libafl, not sure if this needs to be changed?)
|
||||||
|
- [x] AFL_FUZZER_STATS_UPDATE_INTERVAL
|
||||||
|
- [x] AFL_DEFER_FORKSRV
|
||||||
|
- [x] AFL_NO_WARN_INSTABILITY (we don't warn anyways, we should maybe?)
|
||||||
|
- [x] AFL_SYNC_TIME
|
||||||
|
- [ ] AFL_FINAL_SYNC
|
||||||
|
- [x] AFL_AUTORESUME
|
||||||
|
- [ ] AFL_CRASHING_SEEDS_AS_NEW_CRASH
|
||||||
|
- [ ] AFL_IGNORE_UNKNOWN_ENVS
|
||||||
|
- [ ] AFL_NO_UI
|
||||||
|
- [ ] AFL_PIZZA_MODE :)
|
||||||
|
- [ ] AFL_EXIT_WHEN_DONE
|
||||||
|
- [ ] AFL_EXIT_ON_TIME
|
||||||
|
- [ ] AFL_NO_AFFINITY
|
||||||
|
- [ ] AFL_FORKSERVER_KILL_SIGNAL
|
||||||
|
- [ ] AFL_EXPAND_HAVOC_NOW
|
||||||
|
- [ ] AFL_NO_FORKSRV
|
||||||
|
- [ ] AFL_FORKSRV_INIT_TMOUT
|
||||||
|
- [ ] AFL_TRY_AFFINITY
|
||||||
|
- [ ] AFL_FAST_CAL
|
||||||
|
- [ ] AFL_NO_CRASH_README
|
||||||
|
- [ ] AFL_KEEP_TIMEOUTS
|
||||||
|
- [ ] AFL_PERSISTENT_RECORD
|
||||||
|
- [ ] AFL_TESTCACHE_SIZE
|
||||||
|
- [ ] AFL_NO_ARITH
|
||||||
|
- [ ] AFL_DISABLE_TRIM
|
||||||
|
- [ ] AFL_MAX_DET_EXTRAS
|
||||||
|
- [ ] AFL_IGNORE_PROBLEMS
|
||||||
|
- [ ] AFL_IGNORE_PROBLEMS_COVERAGE
|
||||||
|
- [ ] AFL_STATSD_TAGS_FLAVOR
|
||||||
|
- [ ] AFL_STATSD
|
||||||
|
- [ ] AFL_STATSD_PORT
|
||||||
|
- [ ] AFL_STATSD_HOST
|
||||||
|
- [ ] AFL_IMPORT
|
||||||
|
- [x] AFL_IMPORT_FIRST (implicit)
|
||||||
|
- [ ] AFL_SHUFFLE_QUEUE
|
||||||
|
- [ ] AFL_CUSTOM_QEMU_BIN
|
||||||
|
- [ ] AFL_PATH
|
||||||
|
- [ ] AFL_CUSTOM_MUTATOR_LIBRARY
|
||||||
|
- [ ] AFL_CUSTOM_MUTATOR_ONLY
|
||||||
|
- [ ] AFL_PYTHON_MODULE
|
||||||
|
- [ ] AFL_DEBUG
|
||||||
|
- [ ] AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES
|
||||||
|
- [ ] AFL_DUMB_FORKSRV
|
||||||
|
- [ ] AFL_EARLY_FORKSERVER
|
||||||
|
- [ ] AFL_NO_SNAPSHOT
|
602
fuzzers/libafl-fuzz/src/afl_stats.rs
Normal file
602
fuzzers/libafl-fuzz/src/afl_stats.rs
Normal file
@ -0,0 +1,602 @@
|
|||||||
|
use core::{marker::PhantomData, time::Duration};
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
fmt::Display,
|
||||||
|
fs::{File, OpenOptions},
|
||||||
|
io::{BufRead, BufReader, Write},
|
||||||
|
path::PathBuf,
|
||||||
|
process,
|
||||||
|
};
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
corpus::{Corpus, HasCurrentCorpusId, HasTestcase, SchedulerTestcaseMetadata, Testcase},
|
||||||
|
events::EventFirer,
|
||||||
|
executors::HasObservers,
|
||||||
|
inputs::UsesInput,
|
||||||
|
observers::MapObserver,
|
||||||
|
schedulers::{minimizer::IsFavoredMetadata, HasQueueCycles, Scheduler},
|
||||||
|
stages::{calibrate::UnstableEntriesMetadata, Stage},
|
||||||
|
state::{HasCorpus, HasExecutions, HasImported, HasStartTime, Stoppable, UsesState},
|
||||||
|
Error, HasMetadata, HasNamedMetadata, HasScheduler,
|
||||||
|
};
|
||||||
|
use libafl_bolts::{
|
||||||
|
current_time,
|
||||||
|
os::peak_rss_mb_child_processes,
|
||||||
|
tuples::{Handle, Handled, MatchNameRef},
|
||||||
|
Named,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{fuzzer::fuzzer_target_mode, Opt};
|
||||||
|
|
||||||
|
/// The [`AflStatsStage`] is a Stage that calculates and writes
|
||||||
|
/// AFL++'s `fuzzer_stats` and `plot_data` information.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AflStatsStage<C, O, E, EM, Z> {
|
||||||
|
map_observer_handle: Handle<C>,
|
||||||
|
fuzzer_dir: PathBuf,
|
||||||
|
start_time: u64,
|
||||||
|
// the number of testcases that have been fuzzed
|
||||||
|
has_fuzzed_size: usize,
|
||||||
|
// the number of "favored" testcases
|
||||||
|
is_favored_size: usize,
|
||||||
|
// the last time that we report all stats
|
||||||
|
last_report_time: Duration,
|
||||||
|
// the interval at which we report all stats
|
||||||
|
stats_report_interval: Duration,
|
||||||
|
pid: u32,
|
||||||
|
slowest_exec: Duration,
|
||||||
|
max_depth: u64,
|
||||||
|
cycles_done: u64,
|
||||||
|
saved_crashes: u64,
|
||||||
|
saved_hangs: u64,
|
||||||
|
last_find: Duration,
|
||||||
|
last_hang: Duration,
|
||||||
|
last_crash: Duration,
|
||||||
|
exec_timeout: u64,
|
||||||
|
execs_at_last_objective: u64,
|
||||||
|
cycles_wo_finds: u64,
|
||||||
|
/// banner text (e.g., the target name)
|
||||||
|
afl_banner: Cow<'static, str>,
|
||||||
|
/// the version of libafl-fuzz used
|
||||||
|
afl_version: Cow<'static, str>,
|
||||||
|
/// default, persistent, qemu, unicorn, non-instrumented
|
||||||
|
target_mode: Cow<'static, str>,
|
||||||
|
/// full command line used for the fuzzing session
|
||||||
|
command_line: Cow<'static, str>,
|
||||||
|
phantom: PhantomData<(C, O, E, EM, Z)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AFLFuzzerStats<'a> {
|
||||||
|
/// unix time indicating the start time of afl-fuzz
|
||||||
|
start_time: u64,
|
||||||
|
/// unix time corresponding to the last interval
|
||||||
|
last_update: u64,
|
||||||
|
/// run time in seconds to the last update of this file
|
||||||
|
run_time: u64,
|
||||||
|
/// process id of the fuzzer process
|
||||||
|
fuzzer_pid: u32,
|
||||||
|
/// queue cycles completed so far
|
||||||
|
cycles_done: u64,
|
||||||
|
/// number of queue cycles without any new paths found
|
||||||
|
cycles_wo_find: u64,
|
||||||
|
/// longest time in seconds no new path was found
|
||||||
|
time_wo_finds: u64,
|
||||||
|
/// TODO
|
||||||
|
fuzz_time: u64,
|
||||||
|
/// TODO
|
||||||
|
calibration_time: u64,
|
||||||
|
/// TODO
|
||||||
|
sync_time: u64,
|
||||||
|
/// TODO
|
||||||
|
trim_time: u64,
|
||||||
|
/// number of fuzzer executions attempted (what does attempted mean here?)
|
||||||
|
execs_done: u64,
|
||||||
|
/// overall number of execs per second
|
||||||
|
execs_per_sec: u64,
|
||||||
|
/// TODO
|
||||||
|
execs_ps_last_min: u64,
|
||||||
|
/// total number of entries in the queue
|
||||||
|
corpus_count: usize,
|
||||||
|
/// number of queue entries that are favored
|
||||||
|
corpus_favored: usize,
|
||||||
|
/// number of entries discovered through local fuzzing
|
||||||
|
corpus_found: usize,
|
||||||
|
/// number of entries imported from other instances
|
||||||
|
corpus_imported: usize,
|
||||||
|
/// number of levels in the generated data set
|
||||||
|
max_depth: u64,
|
||||||
|
/// currently processed entry number
|
||||||
|
cur_item: usize,
|
||||||
|
/// number of favored entries still waiting to be fuzzed
|
||||||
|
pending_favs: usize,
|
||||||
|
/// number of all entries waiting to be fuzzed
|
||||||
|
pending_total: usize,
|
||||||
|
/// number of test cases showing variable behavior
|
||||||
|
corpus_variable: u64,
|
||||||
|
/// percentage of bitmap bytes that behave consistently
|
||||||
|
stability: f64,
|
||||||
|
/// percentage of edge coverage found in the map so far,
|
||||||
|
bitmap_cvg: f64,
|
||||||
|
/// number of unique crashes recorded
|
||||||
|
saved_crashes: u64,
|
||||||
|
/// number of unique hangs encountered
|
||||||
|
saved_hangs: u64,
|
||||||
|
/// seconds since the last find was found
|
||||||
|
last_find: Duration,
|
||||||
|
/// seconds since the last crash was found
|
||||||
|
last_crash: Duration,
|
||||||
|
/// seconds since the last hang was found
|
||||||
|
last_hang: Duration,
|
||||||
|
/// execs since the last crash was found
|
||||||
|
execs_since_crash: u64,
|
||||||
|
/// the -t command line value
|
||||||
|
exec_timeout: u64,
|
||||||
|
/// real time of the slowest execution in ms
|
||||||
|
slowest_exec_ms: u128,
|
||||||
|
/// max rss usage reached during fuzzing in MB
|
||||||
|
peak_rss_mb: i64,
|
||||||
|
/// TODO
|
||||||
|
cpu_affinity: i64,
|
||||||
|
/// how many edges have been found
|
||||||
|
edges_found: u64,
|
||||||
|
/// TODO:
|
||||||
|
total_edges: u64,
|
||||||
|
/// how many edges are non-deterministic
|
||||||
|
var_byte_count: usize,
|
||||||
|
/// TODO:
|
||||||
|
havoc_expansion: usize,
|
||||||
|
/// TODO:
|
||||||
|
auto_dict_entries: usize,
|
||||||
|
/// TODO:
|
||||||
|
testcache_size: usize,
|
||||||
|
/// TODO:
|
||||||
|
testcache_count: usize,
|
||||||
|
/// TODO:
|
||||||
|
testcache_evict: usize,
|
||||||
|
/// banner text (e.g., the target name)
|
||||||
|
afl_banner: &'a Cow<'static, str>,
|
||||||
|
/// the version of AFL++ used
|
||||||
|
afl_version: &'a Cow<'static, str>,
|
||||||
|
/// default, persistent, qemu, unicorn, non-instrumented
|
||||||
|
target_mode: &'a Cow<'static, str>,
|
||||||
|
/// full command line used for the fuzzing session
|
||||||
|
command_line: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AFLPlotData<'a> {
|
||||||
|
relative_time: &'a u64,
|
||||||
|
cycles_done: &'a u64,
|
||||||
|
cur_item: &'a usize,
|
||||||
|
corpus_count: &'a usize,
|
||||||
|
pending_total: &'a usize,
|
||||||
|
pending_favs: &'a usize,
|
||||||
|
/// Note: renamed `map_size` -> `total_edges` for consistency with `fuzzer_stats`
|
||||||
|
total_edges: &'a u64,
|
||||||
|
saved_crashes: &'a u64,
|
||||||
|
saved_hangs: &'a u64,
|
||||||
|
max_depth: &'a u64,
|
||||||
|
execs_per_sec: &'a u64,
|
||||||
|
/// Note: renamed `total_execs` -> `execs_done` for consistency with `fuzzer_stats`
|
||||||
|
execs_done: &'a u64,
|
||||||
|
edges_found: &'a u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, O, E, EM, Z> UsesState for AflStatsStage<C, O, E, EM, Z>
|
||||||
|
where
|
||||||
|
E: UsesState,
|
||||||
|
EM: EventFirer<State = E::State>,
|
||||||
|
Z: UsesState<State = E::State>,
|
||||||
|
{
|
||||||
|
type State = E::State;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, O, E, EM, Z> Stage<E, EM, Z> for AflStatsStage<C, O, E, EM, Z>
|
||||||
|
where
|
||||||
|
E: UsesState + HasObservers,
|
||||||
|
EM: EventFirer<State = E::State>,
|
||||||
|
Z: UsesState<State = E::State> + HasScheduler,
|
||||||
|
E::State: HasImported
|
||||||
|
+ HasCorpus
|
||||||
|
+ HasMetadata
|
||||||
|
+ HasStartTime
|
||||||
|
+ HasExecutions
|
||||||
|
+ HasNamedMetadata
|
||||||
|
+ Stoppable
|
||||||
|
+ HasTestcase,
|
||||||
|
O: MapObserver,
|
||||||
|
C: AsRef<O> + Named,
|
||||||
|
<Z as HasScheduler>::Scheduler: Scheduler + HasQueueCycles,
|
||||||
|
{
|
||||||
|
fn perform(
|
||||||
|
&mut self,
|
||||||
|
fuzzer: &mut Z,
|
||||||
|
executor: &mut E,
|
||||||
|
state: &mut E::State,
|
||||||
|
_manager: &mut EM,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let Some(corpus_idx) = state.current_corpus_id()? else {
|
||||||
|
return Err(Error::illegal_state(
|
||||||
|
"state is not currently processing a corpus index",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let testcase = state.corpus().get(corpus_idx)?.borrow();
|
||||||
|
// NOTE: scheduled_count represents the amount of fuzzing iterations a
|
||||||
|
// testcase has had. Since this stage is kept at the very end of stage list,
|
||||||
|
// the entry would have been fuzzed already (and should contain IsFavoredMetadata) but would have a scheduled count of zero
|
||||||
|
// since the scheduled count is incremented after all stages have been run.
|
||||||
|
if testcase.scheduled_count() == 0 {
|
||||||
|
// New testcase!
|
||||||
|
self.cycles_wo_finds = 0;
|
||||||
|
self.update_last_find();
|
||||||
|
self.maybe_update_last_crash(&testcase, state);
|
||||||
|
self.maybe_update_last_hang(&testcase, state);
|
||||||
|
self.update_has_fuzzed_size();
|
||||||
|
self.maybe_update_is_favored_size(&testcase);
|
||||||
|
}
|
||||||
|
self.maybe_update_slowest_exec(&testcase);
|
||||||
|
self.maybe_update_max_depth(&testcase)?;
|
||||||
|
|
||||||
|
// See if we actually need to run the stage, if not, avoid dynamic value computation.
|
||||||
|
if !self.check_interval() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let corpus_size = state.corpus().count();
|
||||||
|
let total_executions = *state.executions();
|
||||||
|
|
||||||
|
let scheduler = fuzzer.scheduler();
|
||||||
|
let queue_cycles = scheduler.queue_cycles();
|
||||||
|
self.maybe_update_cycles(queue_cycles);
|
||||||
|
self.maybe_update_cycles_wo_finds(queue_cycles);
|
||||||
|
|
||||||
|
let observers = executor.observers();
|
||||||
|
let map_observer = observers
|
||||||
|
.get(&self.map_observer_handle)
|
||||||
|
.ok_or_else(|| Error::key_not_found("invariant: MapObserver not found".to_string()))?
|
||||||
|
.as_ref();
|
||||||
|
let filled_entries_in_map = map_observer.count_bytes();
|
||||||
|
let map_size = map_observer.usable_count();
|
||||||
|
|
||||||
|
let unstable_entries_metadata = state
|
||||||
|
.metadata_map()
|
||||||
|
.get::<UnstableEntriesMetadata>()
|
||||||
|
.unwrap();
|
||||||
|
let unstable_entries_in_map = unstable_entries_metadata.unstable_entries().len();
|
||||||
|
|
||||||
|
let stats = AFLFuzzerStats {
|
||||||
|
start_time: self.start_time,
|
||||||
|
last_update: self.last_report_time.as_secs(),
|
||||||
|
run_time: self.last_report_time.as_secs() - self.start_time,
|
||||||
|
fuzzer_pid: self.pid,
|
||||||
|
cycles_done: queue_cycles,
|
||||||
|
cycles_wo_find: self.cycles_wo_finds,
|
||||||
|
fuzz_time: 0, // TODO
|
||||||
|
calibration_time: 0, // TODO
|
||||||
|
sync_time: 0, // TODO
|
||||||
|
trim_time: 0, // TODO
|
||||||
|
execs_done: total_executions,
|
||||||
|
execs_per_sec: *state.executions(), // TODO
|
||||||
|
execs_ps_last_min: *state.executions(), // TODO
|
||||||
|
max_depth: self.max_depth,
|
||||||
|
corpus_count: corpus_size,
|
||||||
|
corpus_favored: corpus_size - self.is_favored_size,
|
||||||
|
corpus_found: corpus_size - state.imported(),
|
||||||
|
corpus_imported: *state.imported(),
|
||||||
|
cur_item: corpus_idx.into(),
|
||||||
|
pending_total: corpus_size - self.has_fuzzed_size,
|
||||||
|
pending_favs: 0, // TODO
|
||||||
|
time_wo_finds: (current_time() - self.last_find).as_secs(),
|
||||||
|
corpus_variable: 0,
|
||||||
|
stability: self.calculate_stability(unstable_entries_in_map, filled_entries_in_map),
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
|
bitmap_cvg: (filled_entries_in_map as f64 / map_size as f64) * 100.0,
|
||||||
|
saved_crashes: self.saved_crashes,
|
||||||
|
saved_hangs: self.saved_hangs,
|
||||||
|
last_find: self.last_find,
|
||||||
|
last_hang: self.last_hang,
|
||||||
|
last_crash: self.last_crash,
|
||||||
|
execs_since_crash: total_executions - self.execs_at_last_objective,
|
||||||
|
exec_timeout: self.exec_timeout, // TODO
|
||||||
|
slowest_exec_ms: self.slowest_exec.as_millis(),
|
||||||
|
peak_rss_mb: peak_rss_mb_child_processes()?,
|
||||||
|
cpu_affinity: 0, // TODO
|
||||||
|
total_edges: map_size as u64,
|
||||||
|
edges_found: filled_entries_in_map,
|
||||||
|
var_byte_count: unstable_entries_metadata.unstable_entries().len(),
|
||||||
|
havoc_expansion: 0, // TODO
|
||||||
|
auto_dict_entries: 0, // TODO
|
||||||
|
testcache_size: 0,
|
||||||
|
testcache_count: 0,
|
||||||
|
testcache_evict: 0,
|
||||||
|
afl_banner: &self.afl_banner,
|
||||||
|
afl_version: &self.afl_version,
|
||||||
|
target_mode: &self.target_mode,
|
||||||
|
command_line: &self.command_line,
|
||||||
|
};
|
||||||
|
let plot_data = AFLPlotData {
|
||||||
|
corpus_count: &stats.corpus_count,
|
||||||
|
cur_item: &stats.cur_item,
|
||||||
|
cycles_done: &stats.cycles_done,
|
||||||
|
edges_found: &stats.edges_found,
|
||||||
|
total_edges: &stats.total_edges,
|
||||||
|
execs_per_sec: &stats.execs_per_sec,
|
||||||
|
pending_total: &stats.pending_total,
|
||||||
|
pending_favs: &stats.pending_favs,
|
||||||
|
max_depth: &stats.max_depth,
|
||||||
|
relative_time: &stats.run_time,
|
||||||
|
saved_hangs: &stats.saved_hangs,
|
||||||
|
saved_crashes: &stats.saved_crashes,
|
||||||
|
execs_done: &stats.execs_done,
|
||||||
|
};
|
||||||
|
self.write_fuzzer_stats(&stats)?;
|
||||||
|
self.write_plot_data(&plot_data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn should_restart(&mut self, _state: &mut Self::State) -> Result<bool, Error> {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, O, E, EM, Z> AflStatsStage<C, O, E, EM, Z>
|
||||||
|
where
|
||||||
|
E: UsesState + HasObservers,
|
||||||
|
EM: EventFirer<State = E::State>,
|
||||||
|
Z: UsesState<State = E::State>,
|
||||||
|
E::State: HasImported + HasCorpus + HasMetadata + HasExecutions,
|
||||||
|
C: AsRef<O> + Named,
|
||||||
|
O: MapObserver,
|
||||||
|
{
|
||||||
|
/// create a new instance of the [`AflStatsStage`]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(opt: &Opt, fuzzer_dir: PathBuf, map_observer: &C) -> Self {
|
||||||
|
Self::create_plot_data_file(&fuzzer_dir).unwrap();
|
||||||
|
Self::create_fuzzer_stats_file(&fuzzer_dir).unwrap();
|
||||||
|
Self {
|
||||||
|
map_observer_handle: map_observer.handle(),
|
||||||
|
start_time: current_time().as_secs(),
|
||||||
|
stats_report_interval: Duration::from_secs(opt.stats_interval),
|
||||||
|
has_fuzzed_size: 0,
|
||||||
|
is_favored_size: 0,
|
||||||
|
cycles_done: 0,
|
||||||
|
cycles_wo_finds: 0,
|
||||||
|
execs_at_last_objective: 0,
|
||||||
|
last_crash: current_time(),
|
||||||
|
last_find: current_time(),
|
||||||
|
last_hang: current_time(),
|
||||||
|
max_depth: 0,
|
||||||
|
saved_hangs: 0,
|
||||||
|
saved_crashes: 0,
|
||||||
|
slowest_exec: Duration::from_secs(0),
|
||||||
|
last_report_time: current_time(),
|
||||||
|
pid: process::id(),
|
||||||
|
exec_timeout: opt.hang_timeout,
|
||||||
|
target_mode: fuzzer_target_mode(opt),
|
||||||
|
afl_banner: Cow::Owned(opt.executable.display().to_string()),
|
||||||
|
afl_version: Cow::Borrowed("libafl-fuzz-0.0.1"),
|
||||||
|
command_line: get_run_cmdline(),
|
||||||
|
fuzzer_dir,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_plot_data_file(fuzzer_dir: &PathBuf) -> Result<(), Error> {
|
||||||
|
let path = fuzzer_dir.join("plot_data");
|
||||||
|
if path.exists() {
|
||||||
|
// check if it contains any data
|
||||||
|
let file = File::open(path)?;
|
||||||
|
if BufReader::new(file).lines().next().is_none() {
|
||||||
|
std::fs::write(fuzzer_dir.join("plot_data"), AFLPlotData::get_header())?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::fs::write(fuzzer_dir.join("plot_data"), AFLPlotData::get_header())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_fuzzer_stats_file(fuzzer_dir: &PathBuf) -> Result<(), Error> {
|
||||||
|
let path = fuzzer_dir.join("fuzzer_stats");
|
||||||
|
if !path.exists() {
|
||||||
|
OpenOptions::new().append(true).create(true).open(path)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_fuzzer_stats(&self, stats: &AFLFuzzerStats) -> Result<(), Error> {
|
||||||
|
std::fs::write(self.fuzzer_dir.join("fuzzer_stats"), stats.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_plot_data(&self, plot_data: &AFLPlotData) -> Result<(), Error> {
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.open(self.fuzzer_dir.join("plot_data"))?;
|
||||||
|
writeln!(file, "{plot_data}")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_update_is_favored_size(
|
||||||
|
&mut self,
|
||||||
|
testcase: &Testcase<<<E as UsesState>::State as UsesInput>::Input>,
|
||||||
|
) {
|
||||||
|
if testcase.has_metadata::<IsFavoredMetadata>() {
|
||||||
|
self.is_favored_size += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_update_slowest_exec(
|
||||||
|
&mut self,
|
||||||
|
testcase: &Testcase<<<E as UsesState>::State as UsesInput>::Input>,
|
||||||
|
) {
|
||||||
|
if let Some(exec_time) = testcase.exec_time() {
|
||||||
|
if exec_time > &self.slowest_exec {
|
||||||
|
self.slowest_exec = *exec_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_has_fuzzed_size(&mut self) {
|
||||||
|
self.has_fuzzed_size += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_update_max_depth(
|
||||||
|
&mut self,
|
||||||
|
testcase: &Testcase<<<E as UsesState>::State as UsesInput>::Input>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if let Ok(metadata) = testcase.metadata::<SchedulerTestcaseMetadata>() {
|
||||||
|
if metadata.depth() > self.max_depth {
|
||||||
|
self.max_depth = metadata.depth();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::illegal_state(
|
||||||
|
"testcase must have scheduler metdata?",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_last_find(&mut self) {
|
||||||
|
self.last_find = current_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_update_last_crash(
|
||||||
|
&mut self,
|
||||||
|
testcase: &Testcase<<<E as UsesState>::State as UsesInput>::Input>,
|
||||||
|
state: &E::State,
|
||||||
|
) {
|
||||||
|
if testcase
|
||||||
|
.hit_objectives()
|
||||||
|
.contains(&Cow::Borrowed("CrashFeedback"))
|
||||||
|
{
|
||||||
|
self.last_crash = current_time();
|
||||||
|
self.execs_at_last_objective = *state.executions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_update_last_hang(
|
||||||
|
&mut self,
|
||||||
|
testcase: &Testcase<<<E as UsesState>::State as UsesInput>::Input>,
|
||||||
|
state: &E::State,
|
||||||
|
) {
|
||||||
|
if testcase
|
||||||
|
.hit_objectives()
|
||||||
|
.contains(&Cow::Borrowed("TimeoutFeedback"))
|
||||||
|
{
|
||||||
|
self.last_hang = current_time();
|
||||||
|
self.execs_at_last_objective = *state.executions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_interval(&mut self) -> bool {
|
||||||
|
let cur = current_time();
|
||||||
|
if cur.checked_sub(self.last_report_time).unwrap_or_default() > self.stats_report_interval {
|
||||||
|
self.last_report_time = cur;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn maybe_update_cycles(&mut self, queue_cycles: u64) {
|
||||||
|
if queue_cycles > self.cycles_done {
|
||||||
|
self.cycles_done += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_update_cycles_wo_finds(&mut self, queue_cycles: u64) {
|
||||||
|
if queue_cycles > self.cycles_done && self.last_find < current_time() {
|
||||||
|
self.cycles_wo_finds += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
|
fn calculate_stability(&self, unstable_entries: usize, filled_entries: u64) -> f64 {
|
||||||
|
((filled_entries as f64 - unstable_entries as f64) / filled_entries as f64) * 100.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for AFLPlotData<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{},", self.relative_time)?;
|
||||||
|
write!(f, "{},", self.cycles_done)?;
|
||||||
|
write!(f, "{},", self.cur_item)?;
|
||||||
|
write!(f, "{},", self.corpus_count)?;
|
||||||
|
write!(f, "{},", self.pending_total)?;
|
||||||
|
write!(f, "{},", self.pending_favs)?;
|
||||||
|
write!(f, "{},", self.total_edges)?;
|
||||||
|
write!(f, "{},", self.saved_crashes)?;
|
||||||
|
write!(f, "{},", self.saved_hangs)?;
|
||||||
|
write!(f, "{},", self.max_depth)?;
|
||||||
|
write!(f, "{},", self.execs_per_sec)?;
|
||||||
|
write!(f, "{},", self.execs_done)?;
|
||||||
|
write!(f, "{}", self.edges_found)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AFLPlotData<'_> {
|
||||||
|
fn get_header() -> 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".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Display for AFLFuzzerStats<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(f, "start_time : {}", &self.start_time)?;
|
||||||
|
writeln!(f, "start_time : {}", &self.start_time)?;
|
||||||
|
writeln!(f, "last_update : {}", &self.last_update)?;
|
||||||
|
writeln!(f, "run_time : {}", &self.run_time)?;
|
||||||
|
writeln!(f, "fuzzer_pid : {}", &self.fuzzer_pid)?;
|
||||||
|
writeln!(f, "cycles_done : {}", &self.cycles_done)?;
|
||||||
|
writeln!(f, "cycles_wo_find : {}", &self.cycles_wo_find)?;
|
||||||
|
writeln!(f, "time_wo_finds : {}", &self.time_wo_finds)?;
|
||||||
|
writeln!(f, "fuzz_time : {}", &self.fuzz_time)?;
|
||||||
|
writeln!(f, "calibration_time : {}", &self.calibration_time)?;
|
||||||
|
writeln!(f, "sync_time : {}", &self.sync_time)?;
|
||||||
|
writeln!(f, "trim_time : {}", &self.trim_time)?;
|
||||||
|
writeln!(f, "execs_done : {}", &self.execs_done)?;
|
||||||
|
writeln!(f, "execs_per_sec : {}", &self.execs_per_sec)?;
|
||||||
|
writeln!(f, "execs_ps_last_min : {}", &self.execs_ps_last_min)?;
|
||||||
|
writeln!(f, "corpus_count : {}", &self.corpus_count)?;
|
||||||
|
writeln!(f, "corpus_favored : {}", &self.corpus_favored)?;
|
||||||
|
writeln!(f, "corpus_found : {}", &self.corpus_found)?;
|
||||||
|
writeln!(f, "corpus_imported : {}", &self.corpus_imported)?;
|
||||||
|
writeln!(f, "max_depth : {}", &self.max_depth)?;
|
||||||
|
writeln!(f, "cur_item : {}", &self.cur_item)?;
|
||||||
|
writeln!(f, "pending_favs : {}", &self.pending_favs)?;
|
||||||
|
writeln!(f, "pending_total : {}", &self.pending_total)?;
|
||||||
|
writeln!(f, "corpus_variable : {}", &self.corpus_variable)?;
|
||||||
|
writeln!(f, "stability : {:.2}%", &self.stability)?;
|
||||||
|
writeln!(f, "bitmap_cvg : {:.2}%", &self.bitmap_cvg)?;
|
||||||
|
writeln!(f, "saved_crashes : {}", &self.saved_crashes)?;
|
||||||
|
writeln!(f, "saved_hangs : {}", &self.saved_hangs)?;
|
||||||
|
writeln!(f, "last_find : {}", &self.last_find.as_secs())?;
|
||||||
|
writeln!(f, "last_crash : {}", &self.last_crash.as_secs())?;
|
||||||
|
writeln!(f, "last_hang : {}", &self.last_hang.as_secs())?;
|
||||||
|
writeln!(f, "execs_since_crash : {}", &self.execs_since_crash)?;
|
||||||
|
writeln!(f, "exec_timeout : {}", &self.exec_timeout)?;
|
||||||
|
writeln!(f, "slowest_exec_ms : {}", &self.slowest_exec_ms)?;
|
||||||
|
writeln!(f, "peak_rss_mb : {}", &self.peak_rss_mb)?;
|
||||||
|
writeln!(f, "cpu_affinity : {}", &self.cpu_affinity)?;
|
||||||
|
writeln!(f, "edges_found : {}", &self.edges_found)?;
|
||||||
|
writeln!(f, "total_edges : {}", &self.total_edges)?;
|
||||||
|
writeln!(f, "var_byte_count : {}", &self.var_byte_count)?;
|
||||||
|
writeln!(f, "havoc_expansion : {}", &self.havoc_expansion)?;
|
||||||
|
writeln!(f, "auto_dict_entries : {}", &self.auto_dict_entries)?;
|
||||||
|
writeln!(f, "testcache_size : {}", &self.testcache_size)?;
|
||||||
|
writeln!(f, "testcache_count : {}", &self.testcache_count)?;
|
||||||
|
writeln!(f, "testcache_evict : {}", &self.testcache_evict)?;
|
||||||
|
writeln!(f, "afl_banner : {}", self.afl_banner)?;
|
||||||
|
writeln!(f, "afl_version : {}", self.afl_version)?;
|
||||||
|
writeln!(f, "target_mode : {}", self.target_mode)?;
|
||||||
|
writeln!(f, "command_line : {}", self.command_line)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Get the command used to invoke libafl-fuzz
|
||||||
|
pub fn get_run_cmdline() -> Cow<'static, str> {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
Cow::Owned(args.join(" "))
|
||||||
|
}
|
189
fuzzers/libafl-fuzz/src/corpus.rs
Normal file
189
fuzzers/libafl-fuzz/src/corpus.rs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
fs::File,
|
||||||
|
io::{self, BufRead, BufReader},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
corpus::{Corpus, Testcase},
|
||||||
|
inputs::BytesInput,
|
||||||
|
state::{HasCorpus, HasExecutions, HasStartTime},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
use libafl_bolts::current_time;
|
||||||
|
use nix::{
|
||||||
|
errno::Errno,
|
||||||
|
fcntl::{Flock, FlockArg},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{fuzzer::LibaflFuzzState, OUTPUT_GRACE};
|
||||||
|
|
||||||
|
pub fn generate_base_filename(state: &mut LibaflFuzzState) -> String {
|
||||||
|
let is_seed = state.must_load_initial_inputs();
|
||||||
|
let id = state.corpus().peek_free_id().0;
|
||||||
|
let name = if is_seed {
|
||||||
|
// TODO set orig filename
|
||||||
|
format!("id:{id:0>6},time:0,execs:0,orig:TODO",)
|
||||||
|
} else {
|
||||||
|
// TODO: change hardcoded values of op (operation aka stage_name) & rep (amount of stacked mutations applied)
|
||||||
|
let src = if let Some(parent_id) = state.corpus().current() {
|
||||||
|
parent_id.0
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let execs = *state.executions();
|
||||||
|
let time = (current_time() - *state.start_time()).as_secs();
|
||||||
|
format!("id:{id:0>6},src:{src:0>6},time:{time},execs:{execs},op:havoc,rep:0",)
|
||||||
|
};
|
||||||
|
name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_corpus_filepath(
|
||||||
|
state: &mut LibaflFuzzState,
|
||||||
|
testcase: &mut Testcase<BytesInput>,
|
||||||
|
_fuzzer_dir: &PathBuf,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut name = generate_base_filename(state);
|
||||||
|
if testcase.hit_feedbacks().contains(&Cow::Borrowed("edges")) {
|
||||||
|
name = format!("{name},+cov");
|
||||||
|
}
|
||||||
|
*testcase.filename_mut() = Some(name);
|
||||||
|
// We don't need to set the path since everything goes into one dir unlike with Objectives
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_solution_filepath(
|
||||||
|
state: &mut LibaflFuzzState,
|
||||||
|
testcase: &mut Testcase<BytesInput>,
|
||||||
|
output_dir: &PathBuf,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// sig:0SIGNAL
|
||||||
|
// TODO: verify if 0 time if objective found during seed loading
|
||||||
|
let mut filename = generate_base_filename(state);
|
||||||
|
let mut dir = "crashes";
|
||||||
|
if testcase
|
||||||
|
.hit_objectives()
|
||||||
|
.contains(&Cow::Borrowed("TimeoutFeedback"))
|
||||||
|
{
|
||||||
|
filename = format!("{filename},+tout");
|
||||||
|
dir = "hangs";
|
||||||
|
}
|
||||||
|
*testcase.file_path_mut() = Some(output_dir.join(dir).join(&filename));
|
||||||
|
*testcase.filename_mut() = Some(filename);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_time_line(line: &str) -> Result<u64, Error> {
|
||||||
|
line.split(": ")
|
||||||
|
.last()
|
||||||
|
.ok_or(Error::illegal_state("invalid stats file"))?
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| Error::illegal_state("invalid stats file"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_autoresume(
|
||||||
|
fuzzer_dir: &Path,
|
||||||
|
intial_inputs: &PathBuf,
|
||||||
|
auto_resume: bool,
|
||||||
|
) -> Result<Flock<File>, Error> {
|
||||||
|
if !fuzzer_dir.exists() {
|
||||||
|
std::fs::create_dir(fuzzer_dir)?;
|
||||||
|
}
|
||||||
|
// lock the fuzzer dir
|
||||||
|
let fuzzer_dir_fd = File::open(fuzzer_dir)?;
|
||||||
|
let file = match Flock::lock(fuzzer_dir_fd, FlockArg::LockExclusiveNonblock) {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(err) => match err.1 {
|
||||||
|
Errno::EWOULDBLOCK => return Err(Error::illegal_state(
|
||||||
|
"Looks like the job output directory is being actively used by another instance",
|
||||||
|
)),
|
||||||
|
_ => {
|
||||||
|
return Err(Error::last_os_error(
|
||||||
|
format!("Error creating lock for output dir: exit code {}", err.1).as_str(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// Check if we have an existing fuzzed fuzzer_dir
|
||||||
|
let stats_file = fuzzer_dir.join("fuzzer_stats");
|
||||||
|
if stats_file.exists() {
|
||||||
|
let file = File::open(&stats_file).unwrap();
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let mut start_time: u64 = 0;
|
||||||
|
let mut last_update: u64 = 0;
|
||||||
|
for (index, line) in reader.lines().enumerate() {
|
||||||
|
match index {
|
||||||
|
// first line is start_time
|
||||||
|
0 => {
|
||||||
|
start_time = parse_time_line(&line?).unwrap();
|
||||||
|
}
|
||||||
|
// second_line is last_update
|
||||||
|
1 => {
|
||||||
|
last_update = parse_time_line(&line?).unwrap();
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !auto_resume && last_update.saturating_sub(start_time) > OUTPUT_GRACE * 60 {
|
||||||
|
return Err(Error::illegal_state("The job output directory already exists and contains results! use AFL_AUTORESUME=true or provide \"-\" for -i "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if auto_resume {
|
||||||
|
// TODO: once the queue stuff is implemented finish the rest of the function
|
||||||
|
// see afl-fuzz-init.c line 1898 onwards. Gotta copy and delete shit
|
||||||
|
// No usable test cases in './output/default/_resume'
|
||||||
|
} else {
|
||||||
|
let queue_dir = fuzzer_dir.join("queue");
|
||||||
|
let hangs_dir = fuzzer_dir.join("hangs");
|
||||||
|
let crashes_dir = fuzzer_dir.join("crashes");
|
||||||
|
// Create our (sub) directories for Objectives & Corpus
|
||||||
|
create_dir_if_not_exists(&crashes_dir).expect("should be able to create crashes dir");
|
||||||
|
create_dir_if_not_exists(&hangs_dir).expect("should be able to create hangs dir");
|
||||||
|
create_dir_if_not_exists(&queue_dir).expect("should be able to create queue dir");
|
||||||
|
// Copy all our seeds to queue
|
||||||
|
for file in std::fs::read_dir(intial_inputs)? {
|
||||||
|
let path = file?.path();
|
||||||
|
std::fs::copy(
|
||||||
|
&path,
|
||||||
|
queue_dir.join(path.file_name().ok_or(Error::illegal_state(format!(
|
||||||
|
"file {} in input directory does not have a filename",
|
||||||
|
path.display()
|
||||||
|
)))?),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_dir_if_not_exists(path: &PathBuf) -> std::io::Result<()> {
|
||||||
|
if path.is_file() {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
// TODO: change this to ErrorKind::NotADirectory
|
||||||
|
// when stabilitzed https://github.com/rust-lang/rust/issues/86442
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("{} expected a directory; got a file", path.display()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
match std::fs::create_dir(path) {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(err) => {
|
||||||
|
if matches!(err.kind(), io::ErrorKind::AlreadyExists) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_main_node_file(output_dir: &PathBuf) -> Result<(), Error> {
|
||||||
|
for entry in std::fs::read_dir(output_dir)?.filter_map(std::result::Result::ok) {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() && path.join("is_main_node").exists() {
|
||||||
|
std::fs::remove_file(path.join("is_main_node"))?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::illegal_state("main node's directory not found!"))
|
||||||
|
}
|
155
fuzzers/libafl-fuzz/src/env_parser.rs
Normal file
155
fuzzers/libafl-fuzz/src/env_parser.rs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
use std::{collections::HashMap, path::PathBuf, time::Duration};
|
||||||
|
|
||||||
|
use libafl::Error;
|
||||||
|
use libafl_bolts::core_affinity::Cores;
|
||||||
|
|
||||||
|
use crate::Opt;
|
||||||
|
|
||||||
|
pub fn parse_envs(opt: &mut Opt) -> Result<(), Error> {
|
||||||
|
if let Ok(res) = std::env::var("AFL_CORES") {
|
||||||
|
opt.cores = Some(Cores::from_cmdline(&res)?);
|
||||||
|
} else {
|
||||||
|
return Err(Error::illegal_argument("Missing AFL_CORES"));
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_INPUT_LEN_MAX") {
|
||||||
|
opt.max_input_len = Some(res.parse()?);
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_INPUT_LEN_MIN") {
|
||||||
|
opt.min_input_len = Some(res.parse()?);
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_BENCH_JUST_ONE") {
|
||||||
|
opt.bench_just_one = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_BENCH_UNTIL_CRASH") {
|
||||||
|
opt.bench_until_crash = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_HANG_TMOUT") {
|
||||||
|
opt.hang_timeout = res.parse()?;
|
||||||
|
} else {
|
||||||
|
opt.hang_timeout = 100;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_DEBUG_CHILD") {
|
||||||
|
opt.debug_child = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_PERSISTENT") {
|
||||||
|
opt.is_persistent = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_NO_AUTODICT") {
|
||||||
|
opt.no_autodict = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_MAP_SIZE") {
|
||||||
|
let map_size = res.parse()?;
|
||||||
|
validate_map_size(map_size)?;
|
||||||
|
opt.map_size = Some(map_size);
|
||||||
|
};
|
||||||
|
if let Ok(res) = std::env::var("AFL_IGNORE_TIMEOUT") {
|
||||||
|
opt.ignore_timeouts = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_TMPDIR") {
|
||||||
|
opt.cur_input_dir = Some(PathBuf::from(res));
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_CRASH_EXITCODE") {
|
||||||
|
opt.crash_exitcode = Some(res.parse()?);
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_TARGET_ENV") {
|
||||||
|
opt.target_env = parse_target_env(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_CYCLE_SCHEDULES") {
|
||||||
|
opt.cycle_schedules = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_CMPLOG_ONLY_NEW") {
|
||||||
|
opt.cmplog_only_new = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_PRELOAD") {
|
||||||
|
opt.afl_preload = Some(res);
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_SKIP_BIN_CHECK") {
|
||||||
|
opt.skip_bin_check = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_AUTORESUME") {
|
||||||
|
opt.auto_resume = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_DEFER_FORKSRV") {
|
||||||
|
opt.defer_forkserver = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_FUZZER_STATS_UPDATE_INTERVAL") {
|
||||||
|
opt.stats_interval = res.parse()?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_BROKER_PORT") {
|
||||||
|
opt.broker_port = Some(res.parse()?);
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_EXIT_ON_SEED_ISSUES") {
|
||||||
|
opt.exit_on_seed_issues = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_IGNORE_SEED_ISSUES") {
|
||||||
|
opt.ignore_seed_issues = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_CRASHING_SEED_AS_NEW_CRASH") {
|
||||||
|
opt.crash_seed_as_new_crash = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_FRIDA_PERSISTENT_ADDR") {
|
||||||
|
opt.frida_persistent_addr = Some(res);
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_QEMU_CUSTOM_BIN") {
|
||||||
|
opt.qemu_custom_bin = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_CS_CUSTOM_BIN") {
|
||||||
|
opt.cs_custom_bin = parse_bool(&res)?;
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_KILL_SIGNAL") {
|
||||||
|
opt.kill_signal = Some(res.parse()?);
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_KILL_SIGNAL") {
|
||||||
|
opt.kill_signal = Some(res.parse()?);
|
||||||
|
}
|
||||||
|
if let Ok(res) = std::env::var("AFL_SYNC_TIME") {
|
||||||
|
opt.foreign_sync_interval = Duration::from_secs(res.parse::<u64>()? * 60);
|
||||||
|
} else {
|
||||||
|
opt.foreign_sync_interval = Duration::from_secs(AFL_DEFAULT_FOREIGN_SYNC_INTERVAL);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_bool(val: &str) -> Result<bool, Error> {
|
||||||
|
match val {
|
||||||
|
"1" => Ok(true),
|
||||||
|
"0" => Ok(false),
|
||||||
|
_ => Err(Error::illegal_argument(
|
||||||
|
"boolean values must be either 1 for true or 0 for false",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// parse `AFL_TARGET_ENV`; expects: FOO=BAR TEST=ASD
|
||||||
|
fn parse_target_env(s: &str) -> Result<Option<HashMap<String, String>>, Error> {
|
||||||
|
let env_regex = regex::Regex::new(r"([^\s=]+)\s*=\s*([^\s]+)").unwrap();
|
||||||
|
let mut target_env = HashMap::new();
|
||||||
|
for vars in env_regex.captures_iter(s) {
|
||||||
|
target_env.insert(
|
||||||
|
vars.get(1)
|
||||||
|
.ok_or(Error::illegal_argument("invalid AFL_TARGET_ENV format"))?
|
||||||
|
.as_str()
|
||||||
|
.to_string(),
|
||||||
|
vars.get(2)
|
||||||
|
.ok_or(Error::illegal_argument("invalid AFL_TARGET_ENV format"))?
|
||||||
|
.as_str()
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(Some(target_env))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_map_size(map_size: usize) -> Result<usize, Error> {
|
||||||
|
if map_size > AFL_MAP_SIZE_MIN && map_size < AFL_MAP_SIZE_MAX {
|
||||||
|
Ok(map_size)
|
||||||
|
} else {
|
||||||
|
Err(Error::illegal_argument(format!(
|
||||||
|
"AFL_MAP_SIZE not in range {AFL_MAP_SIZE_MIN} (2 ^ 3) - {AFL_MAP_SIZE_MAX} (2 ^ 30)",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AFL_MAP_SIZE_MIN: usize = usize::pow(2, 3);
|
||||||
|
const AFL_MAP_SIZE_MAX: usize = usize::pow(2, 30);
|
||||||
|
const AFL_DEFAULT_FOREIGN_SYNC_INTERVAL: u64 = 20 * 60;
|
||||||
|
pub const AFL_DEFAULT_MAP_SIZE: usize = 65536;
|
176
fuzzers/libafl-fuzz/src/executor.rs
Normal file
176
fuzzers/libafl-fuzz/src/executor.rs
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
os::{linux::fs::MetadataExt, unix::fs::PermissionsExt},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use libafl::Error;
|
||||||
|
use memmap2::{Mmap, MmapOptions};
|
||||||
|
|
||||||
|
use crate::{Opt, DEFER_SIG, PERSIST_SIG};
|
||||||
|
|
||||||
|
// TODO better error messages and logging
|
||||||
|
pub fn check_binary(opt: &mut Opt, shmem_env_var: &str) -> Result<(), Error> {
|
||||||
|
println!("Validating target binary...");
|
||||||
|
|
||||||
|
let bin_path;
|
||||||
|
// check if it is a file path
|
||||||
|
if opt.executable.components().count() == 1 {
|
||||||
|
// check $PATH for the binary.
|
||||||
|
if let Some(full_bin_path) = find_executable_in_path(&opt.executable) {
|
||||||
|
opt.executable = full_bin_path;
|
||||||
|
bin_path = &opt.executable;
|
||||||
|
} else {
|
||||||
|
return Err(Error::illegal_argument(format!(
|
||||||
|
"Program '{}' not found or not executable",
|
||||||
|
opt.executable.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bin_path = &opt.executable;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
if opt.nyx_mode {
|
||||||
|
if !bin_path.is_symlink() && bin_path.is_dir() {
|
||||||
|
let config_file = bin_path.join("config.ron");
|
||||||
|
if !config_file.is_symlink() && config_file.is_file() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(Error::illegal_argument(
|
||||||
|
format!(
|
||||||
|
"Directory '{}' not found, or is a symlink or is not a nyx share directory",
|
||||||
|
bin_path.display()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let metadata = bin_path.metadata()?;
|
||||||
|
let is_reg = !bin_path.is_symlink() && !bin_path.is_dir();
|
||||||
|
let bin_size = metadata.st_size();
|
||||||
|
let is_executable = metadata.permissions().mode() & 0o111 != 0;
|
||||||
|
if !is_reg || !is_executable || bin_size < 4 {
|
||||||
|
return Err(Error::illegal_argument(format!(
|
||||||
|
"Program '{}' not found or not executable",
|
||||||
|
bin_path.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if opt.skip_bin_check
|
||||||
|
|| opt.use_wine
|
||||||
|
|| opt.unicorn_mode
|
||||||
|
|| (opt.qemu_mode && opt.qemu_custom_bin)
|
||||||
|
|| (opt.forkserver_cs && opt.cs_custom_bin)
|
||||||
|
|| opt.non_instrumented_mode
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = File::open(bin_path)?;
|
||||||
|
let mmap = unsafe { MmapOptions::new().map(&file)? };
|
||||||
|
|
||||||
|
// check if it's a shell script
|
||||||
|
if mmap[0..1] == [0x43, 0x41] {
|
||||||
|
// TODO: finish error message
|
||||||
|
return Err(Error::illegal_argument(
|
||||||
|
"Oops, the target binary looks like a shell script.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the binary is an ELF file
|
||||||
|
if mmap[0..4] != [0x7f, 0x45, 0x4c, 0x46] {
|
||||||
|
return Err(Error::illegal_argument(format!(
|
||||||
|
"Program '{}' is not an ELF binary",
|
||||||
|
bin_path.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_os = "macos", not(target_arch = "arm")))]
|
||||||
|
{
|
||||||
|
if (mmap[0] != 0xCF || mmap[1] != 0xFA || mmap[2] != 0xED)
|
||||||
|
&& (mmap[0] != 0xCA || mmap[1] != 0xFE || mmap[2] != 0xBA)
|
||||||
|
{
|
||||||
|
return Err(Error::illegal_argument(format!(
|
||||||
|
"Program '{}' is not a 64-bit or universal Mach-O binary",
|
||||||
|
bin_path.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let check_instrumentation = !opt.qemu_mode
|
||||||
|
&& !opt.frida_mode
|
||||||
|
&& !opt.unicorn_mode
|
||||||
|
&& !opt.forkserver_cs
|
||||||
|
&& !opt.non_instrumented_mode;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let check_instrumentation = check_instrumentation && !opt.nyx_mode;
|
||||||
|
|
||||||
|
if check_instrumentation && !is_instrumented(&mmap, shmem_env_var) {
|
||||||
|
return Err(Error::illegal_argument(
|
||||||
|
"target binary is not instrumented correctly",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.forkserver_cs || opt.qemu_mode || opt.frida_mode && is_instrumented(&mmap, shmem_env_var)
|
||||||
|
{
|
||||||
|
return Err(Error::illegal_argument("Instrumentation found in -Q mode"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if mmap_has_substr(&mmap, "__asan_init")
|
||||||
|
|| mmap_has_substr(&mmap, "__lsan_init")
|
||||||
|
|| mmap_has_substr(&mmap, "__lsan_init")
|
||||||
|
{
|
||||||
|
opt.uses_asan = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if mmap_has_substr(&mmap, PERSIST_SIG) {
|
||||||
|
opt.is_persistent = true;
|
||||||
|
} else if opt.is_persistent {
|
||||||
|
println!("persistent mode enforced");
|
||||||
|
} else if opt.frida_persistent_addr.is_some() {
|
||||||
|
println!("FRIDA persistent mode configuration options detected");
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.frida_mode || mmap_has_substr(&mmap, DEFER_SIG) {
|
||||||
|
println!("deferred forkserver binary detected");
|
||||||
|
opt.defer_forkserver = true;
|
||||||
|
} else if opt.defer_forkserver {
|
||||||
|
println!("defer forkserver enforced");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
// Safety: unmap() is called on Mmap object Drop
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mmap_has_substr(mmap: &Mmap, sub_str: &str) -> bool {
|
||||||
|
let mmap_len = mmap.len();
|
||||||
|
let substr_len = sub_str.len();
|
||||||
|
if mmap_len < substr_len {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for i in 0..(mmap_len - substr_len) {
|
||||||
|
if &mmap[i..i + substr_len] == sub_str.as_bytes() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_instrumented(mmap: &Mmap, shmem_env_var: &str) -> bool {
|
||||||
|
mmap_has_substr(mmap, shmem_env_var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_executable_in_path(executable: &Path) -> Option<PathBuf> {
|
||||||
|
std::env::var_os("PATH").and_then(|paths| {
|
||||||
|
std::env::split_paths(&paths).find_map(|dir| {
|
||||||
|
let full_path = dir.join(executable);
|
||||||
|
if full_path.is_file() {
|
||||||
|
Some(full_path)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
136
fuzzers/libafl-fuzz/src/feedback/filepath.rs
Normal file
136
fuzzers/libafl-fuzz/src/feedback/filepath.rs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
fmt::{Debug, Formatter},
|
||||||
|
marker::PhantomData,
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
corpus::Testcase,
|
||||||
|
events::EventFirer,
|
||||||
|
executors::ExitKind,
|
||||||
|
feedbacks::{Feedback, FeedbackFactory},
|
||||||
|
inputs::Input,
|
||||||
|
observers::ObserversTuple,
|
||||||
|
state::State,
|
||||||
|
};
|
||||||
|
use libafl_bolts::{Error, Named};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A [`CustomFilepathToTestcaseFeedback`] takes a closure which can set the file name and path for the testcase.
|
||||||
|
/// Is never interesting (use with an OR).
|
||||||
|
/// Note: If used as part of the `Objective` chain, then it will only apply to testcases which are
|
||||||
|
/// `Objectives`, vice versa for `Feedback`.
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CustomFilepathToTestcaseFeedback<F, I, S>
|
||||||
|
where
|
||||||
|
I: Input,
|
||||||
|
S: State<Input = I>,
|
||||||
|
F: FnMut(&mut S, &mut Testcase<I>, &PathBuf) -> Result<(), Error>,
|
||||||
|
{
|
||||||
|
/// Closure that returns the filename.
|
||||||
|
func: F,
|
||||||
|
/// The root output directory
|
||||||
|
out_dir: PathBuf,
|
||||||
|
phantomm: PhantomData<(I, S)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, I, S> CustomFilepathToTestcaseFeedback<F, I, S>
|
||||||
|
where
|
||||||
|
I: Input,
|
||||||
|
S: State<Input = I>,
|
||||||
|
F: FnMut(&mut S, &mut Testcase<I>, &PathBuf) -> Result<(), Error>,
|
||||||
|
{
|
||||||
|
/// Create a new [`CustomFilepathToTestcaseFeedback`].
|
||||||
|
pub fn new(func: F, out_dir: PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
func,
|
||||||
|
out_dir,
|
||||||
|
phantomm: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, I, S, T> FeedbackFactory<CustomFilepathToTestcaseFeedback<F, I, S>, T>
|
||||||
|
for CustomFilepathToTestcaseFeedback<F, I, S>
|
||||||
|
where
|
||||||
|
I: Input,
|
||||||
|
S: State<Input = I>,
|
||||||
|
F: FnMut(&mut S, &mut Testcase<I>, &PathBuf) -> Result<(), Error> + Clone,
|
||||||
|
{
|
||||||
|
fn create_feedback(&self, _ctx: &T) -> CustomFilepathToTestcaseFeedback<F, I, S> {
|
||||||
|
Self {
|
||||||
|
func: self.func.clone(),
|
||||||
|
phantomm: self.phantomm,
|
||||||
|
out_dir: self.out_dir.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, I, S> Named for CustomFilepathToTestcaseFeedback<F, I, S>
|
||||||
|
where
|
||||||
|
I: Input,
|
||||||
|
S: State<Input = I>,
|
||||||
|
F: FnMut(&mut S, &mut Testcase<I>, &PathBuf) -> Result<(), Error>,
|
||||||
|
{
|
||||||
|
fn name(&self) -> &Cow<'static, str> {
|
||||||
|
static NAME: Cow<'static, str> = Cow::Borrowed("CustomFilepathToTestcaseFeedback");
|
||||||
|
&NAME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, I, S> Debug for CustomFilepathToTestcaseFeedback<F, I, S>
|
||||||
|
where
|
||||||
|
I: Input,
|
||||||
|
S: State<Input = I>,
|
||||||
|
F: FnMut(&mut S, &mut Testcase<I>, &PathBuf) -> Result<(), Error>,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("CustomFilepathToTestcaseFeedback")
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, I, S> Feedback<S> for CustomFilepathToTestcaseFeedback<F, I, S>
|
||||||
|
where
|
||||||
|
S: State<Input = I>,
|
||||||
|
F: FnMut(&mut S, &mut Testcase<S::Input>, &PathBuf) -> Result<(), Error>,
|
||||||
|
I: Input,
|
||||||
|
{
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
|
#[inline]
|
||||||
|
fn is_interesting<EM, OT>(
|
||||||
|
&mut self,
|
||||||
|
_state: &mut S,
|
||||||
|
_manager: &mut EM,
|
||||||
|
_input: &I,
|
||||||
|
_observers: &OT,
|
||||||
|
_exit_kind: &ExitKind,
|
||||||
|
) -> Result<bool, Error>
|
||||||
|
where
|
||||||
|
EM: EventFirer<State = S>,
|
||||||
|
{
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_metadata<EM, OT>(
|
||||||
|
&mut self,
|
||||||
|
state: &mut S,
|
||||||
|
_manager: &mut EM,
|
||||||
|
_observers: &OT,
|
||||||
|
testcase: &mut Testcase<<S>::Input>,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
|
EM: EventFirer<State = S>,
|
||||||
|
{
|
||||||
|
(self.func)(state, testcase, &self.out_dir)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "track_hit_feedbacks")]
|
||||||
|
#[inline]
|
||||||
|
fn last_result(&self) -> Result<bool, Error> {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
2
fuzzers/libafl-fuzz/src/feedback/mod.rs
Normal file
2
fuzzers/libafl-fuzz/src/feedback/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod filepath;
|
||||||
|
pub mod seed;
|
151
fuzzers/libafl-fuzz/src/feedback/seed.rs
Normal file
151
fuzzers/libafl-fuzz/src/feedback/seed.rs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
use std::{borrow::Cow, marker::PhantomData};
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
corpus::Testcase, events::EventFirer, executors::ExitKind, feedbacks::Feedback,
|
||||||
|
observers::ObserversTuple, state::State, Error,
|
||||||
|
};
|
||||||
|
use libafl_bolts::Named;
|
||||||
|
|
||||||
|
use crate::Opt;
|
||||||
|
|
||||||
|
/// A wrapper feedback used to determine actions for initial seeds.
|
||||||
|
/// Handles `AFL_EXIT_ON_SEED_ISSUES`, `AFL_IGNORE_SEED_ISSUES` & default afl-fuzz behavior
|
||||||
|
/// then, essentially becomes benign
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SeedFeedback<A, S>
|
||||||
|
where
|
||||||
|
A: Feedback<S>,
|
||||||
|
S: State,
|
||||||
|
{
|
||||||
|
/// Inner [`Feedback`]
|
||||||
|
pub inner: A,
|
||||||
|
ignore_timeouts: bool,
|
||||||
|
ignore_seed_issues: bool,
|
||||||
|
exit_on_seed_issues: bool,
|
||||||
|
phantom: PhantomData<S>,
|
||||||
|
done_loading_seeds: bool,
|
||||||
|
}
|
||||||
|
impl<A, S> SeedFeedback<A, S>
|
||||||
|
where
|
||||||
|
A: Feedback<S>,
|
||||||
|
S: State,
|
||||||
|
{
|
||||||
|
pub fn new(inner: A, opt: &Opt) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
ignore_timeouts: opt.ignore_seed_issues,
|
||||||
|
ignore_seed_issues: opt.ignore_seed_issues,
|
||||||
|
exit_on_seed_issues: opt.exit_on_seed_issues,
|
||||||
|
phantom: PhantomData,
|
||||||
|
done_loading_seeds: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, S> Feedback<S> for SeedFeedback<A, S>
|
||||||
|
where
|
||||||
|
A: Feedback<S>,
|
||||||
|
S: State,
|
||||||
|
{
|
||||||
|
fn init_state(&mut self, state: &mut S) -> Result<(), Error> {
|
||||||
|
self.inner.init_state(state)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
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>,
|
||||||
|
{
|
||||||
|
if !self.done_loading_seeds {
|
||||||
|
match exit_kind {
|
||||||
|
ExitKind::Timeout => {
|
||||||
|
if !self.ignore_timeouts {
|
||||||
|
if !self.ignore_seed_issues || self.exit_on_seed_issues {
|
||||||
|
return Err(Error::invalid_corpus(
|
||||||
|
"input led to a timeout; use AFL_IGNORE_SEED_ISSUES=true",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExitKind::Crash => {
|
||||||
|
if self.exit_on_seed_issues {
|
||||||
|
return Err(Error::invalid_corpus("input let to a crash; either omit AFL_EXIT_ON_SEED_ISSUES or set it to false."));
|
||||||
|
}
|
||||||
|
// We regard all crashes as uninteresting during seed loading
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let is_interesting = self
|
||||||
|
.inner
|
||||||
|
.is_interesting(state, manager, input, observers, exit_kind)?;
|
||||||
|
Ok(is_interesting)
|
||||||
|
}
|
||||||
|
/// Append to the testcase the generated metadata in case of a new corpus item
|
||||||
|
#[inline]
|
||||||
|
fn append_metadata<EM, OT>(
|
||||||
|
&mut self,
|
||||||
|
state: &mut S,
|
||||||
|
manager: &mut EM,
|
||||||
|
observers: &OT,
|
||||||
|
testcase: &mut Testcase<S::Input>,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
|
EM: EventFirer<State = S>,
|
||||||
|
{
|
||||||
|
self.inner
|
||||||
|
.append_metadata(state, manager, observers, testcase)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Discard the stored metadata in case that the testcase is not added to the corpus
|
||||||
|
#[inline]
|
||||||
|
fn discard_metadata(&mut self, state: &mut S, input: &S::Input) -> Result<(), Error> {
|
||||||
|
self.inner.discard_metadata(state, input)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(feature = "track_hit_feedbacks")]
|
||||||
|
fn last_result(&self) -> Result<bool, Error> {
|
||||||
|
self.inner.last_result()
|
||||||
|
}
|
||||||
|
#[cfg(feature = "track_hit_feedbacks")]
|
||||||
|
fn append_hit_feedbacks(&self, list: &mut Vec<Cow<'static, str>>) -> Result<(), Error> {
|
||||||
|
if self.inner.last_result()? {
|
||||||
|
self.inner.append_hit_feedbacks(list)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, A> Named for SeedFeedback<A, S>
|
||||||
|
where
|
||||||
|
A: Feedback<S>,
|
||||||
|
S: State,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn name(&self) -> &Cow<'static, str> {
|
||||||
|
static NAME: Cow<'static, str> = Cow::Borrowed("SeedFeedback");
|
||||||
|
&NAME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, A> SeedFeedback<A, S>
|
||||||
|
where
|
||||||
|
A: Feedback<S>,
|
||||||
|
S: State,
|
||||||
|
{
|
||||||
|
pub fn done_loading_seeds(&mut self) {
|
||||||
|
self.done_loading_seeds = true;
|
||||||
|
}
|
||||||
|
}
|
438
fuzzers/libafl-fuzz/src/fuzzer.rs
Normal file
438
fuzzers/libafl-fuzz/src/fuzzer.rs
Normal file
@ -0,0 +1,438 @@
|
|||||||
|
use std::{borrow::Cow, marker::PhantomData, path::PathBuf, time::Duration};
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
|
||||||
|
events::{
|
||||||
|
CentralizedEventManager, EventManagerHooksTuple, EventProcessor,
|
||||||
|
LlmpRestartingEventManager, ProgressReporter,
|
||||||
|
},
|
||||||
|
executors::forkserver::{ForkserverExecutor, ForkserverExecutorBuilder},
|
||||||
|
feedback_and, feedback_or, feedback_or_fast,
|
||||||
|
feedbacks::{ConstFeedback, CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
||||||
|
fuzzer::StdFuzzer,
|
||||||
|
inputs::BytesInput,
|
||||||
|
mutators::{
|
||||||
|
scheduled::havoc_mutations, tokens_mutations, AFLppRedQueen, StdScheduledMutator, Tokens,
|
||||||
|
},
|
||||||
|
observers::{CanTrack, HitcountsMapObserver, StdMapObserver, TimeObserver},
|
||||||
|
schedulers::{powersched::PowerSchedule, QueueScheduler, StdWeightedScheduler},
|
||||||
|
stages::{
|
||||||
|
mutational::MultiMutationalStage, CalibrationStage, ColorizationStage, IfStage,
|
||||||
|
StagesTuple, StdMutationalStage, StdPowerMutationalStage, SyncFromDiskStage,
|
||||||
|
},
|
||||||
|
state::{
|
||||||
|
HasCorpus, HasCurrentTestcase, HasExecutions, HasLastReportTime, HasStartTime, StdState,
|
||||||
|
UsesState,
|
||||||
|
},
|
||||||
|
Error, Fuzzer, HasFeedback, HasMetadata, SerdeAny,
|
||||||
|
};
|
||||||
|
use libafl_bolts::{
|
||||||
|
core_affinity::CoreId,
|
||||||
|
current_nanos, current_time,
|
||||||
|
fs::get_unique_std_input_file,
|
||||||
|
ownedref::OwnedRefMut,
|
||||||
|
rands::StdRand,
|
||||||
|
shmem::{ShMem, ShMemProvider, StdShMemProvider},
|
||||||
|
tuples::{tuple_list, Handled, Merge},
|
||||||
|
AsSliceMut,
|
||||||
|
};
|
||||||
|
use libafl_targets::{cmps::AFLppCmpLogMap, AFLppCmpLogObserver, AFLppCmplogTracingStage};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
afl_stats::AflStatsStage,
|
||||||
|
corpus::{set_corpus_filepath, set_solution_filepath},
|
||||||
|
env_parser::AFL_DEFAULT_MAP_SIZE,
|
||||||
|
feedback::{filepath::CustomFilepathToTestcaseFeedback, seed::SeedFeedback},
|
||||||
|
mutational_stage::SupportedMutationalStages,
|
||||||
|
scheduler::SupportedSchedulers,
|
||||||
|
Opt, AFL_DEFAULT_INPUT_LEN_MAX, AFL_DEFAULT_INPUT_LEN_MIN, SHMEM_ENV_VAR,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type LibaflFuzzState =
|
||||||
|
StdState<BytesInput, CachedOnDiskCorpus<BytesInput>, StdRand, OnDiskCorpus<BytesInput>>;
|
||||||
|
|
||||||
|
pub fn run_client<EMH, SP>(
|
||||||
|
state: Option<LibaflFuzzState>,
|
||||||
|
mut restarting_mgr: CentralizedEventManager<
|
||||||
|
LlmpRestartingEventManager<(), LibaflFuzzState, SP>,
|
||||||
|
EMH,
|
||||||
|
LibaflFuzzState,
|
||||||
|
SP,
|
||||||
|
>,
|
||||||
|
fuzzer_dir: &PathBuf,
|
||||||
|
core_id: CoreId,
|
||||||
|
opt: &Opt,
|
||||||
|
is_main_node: bool,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
EMH: EventManagerHooksTuple<LibaflFuzzState> + Copy + Clone,
|
||||||
|
SP: ShMemProvider,
|
||||||
|
{
|
||||||
|
// Create the shared memory map for comms with the forkserver
|
||||||
|
let mut shmem_provider = StdShMemProvider::new().unwrap();
|
||||||
|
let mut shmem = shmem_provider
|
||||||
|
.new_shmem(opt.map_size.unwrap_or(AFL_DEFAULT_MAP_SIZE))
|
||||||
|
.unwrap();
|
||||||
|
shmem.write_to_env(SHMEM_ENV_VAR).unwrap();
|
||||||
|
let shmem_buf = shmem.as_slice_mut();
|
||||||
|
|
||||||
|
// Create an observation channel to keep track of edges hit.
|
||||||
|
let edges_observer = unsafe {
|
||||||
|
HitcountsMapObserver::new(StdMapObserver::new("edges", shmem_buf)).track_indices()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a MapFeedback for coverage guided fuzzin'
|
||||||
|
let map_feedback = MaxMapFeedback::new(&edges_observer);
|
||||||
|
|
||||||
|
// Create the CalibrationStage; used to measure the stability of an input.
|
||||||
|
// We run the stage only if we are NOT doing sequential scheduling.
|
||||||
|
let calibration = IfStage::new(
|
||||||
|
|_, _, _, _| Ok(!opt.sequential_queue),
|
||||||
|
tuple_list!(CalibrationStage::new(&map_feedback)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a AFLStatsStage;
|
||||||
|
let afl_stats_stage = AflStatsStage::new(opt, fuzzer_dir.clone(), &edges_observer);
|
||||||
|
|
||||||
|
// Create an observation channel to keep track of the execution time.
|
||||||
|
let time_observer = TimeObserver::new("time");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Feedback to decide if the Input is "corpus worthy"
|
||||||
|
* We only check if it gives new coverage.
|
||||||
|
* The `TimeFeedback` is used to annotate the testcase with it's exec time.
|
||||||
|
* The `CustomFilepathToTestcaseFeedback is used to adhere to AFL++'s corpus format.
|
||||||
|
* The `Seedfeedback` is used during seed loading to adhere to AFL++'s handling of seeds
|
||||||
|
*/
|
||||||
|
let mut feedback = SeedFeedback::new(
|
||||||
|
feedback_or!(
|
||||||
|
map_feedback,
|
||||||
|
TimeFeedback::new(&time_observer),
|
||||||
|
CustomFilepathToTestcaseFeedback::new(set_corpus_filepath, fuzzer_dir.clone())
|
||||||
|
),
|
||||||
|
opt,
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Feedback to decide if the Input is "solution worthy".
|
||||||
|
* We check if it's a crash or a timeout (if we are configured to consider timeouts)
|
||||||
|
* The `CustomFilepathToTestcaseFeedback is used to adhere to AFL++'s corpus format.
|
||||||
|
* The `MaxMapFeedback` saves objectives only if they hit new edges
|
||||||
|
* */
|
||||||
|
let mut objective = feedback_or!(
|
||||||
|
feedback_and!(
|
||||||
|
feedback_or_fast!(
|
||||||
|
CrashFeedback::new(),
|
||||||
|
feedback_and!(
|
||||||
|
ConstFeedback::new(!opt.ignore_timeouts),
|
||||||
|
TimeoutFeedback::new()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
MaxMapFeedback::with_name("edges_objective", &edges_observer)
|
||||||
|
),
|
||||||
|
CustomFilepathToTestcaseFeedback::new(set_solution_filepath, fuzzer_dir.clone())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initialize our State if necessary
|
||||||
|
let mut state = state.unwrap_or_else(|| {
|
||||||
|
StdState::new(
|
||||||
|
StdRand::with_seed(current_nanos()),
|
||||||
|
// TODO: configure testcache size
|
||||||
|
CachedOnDiskCorpus::<BytesInput>::new(fuzzer_dir.join("queue"), 1000).unwrap(),
|
||||||
|
OnDiskCorpus::<BytesInput>::new(fuzzer_dir.clone()).unwrap(),
|
||||||
|
&mut feedback,
|
||||||
|
&mut objective,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create our Mutational Stage.
|
||||||
|
// We can either have a simple MutationalStage (for Queue scheduling)
|
||||||
|
// Or one that utilizes scheduling metadadata (Weighted Random scheduling)
|
||||||
|
let mutation = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
|
||||||
|
let mutational_stage = if opt.sequential_queue {
|
||||||
|
SupportedMutationalStages::StdMutational(StdMutationalStage::new(mutation), PhantomData)
|
||||||
|
} else {
|
||||||
|
SupportedMutationalStages::PowerMutational(
|
||||||
|
StdPowerMutationalStage::new(mutation),
|
||||||
|
PhantomData,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let strategy = opt.power_schedule.unwrap_or(PowerSchedule::EXPLORE);
|
||||||
|
|
||||||
|
// Create our ColorizationStage
|
||||||
|
let colorization = ColorizationStage::new(&edges_observer);
|
||||||
|
|
||||||
|
// Create our Scheduler
|
||||||
|
// Our scheduler can either be a Queue
|
||||||
|
// Or a "Weighted Random" which prioritizes entries that take less time and hit more edges
|
||||||
|
let scheduler;
|
||||||
|
if opt.sequential_queue {
|
||||||
|
scheduler = SupportedSchedulers::Queue(QueueScheduler::new(), PhantomData);
|
||||||
|
} else {
|
||||||
|
let mut weighted_scheduler =
|
||||||
|
StdWeightedScheduler::with_schedule(&mut state, &edges_observer, Some(strategy));
|
||||||
|
if opt.cycle_schedules {
|
||||||
|
weighted_scheduler = weighted_scheduler.cycling_scheduler();
|
||||||
|
}
|
||||||
|
// TODO: Go back to IndexesLenTimeMinimizerScheduler once AflScheduler is implemented for it.
|
||||||
|
scheduler = SupportedSchedulers::Weighted(weighted_scheduler, PhantomData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our Fuzzer
|
||||||
|
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||||
|
|
||||||
|
// Create the base Executor
|
||||||
|
let mut executor = base_executor(opt, &mut shmem_provider);
|
||||||
|
// Set a custom exit code to be interpreted as a Crash if configured.
|
||||||
|
if let Some(crash_exitcode) = opt.crash_exitcode {
|
||||||
|
executor = executor.crash_exitcode(crash_exitcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable autodict if configured
|
||||||
|
let mut tokens = Tokens::new();
|
||||||
|
if !opt.no_autodict {
|
||||||
|
executor = executor.autotokens(&mut tokens);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set a custom directory for the current Input if configured;
|
||||||
|
// May be used to provide a ram-disk etc..
|
||||||
|
if let Some(cur_input_dir) = &opt.cur_input_dir {
|
||||||
|
if opt.harness_input_type.is_none() {
|
||||||
|
return Err(Error::illegal_argument(
|
||||||
|
"cannot use AFL_TMPDIR with stdin input type.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
executor = executor.arg_input_file(cur_input_dir.join(get_unique_std_input_file()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize and build our Executor
|
||||||
|
let mut executor = executor
|
||||||
|
.build(tuple_list!(time_observer, edges_observer))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Load our seeds.
|
||||||
|
if state.must_load_initial_inputs() {
|
||||||
|
state
|
||||||
|
.load_initial_inputs_multicore(
|
||||||
|
&mut fuzzer,
|
||||||
|
&mut executor,
|
||||||
|
&mut restarting_mgr,
|
||||||
|
&[fuzzer_dir.join("queue")],
|
||||||
|
&core_id,
|
||||||
|
opt.cores.as_ref().expect("invariant; should never occur"),
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|err| panic!("Failed to load initial corpus! {err:?}"));
|
||||||
|
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||||
|
}
|
||||||
|
|
||||||
|
// We set IsInitialCorpusEntry as metadata for all initial testcases.
|
||||||
|
// Used in Cmplog stage if AFL_CMPLOG_ONLY_NEW.
|
||||||
|
if opt.cmplog_only_new {
|
||||||
|
for id in state.corpus().ids() {
|
||||||
|
let testcase = state.corpus().get(id).expect("should be present in Corpus");
|
||||||
|
testcase
|
||||||
|
.borrow_mut()
|
||||||
|
.add_metadata(IsInitialCorpusEntryMetadata {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the tokens to State
|
||||||
|
state.add_metadata(tokens);
|
||||||
|
|
||||||
|
// Set the start time of our Fuzzer
|
||||||
|
*state.start_time_mut() = current_time();
|
||||||
|
|
||||||
|
// Tell [`SeedFeedback`] that we're done loading seeds; rendering it benign.
|
||||||
|
fuzzer.feedback_mut().done_loading_seeds();
|
||||||
|
|
||||||
|
// Set LD_PRELOAD (Linux) && DYLD_INSERT_LIBRARIES (OSX) for target.
|
||||||
|
if let Some(preload_env) = &opt.afl_preload {
|
||||||
|
std::env::set_var("LD_PRELOAD", preload_env);
|
||||||
|
std::env::set_var("DYLD_INSERT_LIBRARIES", preload_env);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a Sync stage to sync from foreign fuzzers
|
||||||
|
let sync_stage = IfStage::new(
|
||||||
|
|_, _, _, _| Ok(is_main_node && !opt.foreign_sync_dirs.is_empty()),
|
||||||
|
tuple_list!(SyncFromDiskStage::with_from_file(
|
||||||
|
opt.foreign_sync_dirs.clone(),
|
||||||
|
opt.foreign_sync_interval
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a CmpLog executor if configured.
|
||||||
|
if let Some(ref cmplog_binary) = opt.cmplog_binary {
|
||||||
|
// The CmpLog map shared between the CmpLog observer and CmpLog executor
|
||||||
|
let mut cmplog_shmem = shmem_provider.uninit_on_shmem::<AFLppCmpLogMap>().unwrap();
|
||||||
|
|
||||||
|
// Let the Forkserver know the CmpLog shared memory map ID.
|
||||||
|
cmplog_shmem.write_to_env("__AFL_CMPLOG_SHM_ID").unwrap();
|
||||||
|
let cmpmap = unsafe { OwnedRefMut::from_shmem(&mut cmplog_shmem) };
|
||||||
|
|
||||||
|
// Create the CmpLog observer.
|
||||||
|
let cmplog_observer = AFLppCmpLogObserver::new("cmplog", cmpmap, true);
|
||||||
|
let cmplog_ref = cmplog_observer.handle();
|
||||||
|
|
||||||
|
// Create the CmpLog executor.
|
||||||
|
// Cmplog has 25% execution overhead so we give it double the timeout
|
||||||
|
let cmplog_executor = base_executor(opt, &mut shmem_provider)
|
||||||
|
.timeout(Duration::from_millis(opt.hang_timeout * 2))
|
||||||
|
.program(cmplog_binary)
|
||||||
|
.build(tuple_list!(cmplog_observer))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Create the CmpLog tracing stage.
|
||||||
|
let tracing = AFLppCmplogTracingStage::new(cmplog_executor, cmplog_ref);
|
||||||
|
|
||||||
|
// Create a randomic Input2State stage
|
||||||
|
let rq = MultiMutationalStage::new(AFLppRedQueen::with_cmplog_options(true, true));
|
||||||
|
|
||||||
|
// Create an IfStage and wrap the CmpLog stages in it.
|
||||||
|
// We run cmplog on the second fuzz run of the testcase.
|
||||||
|
// This stage checks if the testcase has been fuzzed more than twice, if so do not run cmplog.
|
||||||
|
// We also check if it is an initial corpus testcase
|
||||||
|
// and if run with AFL_CMPLOG_ONLY_NEW, then we avoid cmplog.
|
||||||
|
let cb = |_fuzzer: &mut _,
|
||||||
|
_executor: &mut _,
|
||||||
|
state: &mut LibaflFuzzState,
|
||||||
|
_event_manager: &mut _|
|
||||||
|
-> Result<bool, Error> {
|
||||||
|
let testcase = state.current_testcase()?;
|
||||||
|
if testcase.scheduled_count() == 1
|
||||||
|
|| (opt.cmplog_only_new && testcase.has_metadata::<IsInitialCorpusEntryMetadata>())
|
||||||
|
{
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
};
|
||||||
|
let cmplog = IfStage::new(cb, tuple_list!(colorization, tracing, rq));
|
||||||
|
|
||||||
|
// The order of the stages matter!
|
||||||
|
let mut stages = tuple_list!(
|
||||||
|
calibration,
|
||||||
|
cmplog,
|
||||||
|
mutational_stage,
|
||||||
|
afl_stats_stage,
|
||||||
|
sync_stage
|
||||||
|
);
|
||||||
|
|
||||||
|
// Run our fuzzer; WITH CmpLog
|
||||||
|
run_fuzzer_with_stages(
|
||||||
|
opt,
|
||||||
|
&mut fuzzer,
|
||||||
|
&mut stages,
|
||||||
|
&mut executor,
|
||||||
|
&mut state,
|
||||||
|
&mut restarting_mgr,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
// The order of the stages matter!
|
||||||
|
let mut stages = tuple_list!(calibration, mutational_stage, afl_stats_stage, sync_stage);
|
||||||
|
|
||||||
|
// Run our fuzzer; NO CmpLog
|
||||||
|
run_fuzzer_with_stages(
|
||||||
|
opt,
|
||||||
|
&mut fuzzer,
|
||||||
|
&mut stages,
|
||||||
|
&mut executor,
|
||||||
|
&mut state,
|
||||||
|
&mut restarting_mgr,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
// TODO: serialize state when exiting.
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_executor<'a>(
|
||||||
|
opt: &'a Opt,
|
||||||
|
shmem_provider: &'a mut StdShMemProvider,
|
||||||
|
) -> ForkserverExecutorBuilder<'a, StdShMemProvider> {
|
||||||
|
let mut executor = ForkserverExecutor::builder()
|
||||||
|
.program(opt.executable.clone())
|
||||||
|
.shmem_provider(shmem_provider)
|
||||||
|
.coverage_map_size(opt.map_size.unwrap_or(AFL_DEFAULT_MAP_SIZE))
|
||||||
|
.debug_child(opt.debug_child)
|
||||||
|
.is_persistent(opt.is_persistent)
|
||||||
|
.is_deferred_frksrv(opt.defer_forkserver)
|
||||||
|
.min_input_size(opt.min_input_len.unwrap_or(AFL_DEFAULT_INPUT_LEN_MIN))
|
||||||
|
.max_input_size(opt.max_input_len.unwrap_or(AFL_DEFAULT_INPUT_LEN_MAX))
|
||||||
|
.timeout(Duration::from_millis(opt.hang_timeout));
|
||||||
|
if let Some(target_env) = &opt.target_env {
|
||||||
|
executor = executor.envs(target_env);
|
||||||
|
}
|
||||||
|
if let Some(kill_signal) = opt.kill_signal {
|
||||||
|
executor = executor.kill_signal(kill_signal);
|
||||||
|
}
|
||||||
|
if let Some(harness_input_type) = &opt.harness_input_type {
|
||||||
|
executor = executor.parse_afl_cmdline([harness_input_type]);
|
||||||
|
}
|
||||||
|
executor
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fuzzer_target_mode(opt: &Opt) -> Cow<'static, str> {
|
||||||
|
let mut res = String::new();
|
||||||
|
if opt.unicorn_mode {
|
||||||
|
res = format!("{res}unicorn ");
|
||||||
|
}
|
||||||
|
if opt.qemu_mode {
|
||||||
|
res = format!("{res}qemu ");
|
||||||
|
}
|
||||||
|
if opt.forkserver_cs {
|
||||||
|
res = format!("{res}coresight ");
|
||||||
|
}
|
||||||
|
if opt.no_forkserver {
|
||||||
|
res = format!("{res}no_fsrv ");
|
||||||
|
}
|
||||||
|
if opt.crash_mode {
|
||||||
|
res = format!("{res}crash ");
|
||||||
|
}
|
||||||
|
if opt.is_persistent {
|
||||||
|
res = format!("{res}persistent ");
|
||||||
|
}
|
||||||
|
// TODO: not always shmem_testcase
|
||||||
|
res = format!("{res}shmem_testcase ");
|
||||||
|
if opt.defer_forkserver {
|
||||||
|
res = format!("{res}deferred ");
|
||||||
|
}
|
||||||
|
if !(opt.unicorn_mode
|
||||||
|
|| opt.qemu_mode
|
||||||
|
|| opt.forkserver_cs
|
||||||
|
|| opt.non_instrumented_mode
|
||||||
|
|| opt.no_forkserver
|
||||||
|
|| opt.crash_mode
|
||||||
|
|| opt.is_persistent
|
||||||
|
|| opt.defer_forkserver)
|
||||||
|
{
|
||||||
|
res = format!("{res}default");
|
||||||
|
}
|
||||||
|
Cow::Owned(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, SerdeAny)]
|
||||||
|
pub struct IsInitialCorpusEntryMetadata {}
|
||||||
|
|
||||||
|
pub fn run_fuzzer_with_stages<Z, ST, E, EM>(
|
||||||
|
opt: &Opt,
|
||||||
|
fuzzer: &mut Z,
|
||||||
|
stages: &mut ST,
|
||||||
|
executor: &mut E,
|
||||||
|
state: &mut <Z as UsesState>::State,
|
||||||
|
mgr: &mut EM,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
Z: Fuzzer<E, EM, ST>,
|
||||||
|
E: UsesState<State = Z::State>,
|
||||||
|
EM: ProgressReporter<State = Z::State> + EventProcessor<E, Z>,
|
||||||
|
ST: StagesTuple<E, EM, Z::State, Z>,
|
||||||
|
<Z as UsesState>::State: HasLastReportTime + HasExecutions + HasMetadata,
|
||||||
|
{
|
||||||
|
if opt.bench_just_one {
|
||||||
|
fuzzer.fuzz_loop_for(stages, executor, state, mgr, 1)?;
|
||||||
|
} else {
|
||||||
|
fuzzer.fuzz_loop(stages, executor, state, mgr)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
38
fuzzers/libafl-fuzz/src/hooks.rs
Normal file
38
fuzzers/libafl-fuzz/src/hooks.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use libafl::{
|
||||||
|
events::{Event, EventManagerHook},
|
||||||
|
state::{State, Stoppable},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
use libafl_bolts::ClientId;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct LibAflFuzzEventHook {
|
||||||
|
exit_on_solution: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LibAflFuzzEventHook {
|
||||||
|
pub fn new(exit_on_solution: bool) -> Self {
|
||||||
|
Self { exit_on_solution }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> EventManagerHook<S> for LibAflFuzzEventHook
|
||||||
|
where
|
||||||
|
S: State + Stoppable,
|
||||||
|
{
|
||||||
|
fn pre_exec(
|
||||||
|
&mut self,
|
||||||
|
state: &mut S,
|
||||||
|
_client_id: ClientId,
|
||||||
|
event: &Event<S::Input>,
|
||||||
|
) -> Result<bool, Error> {
|
||||||
|
if self.exit_on_solution && matches!(event, Event::Objective { .. }) {
|
||||||
|
// TODO: dump state
|
||||||
|
state.request_stop();
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
fn post_exec(&mut self, _state: &mut S, _client_id: ClientId) -> Result<bool, Error> {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
279
fuzzers/libafl-fuzz/src/main.rs
Normal file
279
fuzzers/libafl-fuzz/src/main.rs
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
#![deny(clippy::pedantic)]
|
||||||
|
#![allow(clippy::unsafe_derive_deserialize)]
|
||||||
|
#![allow(clippy::ptr_arg)]
|
||||||
|
#![allow(clippy::unnecessary_wraps)]
|
||||||
|
#![allow(clippy::module_name_repetitions)]
|
||||||
|
#![allow(clippy::similar_names)]
|
||||||
|
#![allow(clippy::too_many_lines)]
|
||||||
|
#![allow(clippy::struct_excessive_bools)]
|
||||||
|
|
||||||
|
use std::{collections::HashMap, path::PathBuf, time::Duration};
|
||||||
|
mod afl_stats;
|
||||||
|
mod env_parser;
|
||||||
|
mod feedback;
|
||||||
|
mod mutational_stage;
|
||||||
|
mod scheduler;
|
||||||
|
use clap::Parser;
|
||||||
|
use corpus::{check_autoresume, create_dir_if_not_exists, remove_main_node_file};
|
||||||
|
mod corpus;
|
||||||
|
mod executor;
|
||||||
|
mod fuzzer;
|
||||||
|
mod hooks;
|
||||||
|
use env_parser::parse_envs;
|
||||||
|
use fuzzer::run_client;
|
||||||
|
use libafl::{
|
||||||
|
events::{CentralizedLauncher, EventConfig},
|
||||||
|
monitors::MultiMonitor,
|
||||||
|
schedulers::powersched::PowerSchedule,
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
use libafl_bolts::{
|
||||||
|
core_affinity::{CoreId, Cores},
|
||||||
|
shmem::{ShMemProvider, StdShMemProvider},
|
||||||
|
};
|
||||||
|
use nix::sys::signal::Signal;
|
||||||
|
|
||||||
|
const AFL_DEFAULT_INPUT_LEN_MAX: usize = 1_048_576;
|
||||||
|
const AFL_DEFAULT_INPUT_LEN_MIN: usize = 1;
|
||||||
|
const OUTPUT_GRACE: u64 = 25;
|
||||||
|
pub const AFL_DEFAULT_BROKER_PORT: u16 = 1337;
|
||||||
|
const PERSIST_SIG: &str = "##SIG_AFL_PERSISTENT##";
|
||||||
|
const DEFER_SIG: &str = "##SIG_AFL_DEFER_FORKSRV##";
|
||||||
|
const SHMEM_ENV_VAR: &str = "__AFL_SHM_ID";
|
||||||
|
static AFL_HARNESS_FILE_INPUT: &str = "@@";
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
let mut opt = Opt::parse();
|
||||||
|
parse_envs(&mut opt).expect("invalid configuration");
|
||||||
|
executor::check_binary(&mut opt, SHMEM_ENV_VAR).expect("binary to be valid");
|
||||||
|
|
||||||
|
// Create the shared memory map provider for LLMP
|
||||||
|
let shmem_provider = StdShMemProvider::new().unwrap();
|
||||||
|
|
||||||
|
// Create our Monitor
|
||||||
|
let monitor = MultiMonitor::new(|s| println!("{s}"));
|
||||||
|
|
||||||
|
opt.auto_resume = if opt.auto_resume {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
opt.input_dir.as_os_str() == "-"
|
||||||
|
};
|
||||||
|
|
||||||
|
create_dir_if_not_exists(&opt.output_dir).expect("could not create output directory");
|
||||||
|
|
||||||
|
// TODO: we need to think about the fuzzer naming scheme since they can be configured in
|
||||||
|
// different ways (ASAN/mutators) etc.... and how to autoresume appropriately.
|
||||||
|
// Currently we do AFL style resume with hardcoded names.
|
||||||
|
// Currently, we will error if we don't find our assigned dir.
|
||||||
|
// This will also not work if we use core 1-8 and then later, 16-24
|
||||||
|
// since fuzzer names are using core_ids
|
||||||
|
match CentralizedLauncher::builder()
|
||||||
|
.shmem_provider(shmem_provider)
|
||||||
|
.configuration(EventConfig::from_name("default"))
|
||||||
|
.monitor(monitor)
|
||||||
|
.main_run_client(|state: Option<_>, mgr: _, core_id: CoreId| {
|
||||||
|
println!("run primary client on core {}", core_id.0);
|
||||||
|
let fuzzer_dir = opt.output_dir.join("fuzzer_main");
|
||||||
|
check_autoresume(&fuzzer_dir, &opt.input_dir, opt.auto_resume).unwrap();
|
||||||
|
let res = run_client(state, mgr, &fuzzer_dir, core_id, &opt, true);
|
||||||
|
let _ = remove_main_node_file(&fuzzer_dir);
|
||||||
|
res
|
||||||
|
})
|
||||||
|
.secondary_run_client(|state: Option<_>, mgr: _, core_id: CoreId| {
|
||||||
|
println!("run secondary client on core {}", core_id.0);
|
||||||
|
let fuzzer_dir = opt
|
||||||
|
.output_dir
|
||||||
|
.join(format!("fuzzer_secondary_{}", core_id.0));
|
||||||
|
check_autoresume(&fuzzer_dir, &opt.input_dir, opt.auto_resume).unwrap();
|
||||||
|
run_client(state, mgr, &fuzzer_dir, core_id, &opt, false)
|
||||||
|
})
|
||||||
|
.cores(&opt.cores.clone().expect("invariant; should never occur"))
|
||||||
|
.broker_port(opt.broker_port.unwrap_or(AFL_DEFAULT_BROKER_PORT))
|
||||||
|
.build()
|
||||||
|
.launch()
|
||||||
|
{
|
||||||
|
Ok(()) => unreachable!(),
|
||||||
|
Err(Error::ShuttingDown) => println!("Fuzzing stopped by user. Good bye."),
|
||||||
|
Err(err) => panic!("Failed to run launcher: {err:?}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
|
#[derive(Debug, Parser, Clone)]
|
||||||
|
#[command(
|
||||||
|
name = "afl-fuzz",
|
||||||
|
about = "afl-fuzz, now with LibAFL!",
|
||||||
|
author = "aarnav <aarnavbos@gmail.com>"
|
||||||
|
)]
|
||||||
|
/// The Configuration
|
||||||
|
struct Opt {
|
||||||
|
executable: PathBuf,
|
||||||
|
|
||||||
|
#[arg(value_parser = validate_harness_input_stdin)]
|
||||||
|
harness_input_type: Option<&'static str>,
|
||||||
|
|
||||||
|
// NOTE: afl-fuzz does not accept multiple input directories
|
||||||
|
#[arg(short = 'i')]
|
||||||
|
input_dir: PathBuf,
|
||||||
|
#[arg(short = 'o')]
|
||||||
|
output_dir: PathBuf,
|
||||||
|
#[arg(short = 'p')]
|
||||||
|
power_schedule: Option<PowerSchedule>,
|
||||||
|
#[arg(short = 'c')]
|
||||||
|
cmplog_binary: Option<PathBuf>,
|
||||||
|
#[arg(short = 'F', num_args = 32)]
|
||||||
|
foreign_sync_dirs: Vec<PathBuf>,
|
||||||
|
// Environment + CLI variables
|
||||||
|
#[arg(short = 'G')]
|
||||||
|
max_input_len: Option<usize>,
|
||||||
|
#[arg(short = 'g')]
|
||||||
|
min_input_len: Option<usize>,
|
||||||
|
#[arg(short = 'Z')]
|
||||||
|
sequential_queue: bool,
|
||||||
|
// TODO: enforce
|
||||||
|
#[arg(short = 'm')]
|
||||||
|
memory_limit: Option<usize>,
|
||||||
|
// TODO: enforce
|
||||||
|
#[arg(short = 'V')]
|
||||||
|
fuzz_for_seconds: Option<usize>,
|
||||||
|
// Environment Variables
|
||||||
|
#[clap(skip)]
|
||||||
|
bench_just_one: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
bench_until_crash: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
hang_timeout: u64,
|
||||||
|
#[clap(skip)]
|
||||||
|
debug_child: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
is_persistent: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
no_autodict: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
kill_signal: Option<Signal>,
|
||||||
|
#[clap(skip)]
|
||||||
|
map_size: Option<usize>,
|
||||||
|
#[clap(skip)]
|
||||||
|
ignore_timeouts: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
cur_input_dir: Option<PathBuf>,
|
||||||
|
#[clap(skip)]
|
||||||
|
crash_exitcode: Option<i8>,
|
||||||
|
#[clap(skip)]
|
||||||
|
target_env: Option<HashMap<String, String>>,
|
||||||
|
#[clap(skip)]
|
||||||
|
cycle_schedules: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
cmplog_only_new: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
afl_preload: Option<String>,
|
||||||
|
#[clap(skip)]
|
||||||
|
auto_resume: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
skip_bin_check: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
defer_forkserver: bool,
|
||||||
|
/// in seconds
|
||||||
|
#[clap(skip)]
|
||||||
|
stats_interval: u64,
|
||||||
|
|
||||||
|
// New Environment Variables
|
||||||
|
#[clap(skip)]
|
||||||
|
cores: Option<Cores>,
|
||||||
|
#[clap(skip)]
|
||||||
|
broker_port: Option<u16>,
|
||||||
|
|
||||||
|
// Seed config
|
||||||
|
#[clap(skip)]
|
||||||
|
exit_on_seed_issues: bool,
|
||||||
|
// renamed from IGNORE_SEED_PROBLEMS
|
||||||
|
#[clap(skip)]
|
||||||
|
ignore_seed_issues: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
crash_seed_as_new_crash: bool,
|
||||||
|
|
||||||
|
// Cmplog config
|
||||||
|
// TODO: actually use this config
|
||||||
|
#[arg(short='l', value_parser=parse_cmplog_args)]
|
||||||
|
cmplog_opts: Option<CmplogOpts>,
|
||||||
|
|
||||||
|
#[clap(skip)]
|
||||||
|
foreign_sync_interval: Duration,
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
#[clap(skip)]
|
||||||
|
frida_persistent_addr: Option<String>,
|
||||||
|
#[clap(skip)]
|
||||||
|
qemu_custom_bin: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
cs_custom_bin: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
use_wine: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
uses_asan: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
frida_mode: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
qemu_mode: bool,
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[clap(skip)]
|
||||||
|
nyx_mode: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
unicorn_mode: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
forkserver_cs: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
no_forkserver: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
crash_mode: bool,
|
||||||
|
#[clap(skip)]
|
||||||
|
non_instrumented_mode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_harness_input_stdin(s: &str) -> Result<&'static str, String> {
|
||||||
|
if s != "@@" {
|
||||||
|
return Err("Unknown harness input type. Use \"@@\" for file, omit for stdin ".to_string());
|
||||||
|
}
|
||||||
|
Ok(AFL_HARNESS_FILE_INPUT)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CmplogOpts {
|
||||||
|
file_size: CmplogFileSize,
|
||||||
|
arith_solving: bool,
|
||||||
|
transform_solving: bool,
|
||||||
|
exterme_transform_solving: bool,
|
||||||
|
random_colorization: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum CmplogFileSize {
|
||||||
|
Small,
|
||||||
|
Larger,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for CmplogFileSize {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
if value.contains('1') {
|
||||||
|
Self::Small
|
||||||
|
} else if value.contains('3') {
|
||||||
|
Self::All
|
||||||
|
} else {
|
||||||
|
Self::Larger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_cmplog_args(s: &str) -> Result<CmplogOpts, String> {
|
||||||
|
Ok(CmplogOpts {
|
||||||
|
file_size: s.into(),
|
||||||
|
arith_solving: s.contains('A'),
|
||||||
|
transform_solving: s.contains('T'),
|
||||||
|
exterme_transform_solving: s.contains('X'),
|
||||||
|
random_colorization: s.contains('R'),
|
||||||
|
})
|
||||||
|
}
|
116
fuzzers/libafl-fuzz/src/mutational_stage.rs
Normal file
116
fuzzers/libafl-fuzz/src/mutational_stage.rs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
use std::{borrow::Cow, marker::PhantomData};
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
inputs::Input,
|
||||||
|
mutators::Mutator,
|
||||||
|
stages::{mutational::MutatedTransform, MutationalStage, Stage},
|
||||||
|
state::{HasCorpus, HasRand, State, UsesState},
|
||||||
|
Error, Evaluator, HasNamedMetadata,
|
||||||
|
};
|
||||||
|
use libafl_bolts::Named;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SupportedMutationalStages<S, SM, P, E, EM, M, I, Z> {
|
||||||
|
StdMutational(SM, PhantomData<(S, I, M, EM, Z, E)>),
|
||||||
|
PowerMutational(P, PhantomData<(S, I, M, EM, Z, E)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, SM, P, E, EM, M, I, Z> MutationalStage<E, EM, I, M, Z>
|
||||||
|
for SupportedMutationalStages<S, SM, P, E, EM, M, I, Z>
|
||||||
|
where
|
||||||
|
E: UsesState<State = S>,
|
||||||
|
EM: UsesState<State = S>,
|
||||||
|
M: Mutator<I, S>,
|
||||||
|
Z: Evaluator<E, EM, State = S>,
|
||||||
|
I: MutatedTransform<S::Input, S> + Clone + Input,
|
||||||
|
SM: MutationalStage<E, EM, I, M, Z, State = S>,
|
||||||
|
P: MutationalStage<E, EM, I, M, Z, State = S>,
|
||||||
|
S: State<Input = I> + HasRand + HasCorpus + HasNamedMetadata,
|
||||||
|
{
|
||||||
|
/// The mutator, added to this stage
|
||||||
|
#[inline]
|
||||||
|
fn mutator(&self) -> &M {
|
||||||
|
match self {
|
||||||
|
Self::StdMutational(m, _) => m.mutator(),
|
||||||
|
Self::PowerMutational(p, _) => p.mutator(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of mutators, added to this stage (as mutable ref)
|
||||||
|
#[inline]
|
||||||
|
fn mutator_mut(&mut self) -> &mut M {
|
||||||
|
match self {
|
||||||
|
Self::StdMutational(m, _) => m.mutator_mut(),
|
||||||
|
Self::PowerMutational(p, _) => p.mutator_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the number of iterations as a random number
|
||||||
|
fn iterations(&self, state: &mut S) -> Result<usize, Error> {
|
||||||
|
match self {
|
||||||
|
Self::StdMutational(m, _) => m.iterations(state),
|
||||||
|
Self::PowerMutational(p, _) => p.iterations(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, SM, P, E, EM, M, I, Z> UsesState for SupportedMutationalStages<S, SM, P, E, EM, M, I, Z>
|
||||||
|
where
|
||||||
|
S: State + HasRand,
|
||||||
|
{
|
||||||
|
type State = S;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, SM, P, E, EM, M, I, Z> Named for SupportedMutationalStages<S, SM, P, E, EM, M, I, Z>
|
||||||
|
where
|
||||||
|
SM: Named,
|
||||||
|
P: Named,
|
||||||
|
{
|
||||||
|
fn name(&self) -> &Cow<'static, str> {
|
||||||
|
match self {
|
||||||
|
Self::StdMutational(m, _) => m.name(),
|
||||||
|
Self::PowerMutational(p, _) => p.name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, SM, P, E, EM, M, I, Z> Stage<E, EM, Z> for SupportedMutationalStages<S, SM, P, E, EM, M, I, Z>
|
||||||
|
where
|
||||||
|
E: UsesState<State = S>,
|
||||||
|
EM: UsesState<State = S>,
|
||||||
|
M: Mutator<I, S>,
|
||||||
|
Z: Evaluator<E, EM, State = S>,
|
||||||
|
I: MutatedTransform<S::Input, S> + Clone + Input,
|
||||||
|
SM: MutationalStage<E, EM, I, M, Z, State = S>,
|
||||||
|
P: MutationalStage<E, EM, I, M, Z, State = S>,
|
||||||
|
S: State<Input = I> + HasRand + HasCorpus + HasNamedMetadata,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
#[allow(clippy::let_and_return)]
|
||||||
|
fn perform(
|
||||||
|
&mut self,
|
||||||
|
fuzzer: &mut Z,
|
||||||
|
executor: &mut E,
|
||||||
|
state: &mut S,
|
||||||
|
manager: &mut EM,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match self {
|
||||||
|
Self::StdMutational(m, _) => m.perform(fuzzer, executor, state, manager),
|
||||||
|
Self::PowerMutational(p, _) => p.perform(fuzzer, executor, state, manager),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_restart(&mut self, state: &mut S) -> Result<bool, Error> {
|
||||||
|
match self {
|
||||||
|
Self::StdMutational(m, _) => m.should_restart(state),
|
||||||
|
Self::PowerMutational(p, _) => p.should_restart(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> {
|
||||||
|
match self {
|
||||||
|
Self::StdMutational(m, _) => m.clear_progress(state),
|
||||||
|
Self::PowerMutational(p, _) => p.clear_progress(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
fuzzers/libafl-fuzz/src/scheduler.rs
Normal file
114
fuzzers/libafl-fuzz/src/scheduler.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
corpus::{CorpusId, HasTestcase, Testcase},
|
||||||
|
inputs::UsesInput,
|
||||||
|
observers::ObserversTuple,
|
||||||
|
schedulers::{HasQueueCycles, RemovableScheduler, Scheduler},
|
||||||
|
state::{HasCorpus, HasRand, State, UsesState},
|
||||||
|
Error, HasMetadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum SupportedSchedulers<S, Q, W> {
|
||||||
|
Queue(Q, PhantomData<(S, Q, W)>),
|
||||||
|
Weighted(W, PhantomData<(S, Q, W)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, Q, W> UsesState for SupportedSchedulers<S, Q, W>
|
||||||
|
where
|
||||||
|
S: State + HasRand + HasCorpus + HasMetadata + HasTestcase,
|
||||||
|
{
|
||||||
|
type State = S;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, Q, W> RemovableScheduler for SupportedSchedulers<S, Q, W>
|
||||||
|
where
|
||||||
|
S: UsesInput + HasTestcase + HasMetadata + HasCorpus + HasRand + State,
|
||||||
|
Q: Scheduler<State = S> + RemovableScheduler,
|
||||||
|
W: Scheduler<State = S> + RemovableScheduler,
|
||||||
|
{
|
||||||
|
fn on_remove(
|
||||||
|
&mut self,
|
||||||
|
state: &mut Self::State,
|
||||||
|
id: CorpusId,
|
||||||
|
testcase: &Option<Testcase<<Self::State as UsesInput>::Input>>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match self {
|
||||||
|
Self::Queue(queue, _) => queue.on_remove(state, id, testcase),
|
||||||
|
Self::Weighted(weighted, _) => weighted.on_remove(state, id, testcase),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_replace(
|
||||||
|
&mut self,
|
||||||
|
state: &mut Self::State,
|
||||||
|
id: CorpusId,
|
||||||
|
prev: &Testcase<<Self::State as UsesInput>::Input>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match self {
|
||||||
|
Self::Queue(queue, _) => queue.on_replace(state, id, prev),
|
||||||
|
Self::Weighted(weighted, _) => weighted.on_replace(state, id, prev),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, Q, W> Scheduler for SupportedSchedulers<S, Q, W>
|
||||||
|
where
|
||||||
|
S: UsesInput + HasTestcase + HasMetadata + HasCorpus + HasRand + State,
|
||||||
|
Q: Scheduler<State = S>,
|
||||||
|
W: Scheduler<State = S>,
|
||||||
|
{
|
||||||
|
fn on_add(&mut self, state: &mut Self::State, id: CorpusId) -> Result<(), Error> {
|
||||||
|
match self {
|
||||||
|
Self::Queue(queue, _) => queue.on_add(state, id),
|
||||||
|
Self::Weighted(weighted, _) => weighted.on_add(state, id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the next entry in the queue
|
||||||
|
fn next(&mut self, state: &mut Self::State) -> Result<CorpusId, Error> {
|
||||||
|
match self {
|
||||||
|
Self::Queue(queue, _) => queue.next(state),
|
||||||
|
Self::Weighted(weighted, _) => weighted.next(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn on_evaluation<OTB>(
|
||||||
|
&mut self,
|
||||||
|
state: &mut Self::State,
|
||||||
|
input: &<Self::State as UsesInput>::Input,
|
||||||
|
observers: &OTB,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
OTB: ObserversTuple<Self::State>,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Queue(queue, _) => queue.on_evaluation(state, input, observers),
|
||||||
|
Self::Weighted(weighted, _) => weighted.on_evaluation(state, input, observers),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current_scheduled(
|
||||||
|
&mut self,
|
||||||
|
state: &mut Self::State,
|
||||||
|
next_id: Option<CorpusId>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match self {
|
||||||
|
Self::Queue(queue, _) => queue.set_current_scheduled(state, next_id),
|
||||||
|
Self::Weighted(weighted, _) => weighted.set_current_scheduled(state, next_id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, Q, W> HasQueueCycles for SupportedSchedulers<S, Q, W>
|
||||||
|
where
|
||||||
|
S: UsesInput + HasTestcase + HasMetadata + HasCorpus + HasRand + State,
|
||||||
|
Q: Scheduler<State = S> + HasQueueCycles,
|
||||||
|
W: Scheduler<State = S> + HasQueueCycles,
|
||||||
|
{
|
||||||
|
fn queue_cycles(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
Self::Queue(queue, _) => queue.queue_cycles(),
|
||||||
|
Self::Weighted(weighted, _) => weighted.queue_cycles(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
fuzzers/libafl-fuzz/test/seeds/init
Normal file
1
fuzzers/libafl-fuzz/test/seeds/init
Normal file
@ -0,0 +1 @@
|
|||||||
|
0
|
1
fuzzers/libafl-fuzz/test/seeds_cmplog/init
Normal file
1
fuzzers/libafl-fuzz/test/seeds_cmplog/init
Normal file
@ -0,0 +1 @@
|
|||||||
|
00000000000000000000000000000000
|
38
fuzzers/libafl-fuzz/test/test-cmplog.c
Normal file
38
fuzzers/libafl-fuzz/test/test-cmplog.c
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t i) {
|
||||||
|
|
||||||
|
if (i < 15) return -1;
|
||||||
|
if (buf[0] != 'A') return 0;
|
||||||
|
int *icmp = (int *)(buf + 1);
|
||||||
|
if (*icmp != 0x69694141) return 0;
|
||||||
|
if (memcmp(buf + 5, "1234EF", 6) == 0) abort();
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __AFL_COMPILER
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
|
unsigned char buf[1024];
|
||||||
|
ssize_t i;
|
||||||
|
while (__AFL_LOOP(1000)) {
|
||||||
|
|
||||||
|
i = read(0, (char *)buf, sizeof(buf) - 1);
|
||||||
|
if (i > 0) buf[i] = 0;
|
||||||
|
LLVMFuzzerTestOneInput(buf, i);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
83
fuzzers/libafl-fuzz/test/test-instr.c
Normal file
83
fuzzers/libafl-fuzz/test/test-instr.c
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
american fuzzy lop++ - a trivial program to test the build
|
||||||
|
--------------------------------------------------------
|
||||||
|
Originally written by Michal Zalewski
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
Copyright 2019-2024 AFLplusplus Project. All rights reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at:
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#ifdef TEST_SHARED_OBJECT
|
||||||
|
#define main main_exported
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
|
||||||
|
int fd = 0, cnt;
|
||||||
|
char buff[8];
|
||||||
|
char *buf = buff;
|
||||||
|
|
||||||
|
// we support command line parameter and stdin
|
||||||
|
if (argc == 2) {
|
||||||
|
|
||||||
|
buf = argv[1];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (argc >= 3 && strcmp(argv[1], "-f") == 0) {
|
||||||
|
|
||||||
|
if ((fd = open(argv[2], O_RDONLY)) < 0) {
|
||||||
|
|
||||||
|
fprintf(stderr, "Error: unable to open %s\n", argv[2]);
|
||||||
|
exit(-1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((cnt = read(fd, buf, sizeof(buf) - 1)) < 1) {
|
||||||
|
|
||||||
|
printf("Hum?\n");
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[cnt] = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getenv("AFL_DEBUG")) fprintf(stderr, "test-instr: %s\n", buf);
|
||||||
|
|
||||||
|
// we support three input cases (plus a 4th if stdin is used but there is no
|
||||||
|
// input)
|
||||||
|
switch (buf[0]) {
|
||||||
|
|
||||||
|
case '0':
|
||||||
|
printf("Looks like a zero to me!\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '1':
|
||||||
|
printf("Pretty sure that is a one!\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
printf("Neither one or zero? How quaint!\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
25
fuzzers/libafl-fuzz/test/test.sh
Executable file
25
fuzzers/libafl-fuzz/test/test.sh
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
export AFL_DIR_NAME="./AFLplusplus-stable"
|
||||||
|
export AFL_CC_PATH="$AFL_DIR_NAME/afl-clang-fast"
|
||||||
|
export LIBAFL_FUZZ_PATH="../target/release/libafl-fuzz"
|
||||||
|
export LLVM_CONFIG="llvm-config-18"
|
||||||
|
if [ ! -d "$AFL_DIR_NAME" ]; then
|
||||||
|
wget https://github.com/AFLplusplus/AFLplusplus/archive/refs/heads/stable.zip
|
||||||
|
unzip stable.zip
|
||||||
|
cd $AFL_DIR_NAME
|
||||||
|
LLVM_CONFIG=$LLVM_CONFIG make
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
|
||||||
|
AFL_PATH=$AFL_DIR_NAME $AFL_CC_PATH $AFL_DIR_NAME/test-instr.c -o out-instr
|
||||||
|
|
||||||
|
AFL_CORES=1 LLVM_CONFIG=llvm-config-18 AFL_STATS_INTERVAL=1 AFL_NUM_CORES=1 timeout 5 $LIBAFL_FUZZ_PATH -i ./seeds -o ./output $(pwd)/out-instr
|
||||||
|
test -n "$( ls output/fuzzer_main/queue/id:000002* 2>/dev/null )" || exit 1
|
||||||
|
test -n "$( ls output/fuzzer_main/fuzzer_stats 2>/dev/null )" || exit 1
|
||||||
|
test -n "$( ls output/fuzzer_main/plot_data 2>/dev/null )" || exit 1
|
||||||
|
test -n "$( ls output/fuzzer_main/crashe2s 2>/dev/null )" || exit 1
|
||||||
|
test -n "$( ls output/fuzzer_main/hangs 2>/dev/null )" || exit 1
|
@ -195,6 +195,8 @@ serial_test = { version = "3", optional = true, default-features = false, featur
|
|||||||
|
|
||||||
# Document all features of this crate (for `cargo doc`)
|
# Document all features of this crate (for `cargo doc`)
|
||||||
document-features = { version = "0.2", optional = true }
|
document-features = { version = "0.2", optional = true }
|
||||||
|
# Optional
|
||||||
|
clap = {version = "4.5", optional = true}
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2" # For (*nix) libc
|
libc = "0.2" # For (*nix) libc
|
||||||
|
@ -161,6 +161,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for Schedulers which track queue cycles
|
||||||
|
pub trait HasQueueCycles: Scheduler
|
||||||
|
where
|
||||||
|
Self::State: HasCorpus,
|
||||||
|
{
|
||||||
|
/// The amount of cycles the scheduler has completed.
|
||||||
|
fn queue_cycles(&self) -> u64;
|
||||||
|
}
|
||||||
|
|
||||||
/// The scheduler define how the fuzzer requests a testcase from the corpus.
|
/// The scheduler define how the fuzzer requests a testcase from the corpus.
|
||||||
/// It has hooks to corpus add/replace/remove to allow complex scheduling algorithms to collect data.
|
/// It has hooks to corpus add/replace/remove to allow complex scheduling algorithms to collect data.
|
||||||
pub trait Scheduler: UsesState
|
pub trait Scheduler: UsesState
|
||||||
|
@ -13,7 +13,7 @@ use crate::{
|
|||||||
corpus::{Corpus, CorpusId, HasTestcase, Testcase},
|
corpus::{Corpus, CorpusId, HasTestcase, Testcase},
|
||||||
inputs::UsesInput,
|
inputs::UsesInput,
|
||||||
observers::{MapObserver, ObserversTuple},
|
observers::{MapObserver, ObserversTuple},
|
||||||
schedulers::{AflScheduler, RemovableScheduler, Scheduler},
|
schedulers::{AflScheduler, HasQueueCycles, RemovableScheduler, Scheduler},
|
||||||
state::{HasCorpus, State, UsesState},
|
state::{HasCorpus, State, UsesState},
|
||||||
Error, HasMetadata,
|
Error, HasMetadata,
|
||||||
};
|
};
|
||||||
@ -157,6 +157,7 @@ impl SchedulerMetadata {
|
|||||||
|
|
||||||
/// The power schedule to use
|
/// The power schedule to use
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||||
pub enum PowerSchedule {
|
pub enum PowerSchedule {
|
||||||
/// The `explore` power schedule
|
/// The `explore` power schedule
|
||||||
EXPLORE,
|
EXPLORE,
|
||||||
@ -177,6 +178,7 @@ pub enum PowerSchedule {
|
|||||||
/// and here we DON'T actually calculate the power (we do it in the stage)
|
/// and here we DON'T actually calculate the power (we do it in the stage)
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PowerQueueScheduler<C, O, S> {
|
pub struct PowerQueueScheduler<C, O, S> {
|
||||||
|
queue_cycles: u64,
|
||||||
strat: PowerSchedule,
|
strat: PowerSchedule,
|
||||||
map_observer_handle: Handle<C>,
|
map_observer_handle: Handle<C>,
|
||||||
last_hash: usize,
|
last_hash: usize,
|
||||||
@ -236,6 +238,17 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<C, O, S> HasQueueCycles for PowerQueueScheduler<C, O, S>
|
||||||
|
where
|
||||||
|
S: HasCorpus + HasMetadata + HasTestcase + State,
|
||||||
|
O: MapObserver,
|
||||||
|
C: AsRef<O>,
|
||||||
|
{
|
||||||
|
fn queue_cycles(&self) -> u64 {
|
||||||
|
self.queue_cycles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<C, O, S> Scheduler for PowerQueueScheduler<C, O, S>
|
impl<C, O, S> Scheduler for PowerQueueScheduler<C, O, S>
|
||||||
where
|
where
|
||||||
S: HasCorpus + HasMetadata + HasTestcase + State,
|
S: HasCorpus + HasMetadata + HasTestcase + State,
|
||||||
@ -270,8 +283,9 @@ where
|
|||||||
if let Some(next) = state.corpus().next(*cur) {
|
if let Some(next) = state.corpus().next(*cur) {
|
||||||
next
|
next
|
||||||
} else {
|
} else {
|
||||||
|
self.queue_cycles += 1;
|
||||||
let psmeta = state.metadata_mut::<SchedulerMetadata>()?;
|
let psmeta = state.metadata_mut::<SchedulerMetadata>()?;
|
||||||
psmeta.set_queue_cycles(psmeta.queue_cycles() + 1);
|
psmeta.set_queue_cycles(self.queue_cycles());
|
||||||
state.corpus().first().unwrap()
|
state.corpus().first().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -309,6 +323,7 @@ where
|
|||||||
state.add_metadata::<SchedulerMetadata>(SchedulerMetadata::new(Some(strat)));
|
state.add_metadata::<SchedulerMetadata>(SchedulerMetadata::new(Some(strat)));
|
||||||
}
|
}
|
||||||
PowerQueueScheduler {
|
PowerQueueScheduler {
|
||||||
|
queue_cycles: 0,
|
||||||
strat,
|
strat,
|
||||||
map_observer_handle: map_observer.handle(),
|
map_observer_handle: map_observer.handle(),
|
||||||
last_hash: 0,
|
last_hash: 0,
|
||||||
|
@ -5,7 +5,7 @@ use core::marker::PhantomData;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
corpus::{Corpus, CorpusId, HasTestcase},
|
corpus::{Corpus, CorpusId, HasTestcase},
|
||||||
schedulers::{RemovableScheduler, Scheduler},
|
schedulers::{HasQueueCycles, RemovableScheduler, Scheduler},
|
||||||
state::{HasCorpus, State, UsesState},
|
state::{HasCorpus, State, UsesState},
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
@ -13,6 +13,8 @@ use crate::{
|
|||||||
/// Walk the corpus in a queue-like fashion
|
/// Walk the corpus in a queue-like fashion
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct QueueScheduler<S> {
|
pub struct QueueScheduler<S> {
|
||||||
|
queue_cycles: u64,
|
||||||
|
runs_in_current_cycle: u64,
|
||||||
phantom: PhantomData<S>,
|
phantom: PhantomData<S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +57,12 @@ where
|
|||||||
.map(|id| state.corpus().next(id))
|
.map(|id| state.corpus().next(id))
|
||||||
.flatten()
|
.flatten()
|
||||||
.unwrap_or_else(|| state.corpus().first().unwrap());
|
.unwrap_or_else(|| state.corpus().first().unwrap());
|
||||||
|
|
||||||
|
self.runs_in_current_cycle += 1;
|
||||||
|
// TODO deal with corpus_counts decreasing due to removals
|
||||||
|
if self.runs_in_current_cycle >= state.corpus().count() as u64 {
|
||||||
|
self.queue_cycles += 1;
|
||||||
|
}
|
||||||
self.set_current_scheduled(state, Some(id))?;
|
self.set_current_scheduled(state, Some(id))?;
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
@ -66,6 +74,8 @@ impl<S> QueueScheduler<S> {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
runs_in_current_cycle: 0,
|
||||||
|
queue_cycles: 0,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,6 +87,15 @@ impl<S> Default for QueueScheduler<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S> HasQueueCycles for QueueScheduler<S>
|
||||||
|
where
|
||||||
|
S: HasCorpus + HasTestcase + State,
|
||||||
|
{
|
||||||
|
fn queue_cycles(&self) -> u64 {
|
||||||
|
self.queue_cycles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -19,7 +19,7 @@ use crate::{
|
|||||||
schedulers::{
|
schedulers::{
|
||||||
powersched::{PowerSchedule, SchedulerMetadata},
|
powersched::{PowerSchedule, SchedulerMetadata},
|
||||||
testcase_score::{CorpusWeightTestcaseScore, TestcaseScore},
|
testcase_score::{CorpusWeightTestcaseScore, TestcaseScore},
|
||||||
AflScheduler, RemovableScheduler, Scheduler,
|
AflScheduler, HasQueueCycles, RemovableScheduler, Scheduler,
|
||||||
},
|
},
|
||||||
state::{HasCorpus, HasRand, State, UsesState},
|
state::{HasCorpus, HasRand, State, UsesState},
|
||||||
Error, HasMetadata,
|
Error, HasMetadata,
|
||||||
@ -100,6 +100,7 @@ pub struct WeightedScheduler<C, F, O, S> {
|
|||||||
strat: Option<PowerSchedule>,
|
strat: Option<PowerSchedule>,
|
||||||
map_observer_handle: Handle<C>,
|
map_observer_handle: Handle<C>,
|
||||||
last_hash: usize,
|
last_hash: usize,
|
||||||
|
queue_cycles: u64,
|
||||||
phantom: PhantomData<(F, O, S)>,
|
phantom: PhantomData<(F, O, S)>,
|
||||||
/// Cycle `PowerSchedule` on completion of every queue cycle.
|
/// Cycle `PowerSchedule` on completion of every queue cycle.
|
||||||
cycle_schedules: bool,
|
cycle_schedules: bool,
|
||||||
@ -128,6 +129,7 @@ where
|
|||||||
strat,
|
strat,
|
||||||
map_observer_handle: map_observer.handle(),
|
map_observer_handle: map_observer.handle(),
|
||||||
last_hash: 0,
|
last_hash: 0,
|
||||||
|
queue_cycles: 0,
|
||||||
table_invalidated: true,
|
table_invalidated: true,
|
||||||
cycle_schedules: false,
|
cycle_schedules: false,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
@ -307,6 +309,18 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<C, F, O, S> HasQueueCycles for WeightedScheduler<C, F, O, S>
|
||||||
|
where
|
||||||
|
F: TestcaseScore<S>,
|
||||||
|
O: MapObserver,
|
||||||
|
S: HasCorpus + HasMetadata + HasRand + HasTestcase + State,
|
||||||
|
C: AsRef<O> + Named,
|
||||||
|
{
|
||||||
|
fn queue_cycles(&self) -> u64 {
|
||||||
|
self.queue_cycles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<C, F, O, S> Scheduler for WeightedScheduler<C, F, O, S>
|
impl<C, F, O, S> Scheduler for WeightedScheduler<C, F, O, S>
|
||||||
where
|
where
|
||||||
F: TestcaseScore<S>,
|
F: TestcaseScore<S>,
|
||||||
@ -369,8 +383,9 @@ where
|
|||||||
|
|
||||||
// Update depth
|
// Update depth
|
||||||
if runs_in_current_cycle >= corpus_counts {
|
if runs_in_current_cycle >= corpus_counts {
|
||||||
|
self.queue_cycles += 1;
|
||||||
let psmeta = state.metadata_mut::<SchedulerMetadata>()?;
|
let psmeta = state.metadata_mut::<SchedulerMetadata>()?;
|
||||||
psmeta.set_queue_cycles(psmeta.queue_cycles() + 1);
|
psmeta.set_queue_cycles(self.queue_cycles());
|
||||||
if self.cycle_schedules {
|
if self.cycle_schedules {
|
||||||
self.cycle_schedule(psmeta)?;
|
self.cycle_schedule(psmeta)?;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! The [`SyncFromDiskStage`] is a stage that imports inputs from disk for e.g. sync with AFL
|
//! The [`SyncFromDiskStage`] is a stage that imports inputs from disk for e.g. sync with AFL
|
||||||
|
|
||||||
use alloc::borrow::{Cow, ToOwned};
|
use alloc::borrow::{Cow, ToOwned};
|
||||||
use core::marker::PhantomData;
|
use core::{marker::PhantomData, time::Duration};
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -33,7 +33,7 @@ use crate::{
|
|||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct SyncFromDiskMetadata {
|
pub struct SyncFromDiskMetadata {
|
||||||
/// The last time the sync was done
|
/// The last time the sync was done
|
||||||
pub last_time: SystemTime,
|
pub last_time: Duration,
|
||||||
/// The paths that are left to sync
|
/// The paths that are left to sync
|
||||||
pub left_to_sync: Vec<PathBuf>,
|
pub left_to_sync: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@ libafl_bolts::impl_serdeany!(SyncFromDiskMetadata);
|
|||||||
impl SyncFromDiskMetadata {
|
impl SyncFromDiskMetadata {
|
||||||
/// Create a new [`struct@SyncFromDiskMetadata`]
|
/// Create a new [`struct@SyncFromDiskMetadata`]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(last_time: SystemTime, left_to_sync: Vec<PathBuf>) -> Self {
|
pub fn new(last_time: Duration, left_to_sync: Vec<PathBuf>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
last_time,
|
last_time,
|
||||||
left_to_sync,
|
left_to_sync,
|
||||||
@ -58,8 +58,9 @@ pub const SYNC_FROM_DISK_STAGE_NAME: &str = "sync";
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SyncFromDiskStage<CB, E, EM, Z> {
|
pub struct SyncFromDiskStage<CB, E, EM, Z> {
|
||||||
name: Cow<'static, str>,
|
name: Cow<'static, str>,
|
||||||
sync_dir: PathBuf,
|
sync_dirs: Vec<PathBuf>,
|
||||||
load_callback: CB,
|
load_callback: CB,
|
||||||
|
interval: Duration,
|
||||||
phantom: PhantomData<(E, EM, Z)>,
|
phantom: PhantomData<(E, EM, Z)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,54 +93,51 @@ where
|
|||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
manager: &mut EM,
|
manager: &mut EM,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
log::debug!("Syncing from disk: {:?}", self.sync_dir);
|
|
||||||
let last = state
|
let last = state
|
||||||
.metadata_map()
|
.metadata_map()
|
||||||
.get::<SyncFromDiskMetadata>()
|
.get::<SyncFromDiskMetadata>()
|
||||||
.map(|m| m.last_time);
|
.map(|m| m.last_time);
|
||||||
|
|
||||||
if let (Some(max_time), mut new_files) = self.load_from_directory(None, &last)? {
|
if let Some(last) = last {
|
||||||
if last.is_none() {
|
if (last + self.interval) < current_time() {
|
||||||
state
|
return Ok(());
|
||||||
.metadata_map_mut()
|
|
||||||
.insert(SyncFromDiskMetadata::new(max_time, new_files));
|
|
||||||
} else {
|
|
||||||
state
|
|
||||||
.metadata_map_mut()
|
|
||||||
.get_mut::<SyncFromDiskMetadata>()
|
|
||||||
.unwrap()
|
|
||||||
.last_time = max_time;
|
|
||||||
state
|
|
||||||
.metadata_map_mut()
|
|
||||||
.get_mut::<SyncFromDiskMetadata>()
|
|
||||||
.unwrap()
|
|
||||||
.left_to_sync
|
|
||||||
.append(&mut new_files);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sync_from_disk_metadata) =
|
let max_time = match last {
|
||||||
state.metadata_map_mut().get_mut::<SyncFromDiskMetadata>()
|
None => None,
|
||||||
{
|
Some(last) => Some(last + self.interval),
|
||||||
// Iterate over the paths of files left to sync.
|
};
|
||||||
// By keeping track of these files, we ensure that no file is missed during synchronization,
|
let new_max_time = max_time.unwrap_or(current_time());
|
||||||
// even in the event of a target restart.
|
|
||||||
let to_sync = sync_from_disk_metadata.left_to_sync.clone();
|
let mut new_files = vec![];
|
||||||
log::debug!("Number of files to sync: {:?}", to_sync.len());
|
for dir in &self.sync_dirs {
|
||||||
for path in to_sync {
|
log::debug!("Syncing from dir: {:?}", dir);
|
||||||
let input = (self.load_callback)(fuzzer, state, &path)?;
|
let new_dir_files = self.load_from_directory(dir, &max_time)?;
|
||||||
// Removing each path from the `left_to_sync` Vec before evaluating
|
new_files.extend(new_dir_files);
|
||||||
// prevents duplicate processing and ensures that each file is evaluated only once. This approach helps
|
}
|
||||||
// avoid potential infinite loops that may occur if a file is an objective.
|
*state.metadata_mut::<SyncFromDiskMetadata>().unwrap() = SyncFromDiskMetadata {
|
||||||
state
|
last_time: new_max_time,
|
||||||
.metadata_map_mut()
|
left_to_sync: new_files,
|
||||||
.get_mut::<SyncFromDiskMetadata>()
|
};
|
||||||
.unwrap()
|
let sync_from_disk_metadata = state.metadata_mut::<SyncFromDiskMetadata>().unwrap();
|
||||||
.left_to_sync
|
// Iterate over the paths of files left to sync.
|
||||||
.retain(|p| p != &path);
|
// By keeping track of these files, we ensure that no file is missed during synchronization,
|
||||||
log::debug!("Evaluating: {:?}", path);
|
// even in the event of a target restart.
|
||||||
fuzzer.evaluate_input(state, executor, manager, input)?;
|
let to_sync = sync_from_disk_metadata.left_to_sync.clone();
|
||||||
}
|
log::debug!("Number of files to sync: {:?}", to_sync.len());
|
||||||
|
for path in to_sync {
|
||||||
|
let input = (self.load_callback)(fuzzer, state, &path)?;
|
||||||
|
// Removing each path from the `left_to_sync` Vec before evaluating
|
||||||
|
// prevents duplicate processing and ensures that each file is evaluated only once. This approach helps
|
||||||
|
// avoid potential infinite loops that may occur if a file is an objective.
|
||||||
|
state
|
||||||
|
.metadata_mut::<SyncFromDiskMetadata>()
|
||||||
|
.unwrap()
|
||||||
|
.left_to_sync
|
||||||
|
.retain(|p| p != &path);
|
||||||
|
log::debug!("Evaluating: {:?}", path);
|
||||||
|
fuzzer.evaluate_input(state, executor, manager, input)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "introspection")]
|
#[cfg(feature = "introspection")]
|
||||||
@ -164,28 +162,24 @@ where
|
|||||||
impl<CB, E, EM, Z> SyncFromDiskStage<CB, E, EM, Z> {
|
impl<CB, E, EM, Z> SyncFromDiskStage<CB, E, EM, Z> {
|
||||||
/// Creates a new [`SyncFromDiskStage`]
|
/// Creates a new [`SyncFromDiskStage`]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(sync_dir: PathBuf, load_callback: CB, name: &str) -> Self {
|
pub fn new(sync_dirs: Vec<PathBuf>, load_callback: CB, interval: Duration, name: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: Cow::Owned(SYNC_FROM_DISK_STAGE_NAME.to_owned() + ":" + name),
|
name: Cow::Owned(SYNC_FROM_DISK_STAGE_NAME.to_owned() + ":" + name),
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
sync_dir,
|
sync_dirs,
|
||||||
|
interval,
|
||||||
load_callback,
|
load_callback,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::only_used_in_recursion)]
|
||||||
fn load_from_directory(
|
fn load_from_directory(
|
||||||
&self,
|
&self,
|
||||||
path: Option<PathBuf>,
|
path: &PathBuf,
|
||||||
last: &Option<SystemTime>,
|
last: &Option<Duration>,
|
||||||
) -> Result<(Option<SystemTime>, Vec<PathBuf>), Error> {
|
) -> Result<Vec<PathBuf>, Error> {
|
||||||
let mut max_time = None;
|
|
||||||
let mut left_to_sync = Vec::<PathBuf>::new();
|
let mut left_to_sync = Vec::<PathBuf>::new();
|
||||||
let in_dir = match path {
|
for entry in fs::read_dir(path)? {
|
||||||
Some(p) => p,
|
|
||||||
None => self.sync_dir.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for entry in fs::read_dir(in_dir)? {
|
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
let attributes = fs::metadata(&path);
|
let attributes = fs::metadata(&path);
|
||||||
@ -198,26 +192,21 @@ impl<CB, E, EM, Z> SyncFromDiskStage<CB, E, EM, Z> {
|
|||||||
|
|
||||||
if attr.is_file() && attr.len() > 0 {
|
if attr.is_file() && attr.len() > 0 {
|
||||||
if let Ok(time) = attr.modified() {
|
if let Ok(time) = attr.modified() {
|
||||||
if let Some(l) = last {
|
if let Some(last) = last {
|
||||||
if time.duration_since(*l).is_err() || time == *l {
|
if time.duration_since(SystemTime::UNIX_EPOCH).unwrap() < *last {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
max_time = Some(max_time.map_or(time, |t: SystemTime| t.max(time)));
|
|
||||||
log::info!("Syncing file: {:?}", path);
|
log::info!("Syncing file: {:?}", path);
|
||||||
left_to_sync.push(path.clone());
|
left_to_sync.push(path.clone());
|
||||||
}
|
}
|
||||||
} else if attr.is_dir() {
|
} else if attr.is_dir() {
|
||||||
let (dir_max_time, dir_left_to_sync) =
|
let dir_left_to_sync = self.load_from_directory(&entry.path(), last)?;
|
||||||
self.load_from_directory(Some(entry.path()), last)?;
|
|
||||||
if let Some(time) = dir_max_time {
|
|
||||||
max_time = Some(max_time.map_or(time, |t: SystemTime| t.max(time)));
|
|
||||||
}
|
|
||||||
left_to_sync.extend(dir_left_to_sync);
|
left_to_sync.extend(dir_left_to_sync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((max_time, left_to_sync))
|
Ok(left_to_sync)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,7 +222,7 @@ where
|
|||||||
{
|
{
|
||||||
/// Creates a new [`SyncFromDiskStage`] invoking `Input::from_file` to load inputs
|
/// Creates a new [`SyncFromDiskStage`] invoking `Input::from_file` to load inputs
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_from_file(sync_dir: PathBuf) -> Self {
|
pub fn with_from_file(sync_dirs: Vec<PathBuf>, interval: Duration) -> Self {
|
||||||
fn load_callback<S: UsesInput, Z>(
|
fn load_callback<S: UsesInput, Z>(
|
||||||
_: &mut Z,
|
_: &mut Z,
|
||||||
_: &mut S,
|
_: &mut S,
|
||||||
@ -242,8 +231,9 @@ where
|
|||||||
Input::from_file(p)
|
Input::from_file(p)
|
||||||
}
|
}
|
||||||
Self {
|
Self {
|
||||||
|
interval,
|
||||||
name: Cow::Borrowed(SYNC_FROM_DISK_STAGE_NAME),
|
name: Cow::Borrowed(SYNC_FROM_DISK_STAGE_NAME),
|
||||||
sync_dir,
|
sync_dirs,
|
||||||
load_callback: load_callback::<_, _>,
|
load_callback: load_callback::<_, _>,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,28 @@ pub fn dup(fd: RawFd) -> Result<RawFd, Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Derived from https://github.com/RustPython/RustPython/blob/7996a10116681e9f85eda03413d5011b805e577f/stdlib/src/resource.rs#L113
|
||||||
|
// LICENSE: MIT https://github.com/RustPython/RustPython/commit/37355d612a451fba7fef8f13a1b9fdd51310b37e
|
||||||
|
/// Get the peak rss (Resident Set Size) of the all child processes
|
||||||
|
/// that have terminated and been waited for
|
||||||
|
#[cfg(all(unix, feature = "std"))]
|
||||||
|
pub fn peak_rss_mb_child_processes() -> Result<i64, Error> {
|
||||||
|
use core::mem;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use libc::{rusage, RUSAGE_CHILDREN};
|
||||||
|
|
||||||
|
let rss = unsafe {
|
||||||
|
let mut rusage = mem::MaybeUninit::<rusage>::uninit();
|
||||||
|
if libc::getrusage(RUSAGE_CHILDREN, rusage.as_mut_ptr()) == -1 {
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
} else {
|
||||||
|
Ok(rusage.assume_init())
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
Ok(rss.ru_maxrss >> 10)
|
||||||
|
}
|
||||||
|
|
||||||
/// "Safe" wrapper around dup2
|
/// "Safe" wrapper around dup2
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
Loading…
x
Reference in New Issue
Block a user