use candid::Principal;
use chrono::Local;
use ic_management_canister_types::CanisterId;
use libafl::feedback_or;
use libafl::feedbacks::{ExitKindFeedback, TimeoutFeedback};
use pocket_ic::PocketIc;
use std::fs::{self, File};
use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc;
use crate::custom::oom_exit_kind::OomLogic;
use crate::libafl::{
Evaluator,
corpus::CachedOnDiskCorpus,
events::SimpleEventManager,
executors::{ExitKind, inprocess::InProcessExecutor},
feedbacks::{CrashFeedback, map::AflMapFeedback},
fuzzer::{Fuzzer, StdFuzzer},
inputs::BytesInput,
mutators::{HavocScheduledMutator, havoc_mutations},
observers::map::{StdMapObserver, hitcount_map::HitcountsMapObserver},
schedulers::QueueScheduler,
stages::{AflStatsStage, CalibrationStage, mutational::StdMutationalStage},
state::StdState,
};
use crate::libafl::monitors::SimpleMonitor;
use crate::libafl_bolts::{current_nanos, rands::StdRand, tuples::tuple_list};
use crate::constants::COVERAGE_FN_EXPORT_NAME;
use crate::fuzzer::FuzzerState;
pub trait FuzzerStateProvider {
fn get_fuzzer_state(&self) -> &FuzzerState;
}
pub trait FuzzerOrchestrator: FuzzerStateProvider {
fn init(&mut self);
fn setup(&self) {}
fn execute(&self, input: BytesInput) -> ExitKind;
fn get_state_machine(&self) -> Arc<PocketIc> {
self.get_fuzzer_state().get_state_machine()
}
fn get_coverage_canister_id(&self) -> CanisterId {
self.get_fuzzer_state().get_coverage_canister_id()
}
fn input_dir(&self) -> PathBuf {
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR is not set");
let input_dir = PathBuf::from(out_dir)
.join("artifacts")
.join(self.get_fuzzer_state().name())
.join(Local::now().format("%Y%m%d_%H%M").to_string())
.join("input");
fs::create_dir_all(&input_dir)
.unwrap_or_else(|e| panic!("Failed to create input directory {input_dir:?}: {e}"));
println!("Input directory: {input_dir:?}");
input_dir
}
fn crashes_dir(&self) -> PathBuf {
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR is not set");
let crashes_dir = PathBuf::from(out_dir)
.join("artifacts")
.join(self.get_fuzzer_state().name())
.join(Local::now().format("%Y%m%d_%H%M").to_string())
.join("crashes");
fs::create_dir_all(&crashes_dir)
.unwrap_or_else(|e| panic!("Failed to create crashes directory {crashes_dir:?}: {e}"));
println!("Crashes directory: {crashes_dir:?}");
crashes_dir
}
fn corpus_dir(&self) -> PathBuf;
#[allow(static_mut_refs)]
fn set_coverage_map(&self) {
let test = self.get_state_machine();
let result = test.update_call(
self.get_coverage_canister_id(),
Principal::anonymous(),
COVERAGE_FN_EXPORT_NAME,
vec![],
);
if let Ok(result) = result {
unsafe { crate::instrumentation::COVERAGE_MAP.copy_from_slice(&result) };
}
}
fn get_coverage_map(&self) -> &'static mut [u8] {
unsafe { crate::instrumentation::COVERAGE_MAP }
}
fn run(&mut self) {
self.init();
let mut harness = |input: &BytesInput| {
self.setup();
let result = self.execute(input.clone());
self.set_coverage_map();
result
};
let hitcount_map_observer = HitcountsMapObserver::new(unsafe {
StdMapObserver::new("coverage_map", self.get_coverage_map())
});
let afl_map_feedback = AflMapFeedback::new(&hitcount_map_observer);
let mut feedback = afl_map_feedback;
let calibration_stage = CalibrationStage::new(&feedback);
let crash_feedback = CrashFeedback::new();
let timeout_feedback = TimeoutFeedback::new();
let oom_feedback: ExitKindFeedback<OomLogic> = ExitKindFeedback::new();
let mut objective = feedback_or!(crash_feedback, timeout_feedback, oom_feedback);
let stats_stage = AflStatsStage::builder()
.map_observer(&hitcount_map_observer)
.build()
.unwrap();
let mut state = StdState::new(
StdRand::with_seed(current_nanos()),
CachedOnDiskCorpus::new(self.input_dir(), 512).unwrap(),
CachedOnDiskCorpus::new(self.crashes_dir(), 512).unwrap(),
&mut feedback,
&mut objective,
)
.unwrap();
let mon = SimpleMonitor::new(|s| println!("{s}"));
let mut mgr = SimpleEventManager::new(mon);
let scheduler = QueueScheduler::new();
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
let mut executor = InProcessExecutor::new(
&mut harness,
tuple_list!(hitcount_map_observer),
&mut fuzzer,
&mut state,
&mut mgr,
)
.expect("Failed to create the Executor");
let paths = fs::read_dir(self.corpus_dir()).unwrap();
for path in paths {
let p = path.unwrap().path();
let mut f = File::open(p.clone()).unwrap();
let mut buffer = Vec::new();
f.read_to_end(&mut buffer).unwrap();
fuzzer
.evaluate_input(
&mut state,
&mut executor,
&mut mgr,
&BytesInput::new(buffer),
)
.unwrap();
}
let mutator = HavocScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(
calibration_stage,
StdMutationalStage::new(mutator),
stats_stage
);
fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
.expect("Error in the fuzzing loop");
}
fn test_one_input(&mut self, bytes: Vec<u8>) {
self.init();
self.setup();
let result = self.execute(BytesInput::new(bytes));
println!("Execution result: {result:?}");
}
}