diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 4a160e0a023..c96bb5d0de5 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -294,6 +294,7 @@ jobs: # - baby/backtrace_baby_fuzzers - baby/baby_fuzzer_unicode - baby/baby_fuzzer_minimizing + - baby/baby_fuzzer_redqueen - baby/backtrace_baby_fuzzers/c_code_with_fork_executor - baby/backtrace_baby_fuzzers/c_code_with_inprocess_executor - baby/backtrace_baby_fuzzers/rust_code_with_fork_executor diff --git a/crates/libafl/src/mutators/token_mutations.rs b/crates/libafl/src/mutators/token_mutations.rs index bcde4c1f494..2a1b90d5fe0 100644 --- a/crates/libafl/src/mutators/token_mutations.rs +++ b/crates/libafl/src/mutators/token_mutations.rs @@ -1076,8 +1076,8 @@ impl AflppRedQueen { if buf_16 == pattern as u16 && another_buf_16 == another_pattern as u16 { let mut cloned = buf.to_vec(); - cloned[buf_idx + 1] = (repl & 0xff) as u8; - cloned[buf_idx] = ((repl >> 8) & 0xff) as u8; + cloned[buf_idx..(buf_idx + 2)] + .copy_from_slice(&(repl as u16).to_be_bytes()); vec.push(cloned); return Ok(true); } @@ -1091,12 +1091,9 @@ impl AflppRedQueen { // println!("buf: {buf_32} {another_buf_32} {pattern} {another_pattern}"); if buf_32 == pattern as u32 && another_buf_32 == another_pattern as u32 { let mut cloned = buf.to_vec(); - cloned[buf_idx + 3] = (repl & 0xff) as u8; - cloned[buf_idx + 2] = ((repl >> 8) & 0xff) as u8; - cloned[buf_idx + 1] = ((repl >> 16) & 0xff) as u8; - cloned[buf_idx] = ((repl >> 24) & 0xff) as u8; + cloned[buf_idx..(buf_idx + 4)] + .copy_from_slice(&(repl as u32).to_be_bytes()); vec.push(cloned); - return Ok(true); } } @@ -1109,16 +1106,7 @@ impl AflppRedQueen { if buf_64 == pattern && another_buf_64 == another_pattern { let mut cloned = buf.to_vec(); - - cloned[buf_idx + 7] = (repl & 0xff) as u8; - cloned[buf_idx + 6] = ((repl >> 8) & 0xff) as u8; - cloned[buf_idx + 5] = ((repl >> 16) & 0xff) as u8; - cloned[buf_idx + 4] = ((repl >> 24) & 0xff) as u8; - cloned[buf_idx + 3] = ((repl >> 32) & 0xff) as u8; - cloned[buf_idx + 2] = ((repl >> 32) & 0xff) as u8; - cloned[buf_idx + 1] = ((repl >> 40) & 0xff) as u8; - cloned[buf_idx] = ((repl >> 48) & 0xff) as u8; - + cloned[buf_idx..(buf_idx + 8)].copy_from_slice(&repl.to_be_bytes()); vec.push(cloned); return Ok(true); } diff --git a/crates/libafl_targets/src/cmps/mod.rs b/crates/libafl_targets/src/cmps/mod.rs index 4da3f412c2b..c200c5691fc 100644 --- a/crates/libafl_targets/src/cmps/mod.rs +++ b/crates/libafl_targets/src/cmps/mod.rs @@ -224,8 +224,8 @@ impl AflppCmpLogOperands { #[repr(C, packed)] /// Comparison function operands, like for strcmp/memcmp, represented as two byte arrays. pub struct AflppCmpLogFnOperands { - v0: [u8; 32], - v1: [u8; 32], + v0: [u8; CMPLOG_RTN_LEN], + v1: [u8; CMPLOG_RTN_LEN], v0_len: u8, v1_len: u8, unused: [u8; 6], @@ -235,14 +235,16 @@ impl AflppCmpLogFnOperands { #[must_use] /// Create a new AFL++ function operands comparison values from two byte slices pub fn new(v0: &[u8], v1: &[u8]) -> Self { - let v0_len = v0.len() as u8; - let v1_len = v1.len() as u8; + let v0_len = v0.len().min(CMPLOG_RTN_LEN) as u8; + let v0_truncated = &v0[..v0_len as usize]; + let v1_len = v1.len().min(CMPLOG_RTN_LEN) as u8; + let v1_truncated = &v1[..v1_len as usize]; - let mut v0_arr = [0; 32]; - let mut v1_arr = [0; 32]; + let mut v0_arr = [0; CMPLOG_RTN_LEN]; + let mut v1_arr = [0; CMPLOG_RTN_LEN]; - v0_arr.copy_from_slice(v0); - v1_arr.copy_from_slice(v1); + v0_arr[..v0_len as usize].copy_from_slice(v0_truncated); + v1_arr[..v1_len as usize].copy_from_slice(v1_truncated); Self { v0: v0_arr, @@ -255,7 +257,7 @@ impl AflppCmpLogFnOperands { #[must_use] /// first rtn operand - pub fn v0(&self) -> &[u8; 32] { + pub fn v0(&self) -> &[u8; CMPLOG_RTN_LEN] { &self.v0 } @@ -267,7 +269,7 @@ impl AflppCmpLogFnOperands { #[must_use] /// first rtn operand len - pub fn v1(&self) -> &[u8; 32] { + pub fn v1(&self) -> &[u8; CMPLOG_RTN_LEN] { &self.v1 } @@ -279,14 +281,14 @@ impl AflppCmpLogFnOperands { /// Set the v0 (left) side of the comparison pub fn set_v0(&mut self, v0: &[u8]) { - self.v0_len = v0.len() as u8; - self.v0.copy_from_slice(v0); + self.v0_len = v0.len().min(CMPLOG_RTN_LEN) as u8; + self.v0[..self.v0_len as usize].copy_from_slice(&v0[..self.v0_len as usize]); } /// Set the v1 (right) side of the comparison pub fn set_v1(&mut self, v1: &[u8]) { - self.v1_len = v1.len() as u8; - self.v1.copy_from_slice(v1); + self.v1_len = v1.len().min(CMPLOG_RTN_LEN) as u8; + self.v1[..self.v1_len as usize].copy_from_slice(&v1[..self.v1_len as usize]); } } diff --git a/fuzzers/baby/baby_fuzzer_redqueen/.gitignore b/fuzzers/baby/baby_fuzzer_redqueen/.gitignore new file mode 100644 index 00000000000..6a49a96db94 --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_redqueen/.gitignore @@ -0,0 +1,2 @@ +out/ +in/ diff --git a/fuzzers/baby/baby_fuzzer_redqueen/Cargo.toml b/fuzzers/baby/baby_fuzzer_redqueen/Cargo.toml new file mode 100644 index 00000000000..53da8ba3abc --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_redqueen/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "baby_fuzzer_redqueen" +version = "0.16.0" +authors = ["Andrew Barbarello "] +edition = "2021" + +[features] +default = ["std"] +std = [] + +[profile.release] +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[dependencies] +libafl = { path = "../../../crates/libafl/" } +libafl_bolts = { path = "../../../crates/libafl_bolts/" } +libafl_targets = { path = "../../../crates/libafl_targets", features = [ + "sancov_pcguard_edges", + "libfuzzer", + "cmplog_extended_instrumentation", +] } +libafl_cc = { path = "../../../crates/libafl_cc" } +clap = { version = "4.5.18", features = ["default"] } + +[lib] +name = "baby_fuzzer_redqueen" +crate-type = ["staticlib"] diff --git a/fuzzers/baby/baby_fuzzer_redqueen/Justfile b/fuzzers/baby/baby_fuzzer_redqueen/Justfile new file mode 100644 index 00000000000..e98f2116c01 --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_redqueen/Justfile @@ -0,0 +1,65 @@ +FUZZER_NAME := 'baby_fuzzer_redqueen' +PROJECT_DIR := absolute_path(".") +PROFILE := 'release' +PROFILE_DIR := 'release' +CARGO_TARGET_DIR := env("CARGO_TARGET_DIR", "target") +FUZZER := absolute_path(CARGO_TARGET_DIR / PROFILE_DIR / FUZZER_NAME) + +alias build := fuzzer +alias cc := cxx + +[linux] +[macos] +cxx: + cargo build --profile={{ PROFILE }} + +[windows] +cxx: + echo "Unsupported on this platform" + +[linux] +[macos] +fuzz_o: cxx + {{ CARGO_TARGET_DIR }}/{{ PROFILE_DIR }}/libafl_cc --libafl-no-link -O3 -g -c fuzz.c -o fuzz.o + +[windows] +fuzz_o: + echo "Unsupported on this platform" + +[linux] +[macos] +fuzzer: cxx fuzz_o + {{ CARGO_TARGET_DIR }}/{{ PROFILE_DIR }}/libafl_cxx --libafl fuzz.o -o {{ FUZZER }} + +[windows] +fuzzer: + echo "Unsupported on this platform" + +run: fuzzer + {{ FUZZER }} -o out -i in + +[windows] +run: + echo "Unsupported on this platform" + +[linux] +[macos] +test: fuzzer + #!/bin/bash + mkdir -p in + head -c 28 /dev/zero > in/zeros + timeout 120s {{ FUZZER }} -o out -i in | tee fuzz_stdout.log || true + if grep -qa "objectives: 1" fuzz_stdout.log; then + echo "Fuzzer is working" + else + echo "Fuzzer does not generate any crashes" + exit 1 + fi + rm -rf out in + +[windows] +test: fuzzer + echo "Unsupported on this platform" + +clean: + cargo clean diff --git a/fuzzers/baby/baby_fuzzer_redqueen/README.md b/fuzzers/baby/baby_fuzzer_redqueen/README.md new file mode 100644 index 00000000000..3bb9bb674e3 --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_redqueen/README.md @@ -0,0 +1,10 @@ +# Baby fuzzer with RedQueen-based CmpLog + +This is a minimalistic example demonstrating the use of the `cmplog_extended_instrumentation` feature of LibAFL, all in-process. For a more production-quality reference, see the `fuzzbench_forkserver_cmplog` fuzzer. + +The tested program is a simple function with comparisons to 16, 32, +and 64 bit magic values, which are difficult/impossible for a simple +bitflipping fuzzer to solve. + +Build and run with `just run`, or `just test`, which is a CI target +that checks that the run triggers a crash within a couple of minutes. diff --git a/fuzzers/baby/baby_fuzzer_redqueen/fuzz.c b/fuzzers/baby/baby_fuzzer_redqueen/fuzz.c new file mode 100644 index 00000000000..9cb23c28321 --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_redqueen/fuzz.c @@ -0,0 +1,63 @@ +#include +#include +#include + +#define MAGIC_U16 ((uint16_t)0xAABB) +#define MAGIC_U32 ((uint32_t)0x11223344) +#define MAGIC_U64 ((uint64_t)0x0102030405060708ULL) + +static inline uint16_t load_u16(const uint8_t *p) { + uint16_t v; + memcpy(&v, p, sizeof(v)); + return v; +} + +static inline uint16_t bswap_u16(const uint16_t v) { + return ((v & 0xff00) >> 8) | ((v & 0xff) << 8); +} + +static inline uint32_t load_u32(const uint8_t *p) { + uint32_t v; + memcpy(&v, p, sizeof(v)); + return v; +} + +static inline uint32_t bswap_u32(const uint32_t v) { + return ((v & 0xff000000UL) >> 24) | ((v & 0x00ff0000UL) >> 8) | + ((v & 0x0000ff00UL) << 8) | ((v & 0x000000ffUL) << 24); +} + +static inline uint64_t load_u64(const uint8_t *p) { + uint64_t v; + memcpy(&v, p, sizeof(v)); + return v; +} + +static inline uint64_t bswap_u64(const uint64_t v) { + return ((v & 0xff00000000000000ULL) >> 56) | + ((v & 0x00ff000000000000ULL) >> 40) | + ((v & 0x0000ff0000000000ULL) >> 24) | + ((v & 0x000000ff00000000ULL) >> 8) | + ((v & 0x00000000ff000000ULL) << 8) | + ((v & 0x0000000000ff0000ULL) << 24) | + ((v & 0x000000000000ff00ULL) << 40) | + ((v & 0x00000000000000ffULL) << 56); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 28) { return 0; } + + if (load_u16(data + 0) == MAGIC_U16) { + if (bswap_u16(load_u16(data + 2)) == MAGIC_U16) { + if (load_u32(data + 4) == MAGIC_U32) { + if (bswap_u32(load_u32(data + 8)) == MAGIC_U32) { + if (load_u64(data + 12) == MAGIC_U64) { + if (bswap_u64(load_u64(data + 20)) == MAGIC_U64) { abort(); } + } + } + } + } + } + + return 0; +} diff --git a/fuzzers/baby/baby_fuzzer_redqueen/src/bin/libafl_cc.rs b/fuzzers/baby/baby_fuzzer_redqueen/src/bin/libafl_cc.rs new file mode 100644 index 00000000000..2b4e2a66fe0 --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_redqueen/src/bin/libafl_cc.rs @@ -0,0 +1,39 @@ +use std::env; + +use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses, ToolWrapper}; + +pub fn main() { + let args: Vec = env::args().collect(); + if args.len() > 1 { + let mut dir = env::current_exe().unwrap(); + let wrapper_name = dir.file_name().unwrap().to_str().unwrap(); + + let is_cpp = match wrapper_name[wrapper_name.len()-2..].to_lowercase().as_str() { + "cc" => false, + "++" | "pp" | "xx" => true, + _ => panic!("Could not figure out if c or c++ wrapper was called. Expected {dir:?} to end with c or cxx"), + }; + + dir.pop(); + + let mut cc = ClangWrapper::new(); + if let Some(code) = cc + .cpp(is_cpp) + // silence the compiler wrapper output, needed for some configure scripts. + .silence(true) + .need_libafl_arg(true) + .parse_args(&args) + .expect("Failed to parse the command line") + .link_staticlib(&dir, "baby_fuzzer_redqueen") + .add_arg("-fsanitize-coverage=trace-pc-guard") + .add_pass(LLVMPasses::CmpLogInstructions) + .add_passes_arg("-cmplog_instructions_extended=1") + .run() + .expect("Failed to run the wrapped compiler") + { + std::process::exit(code); + } + } else { + panic!("LibAFL CC: No Arguments given"); + } +} diff --git a/fuzzers/baby/baby_fuzzer_redqueen/src/bin/libafl_cxx.rs b/fuzzers/baby/baby_fuzzer_redqueen/src/bin/libafl_cxx.rs new file mode 100644 index 00000000000..dabd22971ad --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_redqueen/src/bin/libafl_cxx.rs @@ -0,0 +1,5 @@ +pub mod libafl_cc; + +fn main() { + libafl_cc::main(); +} diff --git a/fuzzers/baby/baby_fuzzer_redqueen/src/lib.rs b/fuzzers/baby/baby_fuzzer_redqueen/src/lib.rs new file mode 100644 index 00000000000..37b7d7d1f78 --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_redqueen/src/lib.rs @@ -0,0 +1,251 @@ +//! A simple in-process fuzzer with redqueen + +use core::time::Duration; +use std::{ + env, + fs::{self}, + path::PathBuf, + process, +}; + +use clap::{Arg, Command}; +use libafl::{ + corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus}, + events::SimpleEventManager, + executors::{inprocess::InProcessExecutor, ExitKind}, + feedbacks::{CrashFeedback, MaxMapFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + monitors::SimpleMonitor, + mutators::{havoc_mutations, token_mutations::AflppRedQueen, HavocScheduledMutator}, + observers::{CanTrack, HitcountsMapObserver}, + schedulers::QueueScheduler, + stages::{mutational::MultiMutationalStage, ColorizationStage, IfStage, StdMutationalStage}, + state::{HasCorpus, HasCurrentTestcase, StdState}, + Error, +}; +use libafl_bolts::{ + ownedref::OwnedRefMut, + rands::StdRand, + tuples::{tuple_list, Handled}, + AsSlice, +}; +use libafl_targets::{ + cmps::{observers::AflppCmpLogObserver, stages::AflppCmplogTracingStage}, + libfuzzer_initialize, libfuzzer_test_one_input, std_edges_map_observer, CMPLOG_MAP_EXTENDED, +}; + +/// The fuzzer main (as `no_mangle` C function) +#[no_mangle] +pub extern "C" fn libafl_main( + _argc: core::ffi::c_int, + _argv: *const *const core::ffi::c_char, +) -> core::ffi::c_int { + let cmd = Command::new(env!("CARGO_PKG_NAME")) + .version(env!("CARGO_PKG_VERSION")) + .author("AFLplusplus team") + .about("Example fuzzer for AflppRedQueen Instrumentation") + .arg( + Arg::new("out") + .short('o') + .long("output") + .help("The directory to place finds in ('corpus')"), + ) + .arg( + Arg::new("in") + .short('i') + .long("input") + .help("The directory to read initial inputs from ('seeds')"), + ); + + let res = match cmd.arg(Arg::new("remaining")).try_get_matches() { + Ok(res) => res, + Err(err) => { + println!( + "Syntax: {}, -o corpus_dir -i seed_dir\n{:?}", + env::current_exe() + .unwrap_or_else(|_| "fuzzer".into()) + .to_string_lossy(), + err, + ); + return 1; + } + }; + + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + + // Put crashes and finds inside the same `corpus` directory, in "crashes" and "queue" subdirs. + let mut out_dir = PathBuf::from( + res.get_one::("out") + .expect("The --output parameter is missing") + .to_string(), + ); + if fs::create_dir(&out_dir).is_err() { + println!("Out dir at {:?} already exists.", &out_dir); + if !out_dir.is_dir() { + println!("Out dir at {:?} is not a valid directory!", &out_dir); + return 1; + } + } + let mut crashes = out_dir.clone(); + crashes.push("crashes"); + out_dir.push("queue"); + + let in_dir = PathBuf::from( + res.get_one::("in") + .expect("The --input parameter is missing") + .to_string(), + ); + if !in_dir.is_dir() { + println!("In dir at {:?} is not a valid directory!", &in_dir); + return 1; + } + + run_fuzzer(out_dir, crashes, &in_dir, Duration::from_secs(5)) + .expect("An error occurred while fuzzing"); + 0 +} + +/// The actual fuzzer +fn run_fuzzer( + corpus_dir: PathBuf, + objective_dir: PathBuf, + seed_dir: &PathBuf, + timeout: Duration, +) -> Result<(), Error> { + // The monitor provides the user some insight into how the fuzz run is going + 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); + + // Create an observation channel using the coverage map + let edges_observer = + HitcountsMapObserver::new(unsafe { std_edges_map_observer("edges") }).track_indices(); + + // For this tiny fuzzer, we'll just use the coverage map for + // feedback. Proper fuzzers will also collect time feedback to use + // for calibration and queue scheduling + let mut feedback = MaxMapFeedback::new(&edges_observer); + + // A feedback to choose if an input is a solution or not + let mut objective = CrashFeedback::new(); + + let mut state = StdState::new( + // RNG + StdRand::new(), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryOnDiskCorpus::new(corpus_dir).unwrap(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(objective_dir).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, + )?; + + println!("Let's fuzz :)"); + + // The actual target run starts here. + // Call LLVMFuzzerInitialize() if present. + let args: Vec = env::args().collect(); + if unsafe { libfuzzer_initialize(&args) } == -1 { + println!("Warning: LLVMFuzzerInitialize failed with -1"); + } + + // Setup a mutational stage with a basic bytes mutator + let mutator = HavocScheduledMutator::new(havoc_mutations()); + let mutational_stage = StdMutationalStage::new(mutator); + + // 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); + + // The wrapped harness function, calling out to the LLVM-style harness + fn harness(input: &BytesInput) -> ExitKind { + let target = input.target_bytes(); + let buf = target.as_slice(); + unsafe { + libfuzzer_test_one_input(buf); + } + ExitKind::Ok + } + let mut harness_main: fn(&BytesInput) -> ExitKind = harness; + let mut harness_cmplog: fn(&BytesInput) -> ExitKind = harness; + + /* + RedQueen's colorization stage needs a reference to edges_observer + */ + let colorization = ColorizationStage::new(&edges_observer); + + // Create the executor for an in-process function with one observer for edge coverage + let mut executor = InProcessExecutor::with_timeout( + &mut harness_main, + tuple_list!(edges_observer), + &mut fuzzer, + &mut state, + &mut mgr, + timeout, + )?; + + // Now, we'll setup the rest of the RedQueen stages. We need the cmplog map, + // an executor that populates it, and observer for it, and the + // colorization/tracing stages . + + let cmpmap_ref = unsafe { OwnedRefMut::from_mut_ptr(&raw mut CMPLOG_MAP_EXTENDED) }; + let cmplog_observer = AflppCmpLogObserver::new("cmplog", cmpmap_ref, true); + let cmplog_ref = cmplog_observer.handle(); + let cmplog_executor = InProcessExecutor::with_timeout( + &mut harness_cmplog, + tuple_list!(cmplog_observer), + &mut fuzzer, + &mut state, + &mut mgr, + timeout, + )?; + + let tracing = AflppCmplogTracingStage::new(cmplog_executor, cmplog_ref); + + // Setup a randomic Input2State stage + let rq: MultiMutationalStage<_, _, BytesInput, _, _, _> = + MultiMutationalStage::new(AflppRedQueen::with_cmplog_options(true, true)); + + // We'll only run the cmplog stuff (run colorization, enable comparison tracing, then do mutations based on those) when a test case is on its second schedule + let cb = |_fuzzer: &mut _, + _executor: &mut _, + state: &mut StdState, _, _, _>, + _event_manager: &mut _| + -> Result { + let testcase = state.current_testcase()?; + let res = testcase.scheduled_count() == 1; // let's try on the 2nd trial + + Ok(res) + }; + let cmplog = IfStage::new(cb, tuple_list!(colorization, tracing, rq)); + + let mut stages = tuple_list!(cmplog, mutational_stage); + + state + .load_initial_inputs( + &mut fuzzer, + &mut executor, + &mut mgr, + std::slice::from_ref(seed_dir), + ) + .unwrap_or_else(|e| { + println!("Failed to load initial corpus at {seed_dir:?} - {e:?}"); + process::exit(0); + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + + Ok(()) +}