From b330c264cac08741f94e6d6790b4c8bb66f5b2ba Mon Sep 17 00:00:00 2001 From: Alvise de Faveri Tron Date: Mon, 31 Mar 2025 23:59:47 +0000 Subject: [PATCH 1/3] Upgrade LibAFL to 0.15.1 --- Cargo.toml | 5 +- LibAFL | 2 +- src/assembler.rs | 6 +- src/bin/sim-fuzzer.rs | 77 ++++++++++++---------- src/calibration.rs | 148 +++++++++++++++++++++++++----------------- src/fuzz_ui.rs | 2 +- src/generator.rs | 27 ++++---- src/monitor.rs | 18 +++-- src/mutator.rs | 30 +++++---- src/parser.rs | 3 +- src/program_input.rs | 8 ++- 11 files changed, 190 insertions(+), 136 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1aeb16..7810a8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,12 @@ hashbrown = "0.13.2" #libafl = { version = "0.10.0", features = ["fork", "errors_backtrace"] } libm = "0.2.7" log = "0.4.17" -nix = "0.26.2" +nix = "0.29" num-traits = "0.2.15" postcard = "1.0.4" rand = "0.8.5" serde = "1.0.163" tui = "0.19.0" -libafl = { path = "LibAFL/libafl", features = ["fork", "errors_backtrace"] } +libafl = { path = "LibAFL/libafl", features = ["fork", "errors_backtrace", "prelude"] } +libafl_bolts = { path = "LibAFL/libafl_bolts" } diff --git a/LibAFL b/LibAFL index c8964a4..2d14a50 160000 --- a/LibAFL +++ b/LibAFL @@ -1 +1 @@ -Subproject commit c8964a431a2bfcfabb663e0ea776aad710476699 +Subproject commit 2d14a50e1d22ddc87fb77ca31cd6a0fc627481e0 diff --git a/src/assembler.rs b/src/assembler.rs index f14a802..675f406 100644 --- a/src/assembler.rs +++ b/src/assembler.rs @@ -15,8 +15,8 @@ pub fn assemble_instructions(input: &Vec) -> Vec { #[cfg(test)] mod tests { - use libafl::prelude::Rand; - use libafl::prelude::Xoshiro256StarRand; + use libafl_bolts::prelude::Rand; + use libafl_bolts::prelude::Xoshiro256StarRand; use crate::generator::InstGenerator; use crate::instructions; @@ -66,7 +66,7 @@ mod tests { let mut insts = Vec::::new(); - for _ in 0..rng.below(5) { + for _ in 0..rng.below(nonzero!(5)) { let inst = generator.generate_instruction::( &mut rng, &instructions::sets::riscv_g(), diff --git a/src/bin/sim-fuzzer.rs b/src/bin/sim-fuzzer.rs index 9bcdeb4..5ad7d63 100644 --- a/src/bin/sim-fuzzer.rs +++ b/src/bin/sim-fuzzer.rs @@ -7,24 +7,16 @@ use std::{ process, sync::{Arc, Mutex}, }; - +use libafl::observers::CanTrack; use clap::Parser; use libafl::{ - bolts::{ - current_nanos, - rands::StdRand, - shmem::{ShMem, ShMemProvider, UnixShMemProvider}, - tuples::tuple_list, - AsMutSlice, - }, corpus::{OnDiskCorpus}, - executors::forkserver::{ForkserverExecutor, TimeoutForkserverExecutor}, + executors::forkserver::{ForkserverExecutor}, feedback_or, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, fuzzer::{Fuzzer, StdFuzzer}, mutators::StdScheduledMutator, observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, - prelude::current_time, schedulers::{ powersched::PowerSchedule, IndexesLenTimeMinimizerScheduler, StdWeightedScheduler, }, @@ -32,12 +24,22 @@ use libafl::{ state::StdState, Error, Evaluator, }; +use libafl_bolts::{ + current_nanos, + rands::StdRand, + shmem::{ShMem, ShMemProvider, UnixShMemProvider}, + tuples::tuple_list, + current_time, + core_affinity::{Cores}, + AsSliceMut, + tuples::Handled, +}; use libafl::{ events::ProgressReporter, - prelude::{Cores, EventConfig, Launcher, LlmpRestartingEventManager}, + prelude::{EventConfig, Launcher, LlmpRestartingEventManager, LlmpShouldSaveState, ClientDescription, NopTargetBytesConverter}, }; use libafl::{ - prelude::{ondisk::OnDiskMetadataFormat, CoreId}, + prelude::{ondisk::OnDiskMetadataFormat}, }; use nix::sys::signal::Signal; use riscv_mutator::{ @@ -174,9 +176,9 @@ pub fn main() { let arguments = &args.arguments[1..]; let scheduler_map: HashMap = HashMap::from([ - ("explore".to_owned(), PowerSchedule::EXPLORE), - ("fast".to_owned(), PowerSchedule::FAST), - ("exploit".to_owned(), PowerSchedule::EXPLOIT), + ("explore".to_owned(), PowerSchedule::explore()), + ("fast".to_owned(), PowerSchedule::fast()), + ("exploit".to_owned(), PowerSchedule::exploit()), ]); let scheduler = scheduler_map.get(&args.scheduler); if scheduler.is_none() { @@ -243,26 +245,32 @@ fn fuzz( let shmem_provider = UnixShMemProvider::new().expect("Failed to init shared memory"); let mut shmem_provider_client = shmem_provider.clone(); - let mut run_client = - |_state: Option<_>, mut mgr: LlmpRestartingEventManager<_, _>, core_id: CoreId| { + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + let time_ref = time_observer.handle(); + + let mut run_client = |_state: Option<_>, + mut mgr: LlmpRestartingEventManager<_, _, _, _, _>, + client_description: ClientDescription| { // The coverage map shared between observer and executor let mut shmem = shmem_provider_client.new_shmem(MAP_SIZE).unwrap(); // let the forkserver know the shmid shmem.write_to_env("__AFL_SHM_ID").unwrap(); - let shmem_buf = shmem.as_mut_slice(); + let shmem_buf = shmem.as_slice_mut(); // To let know the AFL++ binary that we have a big map std::env::set_var("AFL_MAP_SIZE", format!("{}", MAP_SIZE)); // Create an observation channel using the hitcounts map of AFL++ - let edges_observer = - unsafe { HitcountsMapObserver::new(StdMapObserver::new("shared_mem", shmem_buf)) }; + let edges_observer = unsafe { + HitcountsMapObserver::new(StdMapObserver::new("shared_mem", shmem_buf)) + .track_indices() + }; - // Create an observation channel to keep track of the execution time - let time_observer = TimeObserver::new("time"); + let time_observer = time_observer.clone(); - let map_feedback = MaxMapFeedback::tracking(&edges_observer, true, false); + let map_feedback = MaxMapFeedback::new(&edges_observer); let calibration = DummyCalibration::new(&map_feedback); @@ -272,15 +280,15 @@ fn fuzz( // New maximization map feedback linked to the edges observer and the feedback state map_feedback, // Time feedback, this one does not need a feedback state - TimeFeedback::with_observer(&time_observer) + TimeFeedback::new(&time_observer) ); // Create client specific directories to avoid race conditions when // writing the corpus to disk. let mut corpus_dir = base_corpus_dir.clone(); - corpus_dir.push(format!("{}", core_id.0)); + corpus_dir.push(format!("{}", client_description.core_id().0)); let mut objective_dir = base_objective_dir.clone(); - objective_dir.push(format!("{}", core_id.0)); + objective_dir.push(format!("{}", client_description.core_id().0)); // A feedback to choose if an input is a solution or not let mut objective = CrashFeedback::new(); @@ -305,25 +313,26 @@ fn fuzz( // A minimization+queue policy to get testcasess from the corpus let scheduler = IndexesLenTimeMinimizerScheduler::new( + &edges_observer, StdWeightedScheduler::with_schedule(&mut state, &edges_observer, schedule), ); // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let forkserver = ForkserverExecutor::builder() + let mut executor = ForkserverExecutor::builder() .program(executable.clone()) .debug_child(debug_child) .parse_afl_cmdline(arguments) .coverage_map_size(MAP_SIZE) .is_persistent(false) .is_deferred_frksrv(true) + .timeout(timeout) + .kill_signal(signal) + .target_bytes_converter(NopTargetBytesConverter::::new()) .build_dynamic_map(edges_observer, tuple_list!(time_observer)) .unwrap(); - let mut executor = TimeoutForkserverExecutor::with_signal(forkserver, timeout, signal) - .expect("Failed to create the executor."); - // Load the initial seeds from the user directory. // state // .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[seed_dir.clone()]) @@ -350,7 +359,6 @@ fn fuzz( let mut stages = tuple_list!(calibration, power); // Main fuzzing loop. - let mut last = current_time(); let monitor_timeout = Duration::from_secs(1); loop { @@ -358,11 +366,11 @@ fn fuzz( if fuzz_err.is_err() { log::error!("fuzz_one error: {}", fuzz_err.err().unwrap()); } - let last_err = mgr.maybe_report_progress(&mut state, last, monitor_timeout); + let last_err = mgr.maybe_report_progress(&mut state, monitor_timeout); if last_err.is_err() { log::error!("last_err error: {}", last_err.err().unwrap()); } else { - last = last_err.ok().unwrap() + last_err.ok().unwrap() } // If we have a simple UI, we need to manually list all causes @@ -387,8 +395,9 @@ fn fuzz( .configuration(conf) .cores(&cores) .monitor(monitor) - .serialize_state(false) + .serialize_state(LlmpShouldSaveState::Never) .broker_port(actual_port) + .time_ref(Some(time_ref)) .run_client(&mut run_client); let mut launcher_log_file = out_dir.clone(); diff --git a/src/calibration.rs b/src/calibration.rs index e0b24d3..0e7b084 100644 --- a/src/calibration.rs +++ b/src/calibration.rs @@ -1,37 +1,38 @@ -extern crate alloc; -use alloc::string::{String, ToString}; use core::{fmt::Debug, marker::PhantomData, time::Duration}; +use std::borrow::Cow; -use hashbrown::HashSet; - +use std::collections::HashSet; +use libafl_bolts::{impl_serdeany, AsIter, Named, tuples::Handle}; +use num_traits::Bounded; use serde::{Deserialize, Serialize}; use libafl::{ - bolts::{tuples::Named, AsIter}, - corpus::{Corpus, CorpusId, SchedulerTestcaseMetadata}, + corpus::{Corpus, HasCurrentCorpusId, SchedulerTestcaseMetadata}, events::{EventFirer, LogSeverity}, executors::{Executor, ExitKind, HasObservers}, - feedbacks::HasObserverName, + feedbacks::{HasObserverHandle}, fuzzer::Evaluator, - inputs::UsesInput, - observers::{MapObserver, ObserversTuple, UsesObserver}, + inputs::Input, + observers::{MapObserver, ObserversTuple}, schedulers::powersched::SchedulerMetadata, - stages::Stage, - state::{HasClientPerfMonitor, HasCorpus, HasMetadata, HasNamedMetadata, UsesState}, - Error, + stages::{RetryCountRestartHelper, Stage}, + state::{HasCorpus, HasCurrentTestcase, HasExecutions}, + Error, HasMetadata, HasNamedMetadata, }; use crate::program_input::ProgramInput; -libafl::impl_serdeany!(UnstableEntriesMetadata); +/// Default name for `CalibrationStage`; derived from AFL++ +const CALIBRATION_STAGE_NAME: &str = "calibration"; + /// The metadata to keep unstable entries -/// In libafl, the stability is the number of the unstable entries divided by the size of the map -/// This is different from AFL++, which shows the number of the unstable entries divided by the number of filled entries. +/// Formula is same as AFL++: number of unstable entries divided by the number of filled entries. #[derive(Serialize, Deserialize, Clone, Debug)] pub struct UnstableEntriesMetadata { unstable_entries: HashSet, - map_len: usize, + filled_entries_count: usize, } +impl_serdeany!(UnstableEntriesMetadata); impl UnstableEntriesMetadata { #[must_use] @@ -39,7 +40,7 @@ impl UnstableEntriesMetadata { pub fn new(entries: HashSet, map_len: usize) -> Self { Self { unstable_entries: entries, - map_len, + filled_entries_count: map_len, } } @@ -51,49 +52,59 @@ impl UnstableEntriesMetadata { /// Getter #[must_use] - pub fn map_len(&self) -> usize { - self.map_len + pub fn filled_entries_count(&self) -> usize { + self.filled_entries_count } } /// The calibration stage will measure the average exec time and the target's stability for this input. #[derive(Clone, Debug)] -pub struct DummyCalibration { - map_observer_name: String, - phantom: PhantomData<(O, OT, S)>, +pub struct DummyCalibration { + map_observer_handle: Handle, + map_name: Cow<'static, str>, + name: Cow<'static, str>, + phantom: PhantomData<(C, E, I, O, OT, S)>, } -impl UsesState for DummyCalibration -where - S: UsesInput, -{ - type State = S; -} +// impl UsesState for DummyCalibration +// where +// S: UsesInput, +// { +// type State = S; +// } -impl Stage for DummyCalibration +impl Stage for DummyCalibration where - E: Executor + HasObservers, - EM: EventFirer, + E: Executor + HasObservers, + EM: EventFirer, O: MapObserver, - for<'de> ::Entry: Serialize + Deserialize<'de> + 'static, - OT: ObserversTuple, - E::State: HasCorpus + HasMetadata + HasClientPerfMonitor + HasNamedMetadata, - Z: Evaluator, - ProgramInput: From<<::State as UsesInput>::Input>, + C: AsRef, + for<'de> ::Entry: + Serialize + Deserialize<'de> + 'static + Default + Debug + Bounded, + OT: ObserversTuple, + S: HasCorpus + + HasMetadata + + HasNamedMetadata + + HasExecutions + + HasCurrentTestcase + + HasCurrentCorpusId, + Z: Evaluator, + I: Input, + ProgramInput: From, { fn perform( &mut self, fuzzer: &mut Z, executor: &mut E, - state: &mut E::State, + state: &mut S, mgr: &mut EM, - corpus_idx: CorpusId, ) -> Result<(), Error> { // Run this stage only once for each corpus entry and only if we haven't already inspected it { - let corpus = state.corpus().get(corpus_idx)?.borrow(); + let testcase = state.current_testcase()?; + // println!("calibration; corpus.scheduled_count() : {}", testcase.scheduled_count()); - if corpus.scheduled_count() > 0 { + if testcase.scheduled_count() > 0 { return Ok(()); } } @@ -101,16 +112,11 @@ where // We only ran our program once. let iter = 1; - let input = state - .corpus() - .get(corpus_idx)? - .borrow_mut() - .load_input(state.corpus())? - .clone(); - + let input = state.current_input_cloned()?; executor.observers_mut().pre_exec_all(state, &input)?; let exit_kind = executor.run_target(fuzzer, state, mgr, &input)?; + if exit_kind != ExitKind::Ok { mgr.log( state, @@ -123,16 +129,15 @@ where .observers_mut() .post_exec_all(state, &input, &exit_kind)?; + // Estimate duration based on number of instructions. let program: ProgramInput = input.into(); let total_time = Duration::from_secs((program.insts().len() + 1) as u64); // If weighted scheduler or powerscheduler is used, update it if state.has_metadata::() { - let map = executor - .observers() - .match_name::(&self.map_observer_name) - .ok_or_else(|| Error::key_not_found("MapObserver not found".to_string()))?; + let observers = executor.observers(); + let map = observers[&self.map_observer_handle].as_ref(); let bitmap_size = map.count_bytes(); @@ -148,11 +153,9 @@ where psmeta.set_bitmap_size_log(psmeta.bitmap_size_log() + libm::log2(bitmap_size as f64)); psmeta.set_bitmap_entries(psmeta.bitmap_entries() + 1); - let mut testcase = state.corpus().get(corpus_idx)?.borrow_mut(); - let scheduled_count = testcase.scheduled_count(); + let mut testcase = state.current_testcase_mut()?; testcase.set_exec_time(total_time / (iter as u32)); - testcase.set_scheduled_count(scheduled_count + 1); // If the testcase doesn't have its own `SchedulerTestcaseMetadata`, create it. let data = if let Ok(metadata) = testcase.metadata_mut::() { @@ -184,23 +187,48 @@ where Ok(()) } + + fn should_restart(&mut self, state: &mut S) -> Result { + // Calibration stage disallow restarts + // If a testcase that causes crash/timeout in the queue, we need to remove it from the queue immediately. + RetryCountRestartHelper::no_retry(state, &self.name) + + // todo + // remove this guy from corpus queue + } + + fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { + // TODO: Make sure this is the correct way / there may be a better way? + RetryCountRestartHelper::clear_progress(state, &self.name) + } } -impl DummyCalibration +impl DummyCalibration where + C: AsRef, O: MapObserver, - OT: ObserversTuple, - S: HasCorpus + HasMetadata + HasNamedMetadata, + for<'it> O: AsIter<'it, Item = O::Entry>, + OT: ObserversTuple, { #[must_use] pub fn new(map_feedback: &F) -> Self where - F: HasObserverName + Named + UsesObserver, - for<'it> O: AsIter<'it, Item = O::Entry>, + F: HasObserverHandle + Named, { + let map_name = map_feedback.name().clone(); Self { - map_observer_name: map_feedback.observer_name().to_string(), + map_observer_handle: map_feedback.observer_handle().clone(), + map_name: map_name.clone(), phantom: PhantomData, + name: Cow::Owned( + CALIBRATION_STAGE_NAME.to_owned() + ":" + map_name.into_owned().as_str(), + ), } } } + +impl Named for DummyCalibration { + fn name(&self) -> &Cow<'static, str> { + &self.name + } +} diff --git a/src/fuzz_ui.rs b/src/fuzz_ui.rs index cf7f535..4d5f53c 100644 --- a/src/fuzz_ui.rs +++ b/src/fuzz_ui.rs @@ -3,7 +3,7 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use libafl::prelude::{current_time, format_duration_hms}; +use libafl_bolts::{current_time, format_duration_hms}; use std::{ collections::{HashMap, HashSet, VecDeque}, io::{self, Stdout}, diff --git a/src/generator.rs b/src/generator.rs index 64570e7..908a4d2 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -1,4 +1,7 @@ use crate::instructions::{Argument, ArgumentSpec, Instruction, InstructionTemplate}; +use std::env; +use libafl_bolts::nonzero; +use std::num::NonZero; /// Generates random RISC-V instructions. #[derive(Default)] @@ -6,9 +9,9 @@ pub struct InstGenerator { /// List of known arguments the generator should try to reuse. known_args: Vec, // Chance (0-100) of reusing a known arg value in the program. - reuse_chance: u64, + reuse_chance: usize, // Chance (0-100) of choosing a power of two as arg value. - power_of_two_chance: u64, + power_of_two_chance: usize, } impl InstGenerator { @@ -24,12 +27,12 @@ impl InstGenerator { self.known_args.append(&mut args.to_vec()) } - pub fn generate_argument( + pub fn generate_argument( &self, rand: &mut R, arg: &'static ArgumentSpec, ) -> Argument { - if rand.below(100) < self.reuse_chance { + if rand.below(nonzero!(100)) < self.reuse_chance { let filtered = self .known_args .iter() @@ -37,18 +40,18 @@ impl InstGenerator { let options = filtered.collect::>(); if !options.is_empty() { let chosen = rand.choose(options).clone(); - return Argument::new(arg, chosen.value()); + return Argument::new(arg, chosen.expect("No arg found").value()); } } - if rand.below(100) < self.power_of_two_chance { - Argument::new(arg, 1 << rand.below(arg.length() as u64) as u32) + if rand.below(nonzero!(100)) < self.power_of_two_chance { + Argument::new(arg, 1 << rand.below(NonZero::new(arg.length() as usize).expect("Arg len cannot be null"))) } else { - Argument::new(arg, rand.below(arg.max_value() as u64) as u32) + Argument::new(arg, rand.below(NonZero::new(arg.max_value() as usize).expect("Arg max_value cannot be null")) as u32) } } - pub fn generate_instruction( + pub fn generate_instruction( &self, rand: &mut R, insts: &Vec<&'static InstructionTemplate>, @@ -57,13 +60,13 @@ impl InstGenerator { let template = rand.choose(insts.iter()); let mut arguments = Vec::::new(); - for arg in template.operands() { + for arg in template.expect("No template").operands() { arguments.push(self.generate_argument(rand, arg)); } - Instruction::new(template, arguments) + Instruction::new(template.expect("No template"), arguments) } - pub fn generate_instructions( + pub fn generate_instructions( &self, rand: &mut R, insts: &Vec<&'static InstructionTemplate>, diff --git a/src/monitor.rs b/src/monitor.rs index 8eeb6fd..ca5b3e4 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -3,8 +3,9 @@ use std::fs::OpenOptions; use std::io::Write; use std::sync::{Arc, Mutex}; -use libafl::prelude::current_time; -use libafl::prelude::{format_duration_hms, ClientId, ClientStats, Monitor}; +use libafl_bolts::{current_time, format_duration_hms}; +use libafl::prelude::{ClientStats, Monitor}; +use libafl_bolts::ClientId; use crate::fuzz_ui::FuzzUI; @@ -24,21 +25,26 @@ impl Monitor for HWFuzzMonitor { &mut self.client_stats } - /// the client monitor + /// The client monitor fn client_stats(&self) -> &[ClientStats] { &self.client_stats } /// Time this fuzzing run stated - fn start_time(&mut self) -> Duration { + fn start_time(&self) -> Duration { self.start_time } - fn display(&mut self, _event_msg: String, sender_id: ClientId) { + /// Time this fuzzing run stated + fn set_start_time(&mut self, time: Duration) { + self.start_time = time; + } + + fn display(&mut self, _event_msg: &str, sender_id: ClientId) { let execs = self.total_execs(); let execs_per_sec = self.execs_per_sec_pretty(); { - let client = self.client_stats_mut_for(sender_id).clone(); + let client = self.client_stats_for(sender_id).clone(); let mut ui = self.ui.lock().unwrap(); let data = ui.data(); diff --git a/src/mutator.rs b/src/mutator.rs index fa8b112..fe767d5 100644 --- a/src/mutator.rs +++ b/src/mutator.rs @@ -1,6 +1,11 @@ -use std::cmp::max; +use std::num::NonZero; +use crate::mutator::alloc::borrow::Cow; +use libafl_bolts::nonzero; use libafl::prelude::*; +use libafl_bolts::tuples::tuple_list_type; +use libafl_bolts::prelude::{tuple_list, Rand}; +use libafl_bolts::Named; use crate::{ generator::InstGenerator, @@ -55,15 +60,14 @@ where &mut self, state: &mut S, input: &mut I, - _stage_idx: i32, ) -> Result { self.mutate_impl(state.rand_mut(), input.insts_mut()) } } impl Named for RiscVInstructionMutator { - fn name(&self) -> &str { - "RiscVInstructionMutator" + fn name(&self) -> &Cow<'static, str> { + &Cow::Borrowed("RiscVInstructionMutator") } } @@ -124,7 +128,7 @@ impl RiscVInstructionMutator { // auipc x2, 0 // jalr x1, random_offset(x2) let make_call = |rng: &mut Rng| -> Vec { - let raw_offset: u32 = rng.below(64) as u32; + let raw_offset: u32 = rng.below(nonzero!(64)) as u32; // let offset: u32 = if rng.below(2) == 0 { // !raw_offset // } else { @@ -159,7 +163,7 @@ impl RiscVInstructionMutator { }; let options = [make_call, make_ret]; - let selected: usize = rng.below(options.len() as u64) as usize; + let selected: usize = rng.below(NonZero::new(options.len()).expect("Options cannot be empty")) as usize; return options[selected](rng); } @@ -176,14 +180,14 @@ impl RiscVInstructionMutator { if program_empty { return 0; } - rng.below(max(program_len as u64, 1)) as usize + rng.below(NonZero::new(program_len).expect("Program can't be empty")) as usize }; let valid_pos = |rng: &mut Rng| -> Option { if program_empty { return None; } - Some(rng.below(program_len as u64) as usize) + Some(rng.below(NonZero::new(program_len).expect("Program can't be empty")) as usize) }; match mutation { @@ -208,7 +212,7 @@ impl RiscVInstructionMutator { if inst.arguments().is_empty() { return None; } - let old_arg = rng.choose(inst.arguments()); + let old_arg = rng.choose(inst.arguments()).expect("No arg found"); let arg_spec = old_arg.spec(); // Keep generating arguments until we find a new one. loop { @@ -230,7 +234,7 @@ impl RiscVInstructionMutator { } Mutation::RepeatSeveral => { let pos = valid_pos(rng)?; - for _ in 0..(rng.below(4) + 1) { + for _ in 0..(rng.below(nonzero!(4)) + 1) { program.insert(pos, program[pos].clone()); } } @@ -319,8 +323,8 @@ mod tests { use std::cmp::min; use libafl::prelude::MutationResult; - use libafl::prelude::Rand; - use libafl::prelude::Xoshiro256StarRand; + use libafl_bolts::prelude::Rand; + use libafl_bolts::prelude::Xoshiro256StarRand; use crate::assembler::assemble_instructions; use crate::generator::InstGenerator; @@ -397,7 +401,7 @@ mod tests { /// Does not guarantee that it generates any instructions. fn fill_random_inst(&mut self) { let generator = InstGenerator::new(); - let num_insts = self.rng.below(40) as u32; + let num_insts = self.rng.below(nonzero!(40)) as u32; self.data = assemble_instructions(&generator.generate_instructions( &mut self.rng, &instructions::sets::riscv_g(), diff --git a/src/parser.rs b/src/parser.rs index 64536ff..dd36c33 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,4 +1,5 @@ use crate::instructions::{Instruction, InstructionTemplate}; +use libafl_bolts::nonzero; pub fn parse_instructions( input: &Vec, @@ -50,7 +51,7 @@ mod tests { rng.set_seed(i); let mut input = Vec::::new(); - for _ in 0..rng.below(100) { + for _ in 0..rng.below(nonzero!(100)) { input.push((rng.next() % 256) as u8); } diff --git a/src/program_input.rs b/src/program_input.rs index bfdc6f0..0dba4ba 100644 --- a/src/program_input.rs +++ b/src/program_input.rs @@ -1,11 +1,13 @@ //! The gramatron grammar fuzzer use core::hash::{BuildHasher, Hasher}; use libafl::{ - prelude::{HasLen, HasTargetBytes, Input, OwnedSlice}, + prelude::{HasTargetBytes, Input}, Error, }; +use libafl_bolts::HasLen; +use libafl_bolts::prelude::OwnedSlice; +use libafl::prelude::CorpusId; use std::fmt; - use ahash::RandomState; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; @@ -72,7 +74,7 @@ impl<'de> Visitor<'de> for ProgramInputVisitor { impl Input for ProgramInput { /// Generate a name for this input #[must_use] - fn generate_name(&self, _idx: usize) -> String { + fn generate_name(&self, _id: Option) -> String { let mut hasher = RandomState::with_seeds(0, 0, 0, 0).build_hasher(); hasher.write(assemble_instructions(&self.insts).as_slice()); format!("size:{}-hash:{:016x}", self.insts().len(), hasher.finish()) From fef57ea836c1fe37814c1b66548d249071027261 Mon Sep 17 00:00:00 2001 From: Alvise de Faveri Tron Date: Tue, 1 Apr 2025 00:01:34 +0000 Subject: [PATCH 2/3] [mutator] Never produce empty programs --- src/bin/sim-fuzzer.rs | 1 + src/mutator.rs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/bin/sim-fuzzer.rs b/src/bin/sim-fuzzer.rs index 5ad7d63..89a73da 100644 --- a/src/bin/sim-fuzzer.rs +++ b/src/bin/sim-fuzzer.rs @@ -329,6 +329,7 @@ fn fuzz( .is_deferred_frksrv(true) .timeout(timeout) .kill_signal(signal) + .min_input_size(4) .target_bytes_converter(NopTargetBytesConverter::::new()) .build_dynamic_map(edges_observer, tuple_list!(time_observer)) .unwrap(); diff --git a/src/mutator.rs b/src/mutator.rs index fe767d5..14e9241 100644 --- a/src/mutator.rs +++ b/src/mutator.rs @@ -239,7 +239,13 @@ impl RiscVInstructionMutator { } } Mutation::Remove => { - program.remove(valid_pos(rng)?); + // Don't remove if it's too small. + if program_len >= 4 { + program.remove(valid_pos(rng)?); + } else { + // TODO: If we can't remove, we add - does that make sense? + program.insert(add_pos(rng), self.gen_inst(program, rng)); + } } Mutation::ReplaceWithNop => { let pos = valid_pos(rng)?; From c43f15feb1d998a1db02d6eea8b39c58cb490771 Mon Sep 17 00:00:00 2001 From: Alvise de Faveri Tron Date: Tue, 1 Apr 2025 21:41:40 +0000 Subject: [PATCH 3/3] Add env flags to disable biasing `PHANTOM_TRAILS_NO_SNIPPET=1` prevents the mutator from generating call/ret snippets. `PHANTOM_TRAILS_NO_ARG_REUSE=1` prevents the mutator from (intentionally) reusing args when generating instructions. --- src/bin/sim-fuzzer.rs | 4 +- src/generator.rs | 6 ++- src/mutator.rs | 99 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 92 insertions(+), 17 deletions(-) diff --git a/src/bin/sim-fuzzer.rs b/src/bin/sim-fuzzer.rs index 89a73da..d2fb1e7 100644 --- a/src/bin/sim-fuzzer.rs +++ b/src/bin/sim-fuzzer.rs @@ -54,7 +54,7 @@ use riscv_mutator::{ Argument, Instruction, }, monitor::HWFuzzMonitor, - mutator::{all_riscv_mutations}, + mutator::{RiscvScheduledMutator, all_riscv_mutations}, program_input::ProgramInput, }; @@ -307,7 +307,7 @@ fn fuzz( ) .unwrap(); - let mutator = StdScheduledMutator::new(all_riscv_mutations()); + let mutator = RiscvScheduledMutator::new(all_riscv_mutations()); let power = StdPowerMutationalStage::new(mutator); diff --git a/src/generator.rs b/src/generator.rs index 908a4d2..7b9abc2 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -16,10 +16,12 @@ pub struct InstGenerator { impl InstGenerator { pub fn new() -> Self { + let reuse_args = !env::var("PHANTOM_TRAILS_NO_ARG_REUSE").is_ok(); + Self { known_args: Vec::::new(), - reuse_chance: 50, - power_of_two_chance: 50, + reuse_chance: if reuse_args { 50 } else { 0 }, + power_of_two_chance: if reuse_args { 50 } else { 0 }, } } diff --git a/src/mutator.rs b/src/mutator.rs index 14e9241..b14ca60 100644 --- a/src/mutator.rs +++ b/src/mutator.rs @@ -1,7 +1,11 @@ use std::num::NonZero; +use std::env; +use std::marker::PhantomData; use crate::mutator::alloc::borrow::Cow; +use core::fmt::Debug; use libafl_bolts::nonzero; +use libafl_bolts::tuples::NamedTuple; use libafl::prelude::*; use libafl_bolts::tuples::tuple_list_type; use libafl_bolts::prelude::{tuple_list, Rand}; @@ -308,20 +312,89 @@ pub fn all_riscv_mutations() -> RiscVMutationList { ) } -/// All reducing mutations -pub type RiscVReducingMutationList = tuple_list_type!( - RiscVInstructionMutator, - RiscVInstructionMutator, - RiscVInstructionMutator, -); +/// A [`Mutator`] that schedules one of the embedded mutations on each call. +#[derive(Debug)] +pub struct RiscvScheduledMutator { + name: Cow<'static, str>, + mutations: MT, + has_snippet: bool, + max_stack_pow: usize, +} -/// All mutations used to minimize test cases. -pub fn reducing_mutations() -> RiscVReducingMutationList { - tuple_list!( - RiscVInstructionMutator::new(Mutation::Remove), - RiscVInstructionMutator::new(Mutation::Remove), - RiscVInstructionMutator::new(Mutation::ReplaceWithNop), - ) +impl Named for RiscvScheduledMutator { + fn name(&self) -> &Cow<'static, str> { + &self.name + } +} + +impl Mutator for RiscvScheduledMutator +where + MT: MutatorsTuple, + S: HasRand, +{ + #[inline] + fn mutate(&mut self, state: &mut S, input: &mut I) -> Result { + self.scheduled_mutate(state, input) + } +} + +impl ComposedByMutations for RiscvScheduledMutator { + type Mutations = MT; + /// Get the mutations + #[inline] + fn mutations(&self) -> &MT { + &self.mutations + } + + // Get the mutations (mutable) + #[inline] + fn mutations_mut(&mut self) -> &mut MT { + &mut self.mutations + } +} + +impl ScheduledMutator for RiscvScheduledMutator +where + MT: MutatorsTuple, + S: HasRand, +{ + /// Compute the number of iterations used to apply stacked mutations + fn iterations(&self, state: &mut S, _: &I) -> u64 { + 1 << (1 + state.rand_mut().below_or_zero(self.max_stack_pow)) + } + + /// Get the next mutation to apply + fn schedule(&self, state: &mut S, _: &I) -> MutationId { + debug_assert_ne!(self.mutations.len(), 0); + let len = if self.has_snippet { + self.mutations.len() + } else { + // Snippet is the last mutation of the tuple, so we simply + // make sure that we never select it. + self.mutations.len() - 1 + }; + state + .rand_mut() + .below(unsafe { NonZero::new(len).unwrap_unchecked() }) + .into() + } +} + +impl RiscvScheduledMutator +where + MT: NamedTuple, +{ + pub fn new(mutations: MT) -> Self { + RiscvScheduledMutator { + name: Cow::from(format!( + "RiscvScheduledMutator[{}]", + mutations.names().join(", ") + )), + mutations, + has_snippet: !env::var("PHANTOM_TRAILS_NO_SNIPPET").is_ok(), + max_stack_pow: 7, + } + } } #[cfg(test)]