From 9e9425c622934fc9390a67e7ba7c60fb1a11c57a Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Wed, 9 Jun 2021 12:15:08 +0200 Subject: [PATCH 1/6] introduce ShadowExecutor --- libafl/src/executors/mod.rs | 3 + libafl/src/executors/shadow.rs | 178 +++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 libafl/src/executors/shadow.rs diff --git a/libafl/src/executors/mod.rs b/libafl/src/executors/mod.rs index 25bcaf3c89..4dfa0d4cdc 100644 --- a/libafl/src/executors/mod.rs +++ b/libafl/src/executors/mod.rs @@ -13,6 +13,9 @@ pub use forkserver::{Forkserver, ForkserverExecutor, OutFile, TimeoutForkserverE pub mod combined; pub use combined::CombinedExecutor; +pub mod shadow; +pub use shadow::{HasShadowObserverHooks, ShadowExecutor}; + use crate::{ bolts::serdeany::SerdeAny, inputs::{HasTargetBytes, Input}, diff --git a/libafl/src/executors/shadow.rs b/libafl/src/executors/shadow.rs new file mode 100644 index 0000000000..99441fcf59 --- /dev/null +++ b/libafl/src/executors/shadow.rs @@ -0,0 +1,178 @@ +//! A `ShadowExecutor` wraps an executor to have shadow observer that will not be considered by the manager + +use crate::{ + executors::{Executor, ExitKind, HasExecHooksTuple, HasObservers, HasObserversHooks}, + inputs::Input, + observers::ObserversTuple, + Error, +}; + +pub trait HasShadowObserverHooks { + /// Run the pre exec hook for all the shadow [`crate::observers::Observer`]`s` + fn pre_exec_shadow_observers( + &mut self, + fuzzer: &mut Z, + state: &mut S, + mgr: &mut EM, + input: &I, + ) -> Result<(), Error>; + + /// Run the post exec hook for all the shadow [`crate::observers::Observer`]`s` + fn post_exec_shadow_observers( + &mut self, + fuzzer: &mut Z, + state: &mut S, + mgr: &mut EM, + input: &I, + ) -> Result<(), Error>; +} + +/// A [`ShadowExecutor`] wraps a primary executor, forwarding its methods, and a secondary one +pub struct ShadowExecutor { + executor: E, + shadow_observers: SOT, + // Enable the execution of the shadow observers hooks with the regular observers hooks + shadow_hooks: bool, +} + +impl ShadowExecutor +where + SOT: ObserversTuple, +{ + /// Create a new `ShadowExecutor`, wrapping the given `executor`. + pub fn new(executor: E, shadow_observers: SOT) -> Self { + Self { + executor, + shadow_observers, + shadow_hooks: false, + } + } + + /// Create a new `ShadowExecutor`, wrapping the given `executor`. + pub fn with_shadow_hooks( + executor: E, + shadow_observers: SOT, + shadow_hooks: bool, + ) -> Self { + Self { + executor, + shadow_observers, + shadow_hooks, + } + } + + #[inline] + pub fn shadow_observers(&self) -> &SOT { + &self.shadow_observers + } + + #[inline] + pub fn shadow_observers_mut(&mut self) -> &mut SOT { + &mut self.shadow_observers + } +} + +impl HasShadowObserverHooks for ShadowExecutor +where + I: Input, + SOT: ObserversTuple + HasExecHooksTuple, +{ + #[inline] + fn pre_exec_shadow_observers( + &mut self, + fuzzer: &mut Z, + state: &mut S, + mgr: &mut EM, + input: &I, + ) -> Result<(), Error> { + self.shadow_observers + .pre_exec_all(fuzzer, state, mgr, input) + } + + #[inline] + fn post_exec_shadow_observers( + &mut self, + fuzzer: &mut Z, + state: &mut S, + mgr: &mut EM, + input: &I, + ) -> Result<(), Error> { + self.shadow_observers + .post_exec_all(fuzzer, state, mgr, input) + } +} + +impl Executor for ShadowExecutor +where + E: Executor, + I: Input, + SOT: ObserversTuple, +{ + fn run_target( + &mut self, + fuzzer: &mut Z, + state: &mut S, + mgr: &mut EM, + input: &I, + ) -> Result { + self.executor.run_target(fuzzer, state, mgr, input) + } +} + +impl HasObservers for ShadowExecutor +where + E: HasObservers, + OT: ObserversTuple, + SOT: ObserversTuple, +{ + #[inline] + fn observers(&self) -> &OT { + self.executor.observers() + } + + #[inline] + fn observers_mut(&mut self) -> &mut OT { + self.executor.observers_mut() + } +} + +impl HasObserversHooks for ShadowExecutor +where + E: HasObservers, + I: Input, + OT: ObserversTuple + HasExecHooksTuple, + SOT: ObserversTuple + HasExecHooksTuple, +{ + /// Run the pre exec hook for all [`crate::observers::Observer`]`s` linked to this [`Executor`]. + #[inline] + fn pre_exec_observers( + &mut self, + fuzzer: &mut Z, + state: &mut S, + mgr: &mut EM, + input: &I, + ) -> Result<(), Error> { + if self.shadow_hooks { + self.shadow_observers + .pre_exec_all(fuzzer, state, mgr, input)?; + } + self.observers_mut().pre_exec_all(fuzzer, state, mgr, input) + } + + /// Run the post exec hook for all the [`crate::observers::Observer`]`s` linked to this [`Executor`]. + #[inline] + fn post_exec_observers( + &mut self, + fuzzer: &mut Z, + state: &mut S, + mgr: &mut EM, + input: &I, + ) -> Result<(), Error> { + if self.shadow_hooks { + self.shadow_observers + .post_exec_all(fuzzer, state, mgr, input)?; + } + self.observers_mut() + .post_exec_all(fuzzer, state, mgr, input) + } +} From 7abd7c816278eb7ec1e77a3e620a1446da039628 Mon Sep 17 00:00:00 2001 From: OB Date: Wed, 9 Jun 2021 15:11:43 +0300 Subject: [PATCH 2/6] Cmplog instrumentation for Frida (#99) * libafl_targets: refactor sancov trace-pc * cmp observer * libaf_targets: new structure to isolate sancov * fix C warning * combined executor * cmp observer and feedback * I2SRandReplace mutator * impl CmpMap for CmpLogMap in libafl_targets * cmplog observer * clippy * TracingStage * working random cmplog mutations * enable cmplog for libfuzzer_stb_image * re-enable new testcase stats print * fix update stats display * bump 0.3.1 * clippy * clippy * no clippy for fuzzers/ * fix * add cmplog runtime instrumentation * test cmplog against value profile feature * fix compile error * add target arch aarch64 for is_interesting_cmplog_instruction * add cfg target aarch64 on cmplog related code within stalker loop * revert changes in cargo.toml * align code with 'main' branch * revert accidently changed Cargo.toml file * update cmplog runtime code to work with the cmplog backend implementation * change magic to 8 bytes * cmplog runs with observer- no crashes * clippy fixes * add cmplog_runtime as feature * set cmplog command-line argument to false by default * setup cmplog observer and mutator correctly * decrease emitted code opcode count * add cmplog testing to the harness * get rid of irrelevant changes and unused code, add comments, change feature name to "cmplog" * get rid of some unessecery whitespaces and new lines * fix clippy errors Co-authored-by: Andrea Fioraldi Co-authored-by: Dominik Maier Co-authored-by: Omree --- fuzzers/frida_libpng/Cargo.toml | 5 +- fuzzers/frida_libpng/harness.cc | 222 ++++++++-------- fuzzers/frida_libpng/src/fuzzer.rs | 59 ++++- libafl/src/observers/cmp.rs | 1 + libafl_frida/Cargo.toml | 7 +- libafl_frida/src/cmplog_rt.rs | 101 +++++++ libafl_frida/src/helper.rs | 408 ++++++++++++++++++++++++++++- libafl_frida/src/lib.rs | 22 +- libafl_targets/src/sancov_cmp.c | 6 + scripts/build_all_fuzzers.sh | 2 +- 10 files changed, 711 insertions(+), 122 deletions(-) create mode 100644 libafl_frida/src/cmplog_rt.rs diff --git a/fuzzers/frida_libpng/Cargo.toml b/fuzzers/frida_libpng/Cargo.toml index 589c202717..cd4a2ee65c 100644 --- a/fuzzers/frida_libpng/Cargo.toml +++ b/fuzzers/frida_libpng/Cargo.toml @@ -21,10 +21,11 @@ num_cpus = "1.0" which = "4.1" [target.'cfg(unix)'.dependencies] -libafl = { path = "../../libafl/", features = [ "std", "llmp_bind_public" ] } #, "llmp_small_maps", "llmp_debug"]} -libafl_frida = { path = "../../libafl_frida" } +libafl = { path = "../../libafl/", features = [ "std", "llmp_compression", "llmp_bind_public" ] } #, "llmp_small_maps", "llmp_debug"]} capstone = "0.8.0" frida-gum = { version = "0.5.2", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] } +libafl_frida = { path = "../../libafl_frida", version = "0.3.2", features = ["cmplog"] } +libafl_targets = { path = "../../libafl_targets", version = "0.3.2" , features = ["sancov_cmplog"] } lazy_static = "1.4.0" libc = "0.2" libloading = "0.7.0" diff --git a/fuzzers/frida_libpng/harness.cc b/fuzzers/frida_libpng/harness.cc index ed74911b58..1ed473b50c 100644 --- a/fuzzers/frida_libpng/harness.cc +++ b/fuzzers/frida_libpng/harness.cc @@ -108,118 +108,122 @@ void func1() { // Roughly follows the libpng book example: // http://www.libpng.org/pub/png/book/chapter13.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - if (size < kPngHeaderSize) { - return 0; - } - - func1(); - - std::vector v(data, data + size); - if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) { - // not a PNG. - return 0; - } - - PngObjectHandler png_handler; - png_handler.png_ptr = nullptr; - png_handler.row_ptr = nullptr; - png_handler.info_ptr = nullptr; - png_handler.end_info_ptr = nullptr; - - png_handler.png_ptr = png_create_read_struct - (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (!png_handler.png_ptr) { - return 0; - } - - png_handler.info_ptr = png_create_info_struct(png_handler.png_ptr); - if (!png_handler.info_ptr) { - PNG_CLEANUP - return 0; - } - - png_handler.end_info_ptr = png_create_info_struct(png_handler.png_ptr); - if (!png_handler.end_info_ptr) { - PNG_CLEANUP - return 0; - } - - png_set_crc_action(png_handler.png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); -#ifdef PNG_IGNORE_ADLER32 - png_set_option(png_handler.png_ptr, PNG_IGNORE_ADLER32, PNG_OPTION_ON); -#endif - - // Setting up reading from buffer. - png_handler.buf_state = new BufState(); - png_handler.buf_state->data = data + kPngHeaderSize; - png_handler.buf_state->bytes_left = size - kPngHeaderSize; - png_set_read_fn(png_handler.png_ptr, png_handler.buf_state, user_read_data); - png_set_sig_bytes(png_handler.png_ptr, kPngHeaderSize); - - if (setjmp(png_jmpbuf(png_handler.png_ptr))) { - PNG_CLEANUP - return 0; - } - - // Reading. - png_read_info(png_handler.png_ptr, png_handler.info_ptr); - - // reset error handler to put png_deleter into scope. - if (setjmp(png_jmpbuf(png_handler.png_ptr))) { - PNG_CLEANUP - return 0; - } - - png_uint_32 width, height; - int bit_depth, color_type, interlace_type, compression_type; - int filter_type; - - if (!png_get_IHDR(png_handler.png_ptr, png_handler.info_ptr, &width, - &height, &bit_depth, &color_type, &interlace_type, - &compression_type, &filter_type)) { - PNG_CLEANUP - return 0; - } - - // This is going to be too slow. - if (width && height > 100000000 / width) { - PNG_CLEANUP -#ifdef HAS_DUMMY_CRASH - #ifdef __aarch64__ - asm volatile (".word 0xf7f0a000\n"); - #else - asm("ud2"); - #endif -#endif - return 0; - } - - // Set several transforms that browsers typically use: - png_set_gray_to_rgb(png_handler.png_ptr); - png_set_expand(png_handler.png_ptr); - png_set_packing(png_handler.png_ptr); - png_set_scale_16(png_handler.png_ptr); - png_set_tRNS_to_alpha(png_handler.png_ptr); - - int passes = png_set_interlace_handling(png_handler.png_ptr); - - png_read_update_info(png_handler.png_ptr, png_handler.info_ptr); - - png_handler.row_ptr = png_malloc( - png_handler.png_ptr, png_get_rowbytes(png_handler.png_ptr, - png_handler.info_ptr)); - - for (int pass = 0; pass < passes; ++pass) { - for (png_uint_32 y = 0; y < height; ++y) { - png_read_row(png_handler.png_ptr, - static_cast(png_handler.row_ptr), nullptr); + if (size >= 8 && *(uint64_t*)data == 0xABCDEFAA8F1324AA){ + abort(); + } + if (size < kPngHeaderSize) { + return 0; } - } - png_read_end(png_handler.png_ptr, png_handler.end_info_ptr); - PNG_CLEANUP - return 0; + func1(); + + std::vector v(data, data + size); + if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) { + // not a PNG. + return 0; + } + + PngObjectHandler png_handler; + png_handler.png_ptr = nullptr; + png_handler.row_ptr = nullptr; + png_handler.info_ptr = nullptr; + png_handler.end_info_ptr = nullptr; + + png_handler.png_ptr = png_create_read_struct + (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_handler.png_ptr) { + return 0; + } + + png_handler.info_ptr = png_create_info_struct(png_handler.png_ptr); + if (!png_handler.info_ptr) { + PNG_CLEANUP + return 0; + } + + png_handler.end_info_ptr = png_create_info_struct(png_handler.png_ptr); + if (!png_handler.end_info_ptr) { + PNG_CLEANUP + return 0; + } + + png_set_crc_action(png_handler.png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); +#ifdef PNG_IGNORE_ADLER32 + png_set_option(png_handler.png_ptr, PNG_IGNORE_ADLER32, PNG_OPTION_ON); +#endif + + // Setting up reading from buffer. + png_handler.buf_state = new BufState(); + png_handler.buf_state->data = data + kPngHeaderSize; + png_handler.buf_state->bytes_left = size - kPngHeaderSize; + png_set_read_fn(png_handler.png_ptr, png_handler.buf_state, user_read_data); + png_set_sig_bytes(png_handler.png_ptr, kPngHeaderSize); + + if (setjmp(png_jmpbuf(png_handler.png_ptr))) { + PNG_CLEANUP + return 0; + } + + // Reading. + png_read_info(png_handler.png_ptr, png_handler.info_ptr); + + // reset error handler to put png_deleter into scope. + if (setjmp(png_jmpbuf(png_handler.png_ptr))) { + PNG_CLEANUP + return 0; + } + + png_uint_32 width, height; + int bit_depth, color_type, interlace_type, compression_type; + int filter_type; + + if (!png_get_IHDR(png_handler.png_ptr, png_handler.info_ptr, &width, + &height, &bit_depth, &color_type, &interlace_type, + &compression_type, &filter_type)) { + PNG_CLEANUP + return 0; + } + + // This is going to be too slow. + if (width && height > 100000000 / width) { + PNG_CLEANUP +#ifdef HAS_DUMMY_CRASH +#ifdef __aarch64__ + asm volatile (".word 0xf7f0a000\n"); +#else + asm("ud2"); +#endif +#endif + return 0; + } + + // Set several transforms that browsers typically use: + png_set_gray_to_rgb(png_handler.png_ptr); + png_set_expand(png_handler.png_ptr); + png_set_packing(png_handler.png_ptr); + png_set_scale_16(png_handler.png_ptr); + png_set_tRNS_to_alpha(png_handler.png_ptr); + + int passes = png_set_interlace_handling(png_handler.png_ptr); + + png_read_update_info(png_handler.png_ptr, png_handler.info_ptr); + + png_handler.row_ptr = png_malloc( + png_handler.png_ptr, png_get_rowbytes(png_handler.png_ptr, + png_handler.info_ptr)); + + for (int pass = 0; pass < passes; ++pass) { + for (png_uint_32 y = 0; y < height; ++y) { + png_read_row(png_handler.png_ptr, + static_cast(png_handler.row_ptr), nullptr); + } + } + + png_read_end(png_handler.png_ptr, png_handler.end_info_ptr); + + PNG_CLEANUP + return 0; } diff --git a/fuzzers/frida_libpng/src/fuzzer.rs b/fuzzers/frida_libpng/src/fuzzer.rs index 76a5954d6e..a6f96c02eb 100644 --- a/fuzzers/frida_libpng/src/fuzzer.rs +++ b/fuzzers/frida_libpng/src/fuzzer.rs @@ -29,10 +29,11 @@ use libafl::{ inputs::{BytesInput, HasTargetBytes, Input}, mutators::{ scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator}, + token_mutations::I2SRandReplace, token_mutations::Tokens, }, observers::{HitcountsMapObserver, ObserversTuple, StdMapObserver, TimeObserver}, - stages::mutational::StdMutationalStage, + stages::{StdMutationalStage, TracingStage}, state::{HasCorpus, HasMetadata, StdState}, stats::MultiStats, Error, @@ -57,6 +58,7 @@ use libafl_frida::{ helper::{FridaHelper, FridaInstrumentationHelper, MAP_SIZE}, FridaOptions, }; +use libafl_targets::cmplog::{CmpLogObserver, CMPLOG_MAP}; struct FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT, S> where @@ -300,7 +302,7 @@ unsafe fn fuzz( unsafe extern "C" fn(data: *const u8, size: usize) -> i32, > = lib.get(symbol_name.as_bytes()).unwrap(); - let mut frida_harness = move |input: &BytesInput| { + let mut frida_harness = |input: &BytesInput| { let target = input.target_bytes(); let buf = target.as_slice(); (target_func)(buf.as_ptr(), buf.len()); @@ -316,6 +318,9 @@ unsafe fn fuzz( &modules_to_instrument, ); + // Create an observation channel using cmplog map + let cmplog_observer = CmpLogObserver::new("cmplog", &mut CMPLOG_MAP, true); + // Create an observation channel using the coverage map let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr( "edges", @@ -378,7 +383,6 @@ unsafe fn fuzz( // Setup a basic mutator with a mutational stage let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); - let mut stages = tuple_list!(StdMutationalStage::new(mutator)); // A minimization+queue policy to get testcasess from the corpus let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); @@ -413,7 +417,54 @@ unsafe fn fuzz( println!("We imported {} inputs from disk.", state.corpus().count()); } - fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + if frida_options.cmplog_enabled() { + // Secondary harness due to mut ownership + let mut frida_harness = |input: &BytesInput| { + let target = input.target_bytes(); + let buf = target.as_slice(); + (target_func)(buf.as_ptr(), buf.len()); + ExitKind::Ok + }; + + // Secondary helper due to mut ownership + let mut frida_helper = FridaInstrumentationHelper::new( + &gum, + &frida_options, + module_name, + &modules_to_instrument, + ); + + // Setup a tracing stage in which we log comparisons + let tracing = TracingStage::new(FridaInProcessExecutor::new( + &gum, + InProcessExecutor::new( + &mut frida_harness, + tuple_list!(cmplog_observer, AsanErrorsObserver::new(&ASAN_ERRORS)), + &mut fuzzer, + &mut state, + &mut mgr, + )?, + &mut frida_helper, + Duration::new(10, 0), + )); + + // Setup a randomic Input2State stage + let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!( + I2SRandReplace::new() + ))); + + // Setup a basic mutator + let mutational = StdMutationalStage::new(mutator); + + // The order of the stages matter! + let mut stages = tuple_list!(tracing, i2s, mutational); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + } else { + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + }; Ok(()) }; diff --git a/libafl/src/observers/cmp.rs b/libafl/src/observers/cmp.rs index 015487ab06..0c3d34e9a9 100644 --- a/libafl/src/observers/cmp.rs +++ b/libafl/src/observers/cmp.rs @@ -4,6 +4,7 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; + use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::{ diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index 016a8929e8..b8871b08de 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -10,12 +10,17 @@ license = "MIT OR Apache-2.0" keywords = ["fuzzing", "frida", "instrumentation"] edition = "2018" + +[features] +default = [] +cmplog = [] + [build-dependencies] cc = { version = "1.0", features = ["parallel"] } [dependencies] libafl = { path = "../libafl", version = "0.3.1", features = ["std", "libafl_derive"] } -libafl_targets = { path = "../libafl_targets", version = "0.3.2" } +libafl_targets = { path = "../libafl_targets", version = "0.3.2", features = ["sancov_cmplog"] } nix = "0.20.0" libc = "0.2.92" hashbrown = "0.11" diff --git a/libafl_frida/src/cmplog_rt.rs b/libafl_frida/src/cmplog_rt.rs new file mode 100644 index 0000000000..ab93bb67b1 --- /dev/null +++ b/libafl_frida/src/cmplog_rt.rs @@ -0,0 +1,101 @@ +use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; +use libafl_targets::cmplog::CMPLOG_MAP_W; +use std::ffi::c_void; + +extern crate libafl_targets; +extern "C" { + pub fn libafl_targets_cmplog_wrapper(k: u64, shape: u8, arg1: u64, arg2: u64); +} + +pub struct CmpLogRuntime { + ops_save_register_and_blr_to_populate: Option>, +} + +impl CmpLogRuntime { + #[must_use] + pub fn new() -> CmpLogRuntime { + Self { + ops_save_register_and_blr_to_populate: None, + } + } + + /// Call the external function that populates the `cmplog_map` with the relevant values + extern "C" fn populate_lists(&mut self, op1: u64, op2: u64, retaddr: u64) { + // println!( + // "entered populate_lists with: {:#02x}, {:#02x}, {:#02x}", + // op1, op2, retaddr + // ); + let mut k = (retaddr >> 4) ^ (retaddr << 8); + + k &= (CMPLOG_MAP_W as u64) - 1; + + unsafe { + libafl_targets_cmplog_wrapper(k, 8, op1, op2); + } + } + + /// Generate the instrumentation blobs for the current arch. + fn generate_instrumentation_blobs(&mut self) { + macro_rules! blr_to_populate { + ($ops:ident) => {dynasm!($ops + ; .arch aarch64 + ; stp x2, x3, [sp, #-0x10]! + ; stp x4, x5, [sp, #-0x10]! + ; stp x6, x7, [sp, #-0x10]! + ; stp x8, x9, [sp, #-0x10]! + ; stp x10, x11, [sp, #-0x10]! + ; stp x12, x13, [sp, #-0x10]! + ; stp x14, x15, [sp, #-0x10]! + ; stp x29, x30, [sp, #-0x10]! + // jump to rust based population of the lists + ; mov x2, x0 + ; adr x3, >done + ; ldr x4, >populate_lists + ; ldr x0, >self_addr + ; blr x4 + // restore the reg state before returning to the caller + ; ldp x29, x30, [sp], #0x10 + ; ldp x14, x15, [sp], #0x10 + ; ldp x12, x13, [sp], #0x10 + ; ldp x10, x11, [sp], #0x10 + ; ldp x8, x9, [sp], #0x10 + ; ldp x6, x7, [sp], #0x10 + ; ldp x4, x5, [sp], #0x10 + ; ldp x2, x3, [sp], #0x10 + ; b >done + ; self_addr: + ; .qword self as *mut _ as *mut c_void as i64 + ; populate_lists: + ; .qword CmpLogRuntime::populate_lists as *mut c_void as i64 + ; done: + );}; + } + + let mut ops_save_register_and_blr_to_populate = + dynasmrt::VecAssembler::::new(0); + blr_to_populate!(ops_save_register_and_blr_to_populate); + + self.ops_save_register_and_blr_to_populate = Some( + ops_save_register_and_blr_to_populate + .finalize() + .unwrap() + .into_boxed_slice(), + ); + } + pub fn init(&mut self) { + self.generate_instrumentation_blobs(); + } + + /// Get the blob which saves the context, jumps to the populate function and restores the context + #[inline] + #[must_use] + pub fn ops_save_register_and_blr_to_populate(&self) -> &[u8] { + self.ops_save_register_and_blr_to_populate.as_ref().unwrap() + } +} + +impl Default for CmpLogRuntime { + fn default() -> Self { + Self::new() + } +} diff --git a/libafl_frida/src/helper.rs b/libafl_frida/src/helper.rs index f294613376..adc5b1938b 100644 --- a/libafl_frida/src/helper.rs +++ b/libafl_frida/src/helper.rs @@ -33,9 +33,18 @@ use rangemap::RangeMap; use nix::sys::mman::{mmap, MapFlags, ProtFlags}; -use crate::FridaOptions; +use crate::{asan_rt::AsanRuntime, FridaOptions}; -use crate::asan_rt::AsanRuntime; +#[cfg(feature = "cmplog")] +use crate::cmplog_rt::CmpLogRuntime; + +#[cfg(feature = "cmplog")] +enum CmplogOperandType { + Regid(capstone::RegId), + Imm(u64), + Cimm(u64), + Mem(capstone::RegId, capstone::RegId, i32, u32), +} #[cfg(any(target_os = "macos", target_os = "ios"))] const ANONYMOUS_FLAG: MapFlags = MapFlags::MAP_ANON; @@ -80,6 +89,8 @@ pub struct FridaInstrumentationHelper<'a> { #[cfg(target_arch = "aarch64")] capstone: Capstone, asan_runtime: AsanRuntime, + #[cfg(feature = "cmplog")] + cmplog_runtime: CmpLogRuntime, ranges: RangeMap, module_map: ModuleMap, options: &'a FridaOptions, @@ -265,6 +276,8 @@ impl<'a> FridaInstrumentationHelper<'a> { .build() .expect("Failed to create Capstone object"), asan_runtime: AsanRuntime::new(options.clone()), + #[cfg(feature = "cmplog")] + cmplog_runtime: CmpLogRuntime::new(), ranges: RangeMap::new(), module_map: ModuleMap::new_from_names(modules_to_instrument), options, @@ -327,7 +340,7 @@ impl<'a> FridaInstrumentationHelper<'a> { todo!("Implement ASAN for non-aarch64 targets"); #[cfg(target_arch = "aarch64")] if let Ok((basereg, indexreg, displacement, width, shift, extender)) = - helper.is_interesting_instruction(address, instr) + helper.asan_is_interesting_instruction(address, instr) { helper.emit_shadow_check( address, @@ -341,6 +354,19 @@ impl<'a> FridaInstrumentationHelper<'a> { ); } } + if helper.options().cmplog_enabled() { + #[cfg(not(target_arch = "aarch64"))] + todo!("Implement cmplog for non-aarch64 targets"); + #[cfg(all(feature = "cmplog", target_arch = "aarch64"))] + // check if this instruction is a compare instruction and if so save the registers values + if let Ok((op1, op2)) = + helper.cmplog_is_interesting_instruction(address, instr) + { + //emit code that saves the relevant data in runtime(passes it to x0, x1) + helper.emit_comparison_handling(address, &output, op1, op2); + } + } + if helper.options().asan_enabled() || helper.options().drcov_enabled() { helper.asan_runtime.add_stalked_address( output.writer().pc() as usize - 4, @@ -355,6 +381,10 @@ impl<'a> FridaInstrumentationHelper<'a> { if helper.options().asan_enabled() || helper.options().drcov_enabled() { helper.asan_runtime.init(gum, modules_to_instrument); } + #[cfg(feature = "cmplog")] + if helper.options.cmplog_enabled() { + helper.cmplog_runtime.init(); + } } helper } @@ -370,6 +400,312 @@ impl<'a> FridaInstrumentationHelper<'a> { Aarch64Register::from_u32(regint as u32).unwrap() } + #[cfg(all(feature = "cmplog", target_arch = "aarch64"))] + #[inline] + /// Emit the instrumentation code which is responsible for opernads value extraction and cmplog map population + fn emit_comparison_handling( + &self, + _address: u64, + output: &StalkerOutput, + op1: CmplogOperandType, + op2: CmplogOperandType, + ) { + let writer = output.writer(); + + // Preserve x0, x1: + writer.put_stp_reg_reg_reg_offset( + Aarch64Register::X0, + Aarch64Register::X1, + Aarch64Register::Sp, + -(16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32) as i64, + IndexMode::PreAdjust, + ); + + // make sure operand1 value is saved into x0 + match op1 { + CmplogOperandType::Imm(value) | CmplogOperandType::Cimm(value) => { + writer.put_ldr_reg_u64(Aarch64Register::X0, value); + } + CmplogOperandType::Regid(reg) => { + let reg = self.writer_register(reg); + match reg { + Aarch64Register::X0 | Aarch64Register::W0 => {} + Aarch64Register::X1 | Aarch64Register::W1 => { + writer.put_mov_reg_reg(Aarch64Register::X0, Aarch64Register::X1); + } + _ => { + if !writer.put_mov_reg_reg(Aarch64Register::X0, reg) { + writer.put_mov_reg_reg(Aarch64Register::W0, reg); + } + } + } + } + CmplogOperandType::Mem(basereg, indexreg, displacement, _width) => { + let basereg = self.writer_register(basereg); + let indexreg = if indexreg.0 != 0 { + Some(self.writer_register(indexreg)) + } else { + None + }; + + // calculate base+index+displacment into x0 + let displacement = displacement + + if basereg == Aarch64Register::Sp { + 16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32 + } else { + 0 + }; + + if indexreg.is_some() { + if let Some(indexreg) = indexreg { + writer.put_add_reg_reg_reg(Aarch64Register::X0, basereg, indexreg); + } + } else { + match basereg { + Aarch64Register::X0 | Aarch64Register::W0 => {} + Aarch64Register::X1 | Aarch64Register::W1 => { + writer.put_mov_reg_reg(Aarch64Register::X0, Aarch64Register::X1); + } + _ => { + if !writer.put_mov_reg_reg(Aarch64Register::X0, basereg) { + writer.put_mov_reg_reg(Aarch64Register::W0, basereg); + } + } + } + } + + //add displacement + writer.put_add_reg_reg_imm( + Aarch64Register::X0, + Aarch64Register::X0, + displacement as u64, + ); + + //deref into x0 to get the real value + writer.put_ldr_reg_reg_offset(Aarch64Register::X0, Aarch64Register::X0, 0u64); + } + } + + // make sure operand2 value is saved into x1 + match op2 { + CmplogOperandType::Imm(value) | CmplogOperandType::Cimm(value) => { + writer.put_ldr_reg_u64(Aarch64Register::X1, value); + } + CmplogOperandType::Regid(reg) => { + let reg = self.writer_register(reg); + match reg { + Aarch64Register::X1 | Aarch64Register::W1 => {} + Aarch64Register::X0 | Aarch64Register::W0 => { + writer.put_ldr_reg_reg_offset( + Aarch64Register::X1, + Aarch64Register::Sp, + 0u64, + ); + } + _ => { + if !writer.put_mov_reg_reg(Aarch64Register::X1, reg) { + writer.put_mov_reg_reg(Aarch64Register::W1, reg); + } + } + } + } + CmplogOperandType::Mem(basereg, indexreg, displacement, _width) => { + let basereg = self.writer_register(basereg); + let indexreg = if indexreg.0 != 0 { + Some(self.writer_register(indexreg)) + } else { + None + }; + + // calculate base+index+displacment into x1 + let displacement = displacement + + if basereg == Aarch64Register::Sp { + 16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32 + } else { + 0 + }; + + if indexreg.is_some() { + if let Some(indexreg) = indexreg { + match indexreg { + Aarch64Register::X0 | Aarch64Register::W0 => { + match basereg { + Aarch64Register::X1 | Aarch64Register::W1 => { + // x0 is overwrittern indexreg by op1 value. + // x1 is basereg + + // Preserve x2, x3: + writer.put_stp_reg_reg_reg_offset( + Aarch64Register::X2, + Aarch64Register::X3, + Aarch64Register::Sp, + -(16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32) as i64, + IndexMode::PreAdjust, + ); + + //reload indexreg to x2 + writer.put_ldr_reg_reg_offset( + Aarch64Register::X2, + Aarch64Register::Sp, + 0u64, + ); + //add them into basereg==x1 + writer.put_add_reg_reg_reg( + basereg, + basereg, + Aarch64Register::X2, + ); + + // Restore x2, x3 + assert!(writer.put_ldp_reg_reg_reg_offset( + Aarch64Register::X2, + Aarch64Register::X3, + Aarch64Register::Sp, + 16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i64, + IndexMode::PostAdjust, + )); + } + _ => { + // x0 is overwrittern indexreg by op1 value. + // basereg is not x1 nor x0 + + //reload indexreg to x1 + writer.put_ldr_reg_reg_offset( + Aarch64Register::X1, + Aarch64Register::Sp, + 0u64, + ); + //add basereg into indexreg==x1 + writer.put_add_reg_reg_reg( + Aarch64Register::X1, + basereg, + Aarch64Register::X1, + ); + } + } + } + Aarch64Register::X1 | Aarch64Register::W1 => { + match basereg { + Aarch64Register::X0 | Aarch64Register::W0 => { + // x0 is overwrittern basereg by op1 value. + // x1 is indexreg + + // Preserve x2, x3: + writer.put_stp_reg_reg_reg_offset( + Aarch64Register::X2, + Aarch64Register::X3, + Aarch64Register::Sp, + -(16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32) as i64, + IndexMode::PreAdjust, + ); + + //reload basereg to x2 + writer.put_ldr_reg_reg_offset( + Aarch64Register::X2, + Aarch64Register::Sp, + 0u64, + ); + //add basereg into indexreg==x1 + writer.put_add_reg_reg_reg( + indexreg, + Aarch64Register::X2, + indexreg, + ); + + // Restore x2, x3 + assert!(writer.put_ldp_reg_reg_reg_offset( + Aarch64Register::X2, + Aarch64Register::X3, + Aarch64Register::Sp, + 16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i64, + IndexMode::PostAdjust, + )); + } + _ => { + // indexreg is x1 + // basereg is not x0 and not x1 + + //add them into x1 + writer.put_add_reg_reg_reg(indexreg, basereg, indexreg); + } + } + } + _ => { + match basereg { + Aarch64Register::X0 | Aarch64Register::W0 => { + //basereg is overwrittern by op1 value + //index reg is not x0 nor x1 + + //reload basereg to x1 + writer.put_ldr_reg_reg_offset( + Aarch64Register::X1, + Aarch64Register::Sp, + 0u64, + ); + //add indexreg to basereg==x1 + writer.put_add_reg_reg_reg( + Aarch64Register::X1, + Aarch64Register::X1, + indexreg, + ); + } + _ => { + //basereg is not x0, can be x1 + //index reg is not x0 nor x1 + + //add them into x1 + writer.put_add_reg_reg_reg( + Aarch64Register::X1, + basereg, + indexreg, + ); + } + } + } + } + } + } else { + match basereg { + Aarch64Register::X1 | Aarch64Register::W1 => {} + Aarch64Register::X0 | Aarch64Register::W0 => { + // x0 is overwrittern basereg by op1 value. + //reload basereg to x1 + writer.put_ldr_reg_reg_offset( + Aarch64Register::X1, + Aarch64Register::Sp, + 0u64, + ); + } + _ => { + writer.put_mov_reg_reg(Aarch64Register::W1, basereg); + } + } + } + + // add displacement + writer.put_add_reg_reg_imm( + Aarch64Register::X1, + Aarch64Register::X1, + displacement as u64, + ); + //deref into x1 to get the real value + writer.put_ldr_reg_reg_offset(Aarch64Register::X1, Aarch64Register::X1, 0u64); + } + } + + //call cmplog runtime to populate the values map + writer.put_bytes(&self.cmplog_runtime.ops_save_register_and_blr_to_populate()); + + // Restore x0, x1 + assert!(writer.put_ldp_reg_reg_reg_offset( + Aarch64Register::X0, + Aarch64Register::X1, + Aarch64Register::Sp, + 16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i64, + IndexMode::PostAdjust, + )); + } + #[cfg(target_arch = "aarch64")] #[inline] fn emit_shadow_check( @@ -647,7 +983,7 @@ impl<'a> FridaInstrumentationHelper<'a> { #[cfg(target_arch = "aarch64")] #[inline] - fn is_interesting_instruction( + fn asan_is_interesting_instruction( &self, _address: u64, instr: &Insn, @@ -697,6 +1033,70 @@ impl<'a> FridaInstrumentationHelper<'a> { Err(()) } + #[cfg(all(feature = "cmplog", target_arch = "aarch64"))] + #[inline] + /// Check if the current instruction is cmplog relevant one(any opcode which sets the flags) + fn cmplog_is_interesting_instruction( + &self, + _address: u64, + instr: &Insn, + ) -> Result<(CmplogOperandType, CmplogOperandType), ()> { + // We only care for compare instrunctions - aka instructions which set the flags + match instr.mnemonic().unwrap() { + "cmp" | "ands" | "subs" | "adds" | "negs" | "ngcs" | "sbcs" | "bics" | "cls" => (), + _ => return Err(()), + } + let operands = self + .capstone + .insn_detail(instr) + .unwrap() + .arch_detail() + .operands(); + if operands.len() != 2 { + return Err(()); + } + + let operand1 = if let Arm64Operand(arm64operand) = operands.first().unwrap() { + match arm64operand.op_type { + Arm64OperandType::Reg(regid) => Some(CmplogOperandType::Regid(regid)), + Arm64OperandType::Imm(val) => Some(CmplogOperandType::Imm(val as u64)), + Arm64OperandType::Mem(opmem) => Some(CmplogOperandType::Mem( + opmem.base(), + opmem.index(), + opmem.disp(), + self.instruction_width(instr, &operands), + )), + Arm64OperandType::Cimm(val) => Some(CmplogOperandType::Cimm(val as u64)), + _ => return Err(()), + } + } else { + None + }; + + let operand2 = if let Arm64Operand(arm64operand2) = operands.last().unwrap() { + match arm64operand2.op_type { + Arm64OperandType::Reg(regid) => Some(CmplogOperandType::Regid(regid)), + Arm64OperandType::Imm(val) => Some(CmplogOperandType::Imm(val as u64)), + Arm64OperandType::Mem(opmem) => Some(CmplogOperandType::Mem( + opmem.base(), + opmem.index(), + opmem.disp(), + self.instruction_width(instr, &operands), + )), + Arm64OperandType::Cimm(val) => Some(CmplogOperandType::Cimm(val as u64)), + _ => return Err(()), + } + } else { + None + }; + + if operand1.is_some() && operand2.is_some() { + Ok((operand1.unwrap(), operand2.unwrap())) + } else { + Err(()) + } + } + #[inline] fn emit_coverage_mapping(&mut self, address: u64, output: &StalkerOutput) { let writer = output.writer(); diff --git a/libafl_frida/src/lib.rs b/libafl_frida/src/lib.rs index c6f0f47b06..31063c899b 100644 --- a/libafl_frida/src/lib.rs +++ b/libafl_frida/src/lib.rs @@ -9,7 +9,12 @@ pub mod alloc; pub mod asan_errors; /// The frida address sanitizer runtime pub mod asan_rt; -/// The `LibAFL` frida helper + +#[cfg(feature = "cmplog")] +/// The frida cmplog runtime +pub mod cmplog_rt; + +/// The `LibAFL` firda helper pub mod helper; // for parsing asan cores @@ -28,6 +33,7 @@ pub struct FridaOptions { enable_coverage: bool, enable_drcov: bool, instrument_suppress_locations: Option>, + enable_cmplog: bool, } impl FridaOptions { @@ -100,6 +106,12 @@ impl FridaOptions { ); } } + "cmplog" => { + options.enable_cmplog = value.parse().unwrap(); + if !cfg!(feature = "cmplog") && options.enable_cmplog { + panic!("cmplog feature is disabled!") + } + } _ => { panic!("unknown FRIDA option: '{}'", option); } @@ -144,6 +156,13 @@ impl FridaOptions { self.enable_drcov } + /// Is `CmpLog` enabled? + #[must_use] + #[inline] + pub fn cmplog_enabled(&self) -> bool { + self.enable_cmplog + } + /// Should ASAN detect leaks #[must_use] #[inline] @@ -190,6 +209,7 @@ impl Default for FridaOptions { enable_coverage: true, enable_drcov: false, instrument_suppress_locations: None, + enable_cmplog: false, } } } diff --git a/libafl_targets/src/sancov_cmp.c b/libafl_targets/src/sancov_cmp.c index 33cbd31daf..8e4075e871 100644 --- a/libafl_targets/src/sancov_cmp.c +++ b/libafl_targets/src/sancov_cmp.c @@ -72,6 +72,12 @@ void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2) { } +#ifdef SANCOV_CMPLOG +void libafl_targets_cmplog_wrapper(uintptr_t k, uint8_t shape, uint64_t arg1, uint64_t arg2){ + return __libafl_targets_cmplog(k, shape, arg1, arg2); +} +#endif + void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases) { uintptr_t rt = RETADDR; diff --git a/scripts/build_all_fuzzers.sh b/scripts/build_all_fuzzers.sh index 6c6586d176..f96de961e7 100755 --- a/scripts/build_all_fuzzers.sh +++ b/scripts/build_all_fuzzers.sh @@ -11,7 +11,7 @@ do echo "[+] Checking fmt and building $fuzzer" cd $fuzzer \ && cargo fmt --all -- --check \ - && cargo clippy \ + && cargo clippy \ && cargo build \ && cd .. \ || exit 1 From 2b16e924613596bc86a3c4019fc852eef9f53325 Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Wed, 9 Jun 2021 14:17:43 +0200 Subject: [PATCH 3/6] introduce ShadowTracingStage --- libafl/src/executors/shadow.rs | 12 ++++- libafl/src/stages/tracing.rs | 81 ++++++++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/libafl/src/executors/shadow.rs b/libafl/src/executors/shadow.rs index 99441fcf59..a3cec08f43 100644 --- a/libafl/src/executors/shadow.rs +++ b/libafl/src/executors/shadow.rs @@ -1,4 +1,4 @@ -//! A `ShadowExecutor` wraps an executor to have shadow observer that will not be considered by the manager +//! A `ShadowExecutor` wraps an executor to have shadow observer that will not be considered by the feedbacks and the manager use crate::{ executors::{Executor, ExitKind, HasExecHooksTuple, HasObservers, HasObserversHooks}, @@ -27,7 +27,7 @@ pub trait HasShadowObserverHooks { ) -> Result<(), Error>; } -/// A [`ShadowExecutor`] wraps a primary executor, forwarding its methods, and a secondary one +/// A [`ShadowExecutor`] wraps an executor and a set of shadow observers pub struct ShadowExecutor { executor: E, shadow_observers: SOT, @@ -70,6 +70,14 @@ where pub fn shadow_observers_mut(&mut self) -> &mut SOT { &mut self.shadow_observers } + + pub fn shadow_hooks(&self) -> &bool { + &self.shadow_hooks + } + + pub fn shadow_hooks_mut(&mut self) -> &mut bool { + &mut self.shadow_hooks + } } impl HasShadowObserverHooks for ShadowExecutor diff --git a/libafl/src/stages/tracing.rs b/libafl/src/stages/tracing.rs index 4e37dce554..1a564c116e 100644 --- a/libafl/src/stages/tracing.rs +++ b/libafl/src/stages/tracing.rs @@ -2,7 +2,7 @@ use core::{marker::PhantomData, mem::drop}; use crate::{ corpus::Corpus, - executors::{Executor, HasExecHooksTuple, HasObservers, HasObserversHooks}, + executors::{Executor, HasExecHooksTuple, HasObservers, HasObserversHooks, ShadowExecutor}, inputs::Input, mark_feature_time, observers::ObserversTuple, @@ -15,7 +15,7 @@ use crate::{ #[cfg(feature = "introspection")] use crate::stats::PerfFeature; -/// The default mutational stage +/// A stage that runs a tracer executor #[derive(Clone, Debug)] pub struct TracingStage where @@ -87,7 +87,7 @@ where OT: ObserversTuple + HasExecHooksTuple, S: HasClientPerfStats + HasExecutions + HasCorpus, { - /// Creates a new default mutational stage + /// Creates a new default stage pub fn new(tracer_executor: TE) -> Self { Self { tracer_executor, @@ -95,3 +95,78 @@ where } } } + +/// A stage that runs the shadow executor using also the shadow observers +#[derive(Clone, Debug)] +pub struct ShadowTracingStage { + #[allow(clippy::type_complexity)] + phantom: PhantomData<(C, E, EM, I, OT, S, SOT, Z)>, +} + +impl Stage, EM, S, Z> + for ShadowTracingStage +where + I: Input, + C: Corpus, + E: Executor + HasObservers + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, + SOT: ObserversTuple + HasExecHooksTuple, + S: HasClientPerfStats + HasExecutions + HasCorpus, +{ + #[inline] + fn perform( + &mut self, + fuzzer: &mut Z, + executor: &mut ShadowExecutor, + state: &mut S, + manager: &mut EM, + corpus_idx: usize, + ) -> Result<(), Error> { + start_timer!(state); + let input = state + .corpus() + .get(corpus_idx)? + .borrow_mut() + .load_input()? + .clone(); + mark_feature_time!(state, PerfFeature::GetInputFromCorpus); + + let prev_shadow_hooks = *executor.shadow_hooks(); + *executor.shadow_hooks_mut() = true; + + start_timer!(state); + executor.pre_exec_observers(fuzzer, state, manager, &input)?; + mark_feature_time!(state, PerfFeature::PreExecObservers); + + start_timer!(state); + drop(executor.run_target(fuzzer, state, manager, &input)?); + mark_feature_time!(state, PerfFeature::TargetExecution); + + *state.executions_mut() += 1; + + start_timer!(state); + executor.post_exec_observers(fuzzer, state, manager, &input)?; + mark_feature_time!(state, PerfFeature::PostExecObservers); + + *executor.shadow_hooks_mut() = prev_shadow_hooks; + + Ok(()) + } +} + +impl ShadowTracingStage +where + I: Input, + C: Corpus, + E: Executor + HasObservers + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, + SOT: ObserversTuple + HasExecHooksTuple, + S: HasClientPerfStats + HasExecutions + HasCorpus, +{ + /// Creates a new default stage + pub fn new(_executor: &mut ShadowExecutor) -> Self { + Self { + phantom: PhantomData, + } + } +} From 8fa654dd61002bd45f5df12fc7347d84d3033882 Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Wed, 9 Jun 2021 14:34:38 +0200 Subject: [PATCH 4/6] fix generics for ShadowExecutor --- libafl/src/executors/combined.rs | 7 ++++++- libafl/src/executors/shadow.rs | 2 +- libafl/src/stages/mod.rs | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libafl/src/executors/combined.rs b/libafl/src/executors/combined.rs index a4ae7b3ccb..9d1b3225e8 100644 --- a/libafl/src/executors/combined.rs +++ b/libafl/src/executors/combined.rs @@ -15,7 +15,12 @@ pub struct CombinedExecutor { impl CombinedExecutor { /// Create a new `CombinedExecutor`, wrapping the given `executor`s. - pub fn new(primary: A, secondary: B) -> Self { + pub fn new(primary: A, secondary: B) -> Self + where + A: Executor, + B: Executor, + I: Input, + { Self { primary, secondary } } diff --git a/libafl/src/executors/shadow.rs b/libafl/src/executors/shadow.rs index a3cec08f43..2f15dc1888 100644 --- a/libafl/src/executors/shadow.rs +++ b/libafl/src/executors/shadow.rs @@ -40,7 +40,7 @@ where SOT: ObserversTuple, { /// Create a new `ShadowExecutor`, wrapping the given `executor`. - pub fn new(executor: E, shadow_observers: SOT) -> Self { + pub fn new(executor: E, shadow_observers: SOT) -> Self { Self { executor, shadow_observers, diff --git a/libafl/src/stages/mod.rs b/libafl/src/stages/mod.rs index a9eebc8316..5042d8e680 100644 --- a/libafl/src/stages/mod.rs +++ b/libafl/src/stages/mod.rs @@ -9,7 +9,7 @@ pub mod mutational; pub use mutational::{MutationalStage, StdMutationalStage}; pub mod tracing; -pub use tracing::TracingStage; +pub use tracing::{ShadowTracingStage, TracingStage}; //pub mod power; //pub use power::PowerMutationalStage; From 308e9c7fe94c79c9a018d00324fda349c21c5dfe Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Wed, 9 Jun 2021 14:38:45 +0200 Subject: [PATCH 5/6] adapt fuzzers/libfuzzer_stb_image to use ShadowTracingStage for CmpLog --- fuzzers/libfuzzer_stb_image/src/main.rs | 37 +++++++++---------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/fuzzers/libfuzzer_stb_image/src/main.rs b/fuzzers/libfuzzer_stb_image/src/main.rs index c718b1ad2e..16eb76ae28 100644 --- a/fuzzers/libfuzzer_stb_image/src/main.rs +++ b/fuzzers/libfuzzer_stb_image/src/main.rs @@ -10,7 +10,7 @@ use libafl::{ QueueCorpusScheduler, }, events::setup_restarting_mgr_std, - executors::{inprocess::InProcessExecutor, ExitKind}, + executors::{inprocess::InProcessExecutor, ExitKind, ShadowExecutor}, feedback_or, feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback}, fuzzer::{Fuzzer, StdFuzzer}, @@ -18,7 +18,7 @@ use libafl::{ mutators::scheduled::{havoc_mutations, StdScheduledMutator}, mutators::token_mutations::I2SRandReplace, observers::{StdMapObserver, TimeObserver}, - stages::{StdMutationalStage, TracingStage}, + stages::{ShadowTracingStage, StdMutationalStage}, state::{HasCorpus, StdState}, stats::MultiStats, Error, @@ -123,13 +123,16 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re }; // Create the executor for an in-process function with just one observer for edge coverage - let mut executor = InProcessExecutor::new( - &mut harness, - tuple_list!(edges_observer, time_observer), - &mut fuzzer, - &mut state, - &mut restarting_mgr, - )?; + let mut executor = ShadowExecutor::new( + InProcessExecutor::new( + &mut harness, + tuple_list!(edges_observer, time_observer), + &mut fuzzer, + &mut state, + &mut restarting_mgr, + )?, + tuple_list!(cmplog_observer), + ); // The actual target run starts here. // Call LLVMFUzzerInitialize() if present. @@ -151,22 +154,8 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re println!("We imported {} inputs from disk.", state.corpus().count()); } - // Secondary harness due to mut ownership - let mut harness = |input: &BytesInput| { - let target = input.target_bytes(); - let buf = target.as_slice(); - libfuzzer_test_one_input(buf); - ExitKind::Ok - }; - // Setup a tracing stage in which we log comparisons - let tracing = TracingStage::new(InProcessExecutor::new( - &mut harness, - tuple_list!(cmplog_observer), - &mut fuzzer, - &mut state, - &mut restarting_mgr, - )?); + let tracing = ShadowTracingStage::new(&mut executor); // Setup a randomic Input2State stage let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(I2SRandReplace::new()))); From 6b235472e0f0f2664ac08f6c076c47b02393163d Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Wed, 9 Jun 2021 14:51:48 +0200 Subject: [PATCH 6/6] Added load_initial_inputs_forced to add all inputs to a corpus (fixes #123) (#158) Co-authored-by: Andrea Fioraldi --- libafl/src/fuzzer.rs | 48 ++++++++++++++++++++++++++ libafl/src/state/mod.rs | 76 ++++++++++++++++++++++++++++++++--------- 2 files changed, 107 insertions(+), 17 deletions(-) diff --git a/libafl/src/fuzzer.rs b/libafl/src/fuzzer.rs index a24b3fb924..300441cdfa 100644 --- a/libafl/src/fuzzer.rs +++ b/libafl/src/fuzzer.rs @@ -106,6 +106,18 @@ pub trait Evaluator { manager: &mut EM, input: I, ) -> Result<(bool, Option), Error>; + + /// Runs the input and triggers observers and feedback. + /// Adds an input, to the corpus even if it's not considered `interesting` by the `feedback`. + /// Returns the `index` of the new testcase in the corpus. + /// Usually, you want to use [`Evaluator::evaluate_input`], unless you know what you are doing. + fn add_input( + &mut self, + state: &mut S, + executor: &mut E, + manager: &mut EM, + input: I, + ) -> Result; } /// The main fuzzer trait. @@ -391,6 +403,42 @@ where } } } + + /// Adds an input, even if it's not conisered `interesting` by any of the executors + fn add_input( + &mut self, + state: &mut S, + executor: &mut E, + manager: &mut EM, + input: I, + ) -> Result { + let _ = self.execute_input(state, executor, manager, &input)?; + let observers = executor.observers(); + // Always consider this to be "interesting" + + // Not a solution + self.objective_mut().discard_metadata(state, &input)?; + + // Add the input to the main corpus + let mut testcase = Testcase::new(input.clone()); + self.feedback_mut().append_metadata(state, &mut testcase)?; + let idx = state.corpus_mut().add(testcase)?; + self.scheduler_mut().on_add(state, idx)?; + + let observers_buf = manager.serialize_observers(observers)?; + manager.fire( + state, + Event::NewTestcase { + input, + observers_buf, + corpus_size: state.corpus().count(), + client_config: "TODO".into(), + time: current_time(), + executions: *state.executions(), + }, + )?; + Ok(idx) + } } impl Fuzzer diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index 4b7d791df5..5a387fd17f 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -355,12 +355,15 @@ where SC: Corpus, { /// loads inputs from a directory + /// If `forced` is `true`, the value will be loaded, + /// even if it's not considered to be `interesting`. fn load_from_directory( &mut self, fuzzer: &mut Z, executor: &mut E, manager: &mut EM, in_dir: &Path, + forced: bool, ) -> Result<(), Error> where Z: Evaluator, @@ -379,18 +382,69 @@ where if attr.is_file() && attr.len() > 0 { println!("Loading file {:?} ...", &path); let input = I::from_file(&path)?; - let (is_interesting, _) = fuzzer.evaluate_input(self, executor, manager, input)?; - if !is_interesting { - println!("File {:?} was not interesting, skipped.", &path); + if forced { + let _ = fuzzer.add_input(self, executor, manager, input)?; + } else { + let (is_interesting, _) = + fuzzer.evaluate_input(self, executor, manager, input)?; + if !is_interesting { + println!("File {:?} was not interesting, skipped.", &path); + } } } else if attr.is_dir() { - self.load_from_directory(fuzzer, executor, manager, &path)?; + self.load_from_directory(fuzzer, executor, manager, &path, forced)?; } } Ok(()) } + /// Loads initial inputs from the passed-in `in_dirs`. + /// If `forced` is true, will add all testcases, no matter what. + fn load_initial_inputs_internal( + &mut self, + fuzzer: &mut Z, + executor: &mut E, + manager: &mut EM, + in_dirs: &[PathBuf], + forced: bool, + ) -> Result<(), Error> + where + Z: Evaluator, + EM: EventManager, + { + for in_dir in in_dirs { + self.load_from_directory(fuzzer, executor, manager, in_dir, forced)?; + } + manager.fire( + self, + Event::Log { + severity_level: LogSeverity::Debug, + message: format!("Loaded {} initial testcases.", self.corpus().count()), // get corpus count + phantom: PhantomData, + }, + )?; + manager.process(fuzzer, self, executor)?; + Ok(()) + } + + /// Loads all intial inputs, even if they are not consiered `intesting`. + /// This is rarely the right method, use `load_initial_inputs`, + /// and potentially fix your `Feedback`, instead. + pub fn load_initial_inputs_forced( + &mut self, + fuzzer: &mut Z, + executor: &mut E, + manager: &mut EM, + in_dirs: &[PathBuf], + ) -> Result<(), Error> + where + Z: Evaluator, + EM: EventManager, + { + self.load_initial_inputs_internal(fuzzer, executor, manager, in_dirs, true) + } + /// Loads initial inputs from the passed-in `in_dirs`. pub fn load_initial_inputs( &mut self, @@ -403,19 +457,7 @@ where Z: Evaluator, EM: EventManager, { - for in_dir in in_dirs { - self.load_from_directory(fuzzer, executor, manager, in_dir)?; - } - manager.fire( - self, - Event::Log { - severity_level: LogSeverity::Debug, - message: format!("Loaded {} initial testcases.", self.corpus().count()), // get corpus count - phantom: PhantomData, - }, - )?; - manager.process(fuzzer, self, executor)?; - Ok(()) + self.load_initial_inputs_internal(fuzzer, executor, manager, in_dirs, false) } }