num_covered_map_indexes may be also counted if R::reduce(histroy_map[i], value) returns history_map[i] and history_map[i] is the initial value. For instance, given history_map[i] = 0, value = -1, R::reduce = max, num_covered_map_indexes is updated but history_map[i] doesn't change and is still 0, as the initial value. This soon falsely triggers the debug_assert:
A full reproduction modified from baby_fuzzer, note i8 below.
use std::{path::PathBuf, ptr::write};
use libafl::monitors::SimpleMonitor;
use libafl::Evaluator;
use libafl::{
corpus::{InMemoryCorpus, OnDiskCorpus},
events::SimpleEventManager,
executors::{inprocess::InProcessExecutor, ExitKind},
feedbacks::{CrashFeedback, MaxMapFeedback},
fuzzer::{Fuzzer, StdFuzzer},
generators::RandPrintablesGenerator,
inputs::{BytesInput, HasTargetBytes},
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
observers::StdMapObserver,
schedulers::QueueScheduler,
stages::mutational::StdMutationalStage,
state::StdState,
};
use libafl_bolts::{current_nanos, rands::StdRand, tuples::tuple_list, AsSlice};
/// Coverage map with explicit assignments due to the lack of instrumentation
static mut SIGNALS: [i8; 16] = [0; 16];
static mut SIGNALS_PTR: *mut i8 = unsafe { SIGNALS.as_mut_ptr() };
/// Assign a signal to the signals map
fn signals_set(idx: usize) {
unsafe { write(SIGNALS_PTR.add(idx), -1) };
}
#[allow(clippy::similar_names, clippy::manual_assert)]
pub fn main() {
// The closure that we want to fuzz
let mut harness = |input: &BytesInput| {
let target = input.target_bytes();
let buf = target.as_slice();
signals_set(0);
if !buf.is_empty() && buf[0] == b'a' {
signals_set(1);
if buf.len() > 1 && buf[1] == b'b' {
signals_set(2);
if buf.len() > 2 && buf[2] == b'c' {
panic!("Artificial bug triggered =)");
}
}
}
ExitKind::Ok
};
// Create an observation channel using the signals map
let observer = unsafe { StdMapObserver::<i8, false>::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS.len()) };
// Feedback to rate the interestingness of an input
let mut feedback = MaxMapFeedback::new(&observer);
// A feedback to choose if an input is a solution or not
let mut objective = CrashFeedback::new();
// create a State from scratch
let mut state = StdState::new(
// RNG
StdRand::with_seed(current_nanos()),
// Corpus that will be evolved, we keep it in memory for performance
InMemoryCorpus::new(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(),
// States of the feedbacks.
// The feedbacks can report the data that should persist in the State.
&mut feedback,
// Same for objective feedbacks
&mut objective,
)
.unwrap();
// The Monitor trait define how the fuzzer stats are displayed to the user
let mon = SimpleMonitor::new(|s| println!("{s}"));
// The event manager handle the various events generated during the fuzzing loop
// such as the notification of the addition of a new item to the corpus
let mut mgr = SimpleEventManager::new(mon);
// A queue policy to get testcasess from the corpus
let scheduler = QueueScheduler::new();
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// Create the executor for an in-process function with just one observer
let mut executor = InProcessExecutor::new(
&mut harness,
tuple_list!(observer),
&mut fuzzer,
&mut state,
&mut mgr,
)
.expect("Failed to create the Executor");
fuzzer.add_input(&mut state, &mut executor, &mut mgr, BytesInput::new(vec![])).unwrap();
// Setup a mutational stage with a basic bytes mutator
let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
.expect("Error in the fuzzing loop");
}
This would panic with:
history_map had 0 filled, but map_state.num_covered_map_indexes was 1
This PR fixes by detecting if history_map[i] value really changes.
The original implementation:
num_covered_map_indexes
may be also counted ifR::reduce(histroy_map[i], value)
returnshistory_map[i]
andhistory_map[i]
is the initial value. For instance, givenhistory_map[i] = 0, value = -1, R::reduce = max
,num_covered_map_indexes
is updated buthistory_map[i]
doesn't change and is still 0, as the initial value. This soon falsely triggers the debug_assert:A full reproduction modified from baby_fuzzer, note
i8
below.This would panic with:
This PR fixes by detecting if
history_map[i]
value really changes.