From e15c17fd75a5183ad93fdad71ef0b32b169eec2f Mon Sep 17 00:00:00 2001 From: tokatoka Date: Fri, 30 Apr 2021 04:08:25 +0900 Subject: [PATCH 01/12] add reachability observer/feedback --- libafl/src/feedbacks/mod.rs | 39 ++++++++++++++++++++++++++++++++++++- libafl/src/observers/mod.rs | 34 +++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 6cb8261383..cef7110074 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -11,7 +11,7 @@ use crate::{ corpus::Testcase, executors::ExitKind, inputs::Input, - observers::{ObserversTuple, TimeObserver}, + observers::{ObserversTuple, TimeObserver, ReachabilityObserver}, Error, }; @@ -252,3 +252,40 @@ impl Default for TimeFeedback { Self::new() } } + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ReachabilityFeedback { +} + +impl ReachabilityFeedback{ + pub fn new() -> Self{ + Self{} + } +} + +impl Feedback for ReachabilityFeedback +where + I: Input, +{ + fn is_interesting( + &mut self, + _input: &I, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result { + let observer = observers.match_first_type::().unwrap(); + if observer.has_reached(){ + Ok(1) + } + else{ + Ok(0) + } + } +} + +impl Named for ReachabilityFeedback{ + #[inline] + fn name(&self) -> &str{ + "ReachabilityFeedback" + } +} \ No newline at end of file diff --git a/libafl/src/observers/mod.rs b/libafl/src/observers/mod.rs index 2ff66ca5fc..320c745299 100644 --- a/libafl/src/observers/mod.rs +++ b/libafl/src/observers/mod.rs @@ -11,7 +11,7 @@ use core::time::Duration; use serde::{Deserialize, Serialize}; use crate::{ - bolts::tuples::{MatchFirstType, MatchNameAndType, MatchType, Named}, + bolts::{tuples::{MatchFirstType, MatchNameAndType, MatchType, Named}, ownedref::OwnedPtrMut}, utils::current_time, Error, }; @@ -132,6 +132,38 @@ impl Named for TimeObserver { } } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ReachabilityObserver{ + name: String, + counter: OwnedPtrMut, +} + +impl ReachabilityObserver{ + pub fn new(name: &'static str, ctr: &'static mut usize) -> Self { + Self{ + name: name.to_string(), + counter: OwnedPtrMut::Ptr(ctr), + } + } + + pub fn has_reached(&self) -> bool { + *self.counter.as_ref() > 0 + } +} + +impl Observer for ReachabilityObserver{ + fn pre_exec(&mut self) -> Result<(), Error>{ + *self.counter.as_mut() = 0; + Ok(()) + } +} + +impl Named for ReachabilityObserver{ + fn name(&self) -> &str{ + &self.name + } +} + #[cfg(feature = "std")] #[cfg(test)] mod tests { From d8d6db4b286e64acdbfe93844cd77f9c028bf6af Mon Sep 17 00:00:00 2001 From: tokatoka Date: Fri, 30 Apr 2021 04:40:15 +0900 Subject: [PATCH 02/12] add fuzzer exmaple --- fuzzers/libfuzzer_reachability/.gitignore | 1 + fuzzers/libfuzzer_reachability/Cargo.toml | 30 +++ fuzzers/libfuzzer_reachability/README.md | 79 +++++++ .../corpus/not_kitty.png | Bin 0 -> 218 bytes .../corpus/not_kitty_alpha.png | Bin 0 -> 376 bytes .../corpus/not_kitty_gamma.png | Bin 0 -> 228 bytes .../corpus/not_kitty_icc.png | Bin 0 -> 427 bytes fuzzers/libfuzzer_reachability/diff.patch | 12 ++ fuzzers/libfuzzer_reachability/harness.cc | 197 ++++++++++++++++++ .../src/bin/libafl_cc.rs | 33 +++ .../src/bin/libafl_cxx.rs | 34 +++ fuzzers/libfuzzer_reachability/src/lib.rs | 177 ++++++++++++++++ fuzzers/libfuzzer_reachability/test.sh | 10 + 13 files changed, 573 insertions(+) create mode 100644 fuzzers/libfuzzer_reachability/.gitignore create mode 100644 fuzzers/libfuzzer_reachability/Cargo.toml create mode 100644 fuzzers/libfuzzer_reachability/README.md create mode 100644 fuzzers/libfuzzer_reachability/corpus/not_kitty.png create mode 100644 fuzzers/libfuzzer_reachability/corpus/not_kitty_alpha.png create mode 100644 fuzzers/libfuzzer_reachability/corpus/not_kitty_gamma.png create mode 100644 fuzzers/libfuzzer_reachability/corpus/not_kitty_icc.png create mode 100644 fuzzers/libfuzzer_reachability/diff.patch create mode 100644 fuzzers/libfuzzer_reachability/harness.cc create mode 100644 fuzzers/libfuzzer_reachability/src/bin/libafl_cc.rs create mode 100644 fuzzers/libfuzzer_reachability/src/bin/libafl_cxx.rs create mode 100644 fuzzers/libfuzzer_reachability/src/lib.rs create mode 100755 fuzzers/libfuzzer_reachability/test.sh diff --git a/fuzzers/libfuzzer_reachability/.gitignore b/fuzzers/libfuzzer_reachability/.gitignore new file mode 100644 index 0000000000..a977a2ca5b --- /dev/null +++ b/fuzzers/libfuzzer_reachability/.gitignore @@ -0,0 +1 @@ +libpng-* \ No newline at end of file diff --git a/fuzzers/libfuzzer_reachability/Cargo.toml b/fuzzers/libfuzzer_reachability/Cargo.toml new file mode 100644 index 0000000000..589fc6bb6b --- /dev/null +++ b/fuzzers/libfuzzer_reachability/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "libfuzzer_libpng" +version = "0.1.0" +authors = ["Andrea Fioraldi ", "Dominik Maier "] +edition = "2018" + +[features] +default = ["std"] +std = [] + +#[profile.release] +#lto = true +#codegen-units = 1 +#opt-level = 3 +#debug = true + +[build-dependencies] +cc = { version = "1.0", features = ["parallel"] } +which = { version = "4.0.2" } +num_cpus = "1.0" + +[dependencies] +libafl = { path = "../../libafl/" } +libafl_targets = { path = "../../libafl_targets/", features = ["pcguard_hitcounts", "libfuzzer"] } +# TODO Include it only when building cc +libafl_cc = { path = "../../libafl_cc/" } + +[lib] +name = "libfuzzer_libpng" +crate-type = ["staticlib"] diff --git a/fuzzers/libfuzzer_reachability/README.md b/fuzzers/libfuzzer_reachability/README.md new file mode 100644 index 0000000000..922667e8fe --- /dev/null +++ b/fuzzers/libfuzzer_reachability/README.md @@ -0,0 +1,79 @@ +# Libfuzzer for libpng + +This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection. + +In contrast to other fuzzer examples, this setup uses `fuzz_loop_for`, to occasionally respawn the fuzzer executor. +While this costs performance, it can be useful for targets with memory leaks or other instabilities. +If your target is really instable, however, consider exchanging the `InProcessExecutor` for a `ForkserverExecutor` instead. + +To show off crash detection, we added a `ud2` instruction to the harness, edit harness.cc if you want a non-crashing example. +It has been tested on Linux. + +## Build + +To build this example, run + +```bash +cargo build --release +``` + +This will build the library with the fuzzer (src/lib.rs) with the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback. +In addition, it will also build two C and C++ compiler wrappers (bin/libafl_c(libafl_c/xx).rs) that you must use to compile the target. + +The compiler wrappers, `libafl_cc` and libafl_cxx`, will end up in `./target/release/` (or `./target/debug`, in case you did not build with the `--release` flag). + +Then download libpng, and unpack the archive: +```bash +wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz +tar -xvf libpng-1.6.37.tar.xz +``` + +Now compile libpng, using the libafl_cc compiler wrapper: + +```bash +cd libpng-1.6.37 +./configure +make CC=$(realpath ../target/release/libafl_cc) CXX=$(realpath ../target/release/libafl_cxx) -j `nproc` +``` + +You can find the static lib at `libpng-1.6.37/.libs/libpng16.a`. + +Now, we have to build the libfuzzer harness and link all together to create our fuzzer binary. + +``` +cd .. +./target/release/libafl_cxx ./harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer_libpng -lz -lm +``` + +Afterward, the fuzzer will be ready to run. +Note that, unless you use the `launcher`, you will have to run the binary multiple times to actually start the fuzz process, see `Run` in the following. +This allows you to run multiple different builds of the same fuzzer alongside, for example, with and without ASAN (`-fsanitize=address`) or with different mutators. + +## Run + +The first time you run the binary, the broker will open a tcp port (currently on port `1337`), waiting for fuzzer clients to connect. This port is local and only used for the initial handshake. All further communication happens via shared map, to be independent of the kernel. Currently you must run the clients from the libfuzzer_libpng directory for them to be able to access the PNG corpus. + +``` +./fuzzer_libpng + +[libafl/src/bolts/llmp.rs:407] "We're the broker" = "We\'re the broker" +Doing broker things. Run this tool again to start fuzzing in a client. +``` + +And after running the above again in a separate terminal: + +``` +[libafl/src/bolts/llmp.rs:1464] "New connection" = "New connection" +[libafl/src/bolts/llmp.rs:1464] addr = 127.0.0.1:33500 +[libafl/src/bolts/llmp.rs:1464] stream.peer_addr().unwrap() = 127.0.0.1:33500 +[LOG Debug]: Loaded 4 initial testcases. +[New Testcase #2] clients: 3, corpus: 6, objectives: 0, executions: 5, exec/sec: 0 +< fuzzing stats > +``` + +As this example uses in-process fuzzing, we added a Restarting Event Manager (`setup_restarting_mgr`). +This means each client will start itself again to listen for crashes and timeouts. +By restarting the actual fuzzer, it can recover from these exit conditions. + +In any real-world scenario, you should use `taskset` to pin each client to an empty CPU core, the lib does not pick an empty core automatically (yet). + diff --git a/fuzzers/libfuzzer_reachability/corpus/not_kitty.png b/fuzzers/libfuzzer_reachability/corpus/not_kitty.png new file mode 100644 index 0000000000000000000000000000000000000000..eff7c1707b936a8f8df725814f604d454b78b5c3 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X_yc@GT+_~+`TzevkY_wIZRYx+5&y#hyq+?%!C8<`)MX5lF!N|bSRM)^r*U&J;z}U*bz{;0L z1Vuw`eoAIqC5i?kD`P_|6GMoGiCWXn12ss3YzWRzD=AMbN@Z|N$xljE@XSq2PYp^< WOsOn9nQ8-6#Ng@b=d#Wzp$PyV*n0l} literal 0 HcmV?d00001 diff --git a/fuzzers/libfuzzer_reachability/corpus/not_kitty_gamma.png b/fuzzers/libfuzzer_reachability/corpus/not_kitty_gamma.png new file mode 100644 index 0000000000000000000000000000000000000000..939d9d29a9b9f95bac5e9a72854361ee85469921 GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmTQ929t;oCfmw1AIbU z)6Sgv|NlRbXFM})=KnKxKI=t+9LW;bh?3y^w370~qErUQl>DSr1<%~X^wgl##FWay zlc_d9MbVxvjv*GO?@o5)YH;9THa`3B|5>?^8?LvjJ}xLe>!7e@k)r^sLedir0mCVe z=5sMjEm$*~tHD+}{NS_$nMdb|ABqg-@UGMMsZ=uY-X%Cq@&3vmZ%&@H{P?6&+U!yq VvuXWlo?M_c44$rjF6*2UngF4cP+$N6 literal 0 HcmV?d00001 diff --git a/fuzzers/libfuzzer_reachability/corpus/not_kitty_icc.png b/fuzzers/libfuzzer_reachability/corpus/not_kitty_icc.png new file mode 100644 index 0000000000000000000000000000000000000000..f0c7804d99829cc6307c1c6ae9915cf42d555414 GIT binary patch literal 427 zcmV;c0aX5pP)9xSWu9|B*4Isn^#g47m^r~thH)GiR<@yX0fO)OF<2Kt#qCldyUF#H?{4jV?XGw9)psxE&K1B1m^ z1_tH{2(hG@3=G>_85ksPA;eS`Ffj19FfeR8pIlm01~rBeWCZ{dbvfq;rA3DT000kA zOjJc?%*_A){{R30GnreSaefwW^{L9a%BKPWN%_+AW3auXJt}l zVPtu6$z?nM003J_L_t(I%iWVf3V=Wi12fJ3|IHp$*hSlV@t||fKp?cDK@bHXV&o_g zF_hw;3ILUGteXmeJsVfSmcVJno)^MdQwU3bFHCtNG)uY>mLcD%`0UBaIq~Fq8#dBr V12uok3~c}a002ovPDHLkV1nKBo!S5Z literal 0 HcmV?d00001 diff --git a/fuzzers/libfuzzer_reachability/diff.patch b/fuzzers/libfuzzer_reachability/diff.patch new file mode 100644 index 0000000000..a60d43bf54 --- /dev/null +++ b/fuzzers/libfuzzer_reachability/diff.patch @@ -0,0 +1,12 @@ +385,393d384 +< +< +< #include +< size_t libafl_target_ctr = 0; +< void hook_func(){ +< uintptr_t k = (uintptr_t)__builtin_return_address(0); +< libafl_target_ctr += 1; +< } +< +400d390 +< hook_func(); diff --git a/fuzzers/libfuzzer_reachability/harness.cc b/fuzzers/libfuzzer_reachability/harness.cc new file mode 100644 index 0000000000..65faff685d --- /dev/null +++ b/fuzzers/libfuzzer_reachability/harness.cc @@ -0,0 +1,197 @@ +// libpng_read_fuzzer.cc +// Copyright 2017-2018 Glenn Randers-Pehrson +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that may +// be found in the LICENSE file https://cs.chromium.org/chromium/src/LICENSE + +// Last changed in libpng 1.6.35 [July 15, 2018] + +// The modifications in 2017 by Glenn Randers-Pehrson include +// 1. addition of a PNG_CLEANUP macro, +// 2. setting the option to ignore ADLER32 checksums, +// 3. adding "#include " which is needed on some platforms +// to provide memcpy(). +// 4. adding read_end_info() and creating an end_info structure. +// 5. adding calls to png_set_*() transforms commonly used by browsers. + +#include +#include +#include + +#include + +#define PNG_INTERNAL +#include "png.h" + +#define PNG_CLEANUP \ + if(png_handler.png_ptr) \ + { \ + if (png_handler.row_ptr) \ + png_free(png_handler.png_ptr, png_handler.row_ptr); \ + if (png_handler.end_info_ptr) \ + png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr,\ + &png_handler.end_info_ptr); \ + else if (png_handler.info_ptr) \ + png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr,\ + nullptr); \ + else \ + png_destroy_read_struct(&png_handler.png_ptr, nullptr, nullptr); \ + png_handler.png_ptr = nullptr; \ + png_handler.row_ptr = nullptr; \ + png_handler.info_ptr = nullptr; \ + png_handler.end_info_ptr = nullptr; \ + } + +struct BufState { + const uint8_t* data; + size_t bytes_left; +}; + +struct PngObjectHandler { + png_infop info_ptr = nullptr; + png_structp png_ptr = nullptr; + png_infop end_info_ptr = nullptr; + png_voidp row_ptr = nullptr; + BufState* buf_state = nullptr; + + ~PngObjectHandler() { + if (row_ptr) + png_free(png_ptr, row_ptr); + if (end_info_ptr) + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info_ptr); + else if (info_ptr) + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + else + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + delete buf_state; + } +}; + +void user_read_data(png_structp png_ptr, png_bytep data, size_t length) { + BufState* buf_state = static_cast(png_get_io_ptr(png_ptr)); + if (length > buf_state->bytes_left) { + png_error(png_ptr, "read error"); + } + memcpy(data, buf_state->data, length); + buf_state->bytes_left -= length; + buf_state->data += length; +} + +static const int kPngHeaderSize = 8; + +// Entry point for LibFuzzer. +// 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; + } + + 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/libfuzzer_reachability/src/bin/libafl_cc.rs b/fuzzers/libfuzzer_reachability/src/bin/libafl_cc.rs new file mode 100644 index 0000000000..dc65a9a57b --- /dev/null +++ b/fuzzers/libfuzzer_reachability/src/bin/libafl_cc.rs @@ -0,0 +1,33 @@ +use libafl_cc::{ClangWrapper, CompilerWrapper, LIB_EXT, LIB_PREFIX}; +use std::env; + +fn main() { + let args: Vec = env::args().collect(); + if args.len() > 1 { + let mut dir = env::current_exe().unwrap(); + dir.pop(); + + let mut cc = ClangWrapper::new("clang", "clang++"); + cc.from_args(&args) + .unwrap() + .add_arg("-fsanitize-coverage=trace-pc-guard".into()) + .unwrap() + .add_link_arg( + dir.join(format!("{}libfuzzer_libpng.{}", LIB_PREFIX, LIB_EXT)) + .display() + .to_string(), + ) + .unwrap(); + // Libraries needed by libafl on Windows + #[cfg(windows)] + cc.add_link_arg("-lws2_32".into()) + .unwrap() + .add_link_arg("-lBcrypt".into()) + .unwrap() + .add_link_arg("-lAdvapi32".into()) + .unwrap(); + cc.run().unwrap(); + } else { + panic!("LibAFL CC: No Arguments given"); + } +} diff --git a/fuzzers/libfuzzer_reachability/src/bin/libafl_cxx.rs b/fuzzers/libfuzzer_reachability/src/bin/libafl_cxx.rs new file mode 100644 index 0000000000..2183682d96 --- /dev/null +++ b/fuzzers/libfuzzer_reachability/src/bin/libafl_cxx.rs @@ -0,0 +1,34 @@ +use libafl_cc::{ClangWrapper, CompilerWrapper, LIB_EXT, LIB_PREFIX}; +use std::env; + +fn main() { + let args: Vec = env::args().collect(); + if args.len() > 1 { + let mut dir = env::current_exe().unwrap(); + dir.pop(); + + let mut cc = ClangWrapper::new("clang", "clang++"); + cc.is_cpp() + .from_args(&args) + .unwrap() + .add_arg("-fsanitize-coverage=trace-pc-guard".into()) + .unwrap() + .add_link_arg( + dir.join(format!("{}libfuzzer_libpng.{}", LIB_PREFIX, LIB_EXT)) + .display() + .to_string(), + ) + .unwrap(); + // Libraries needed by libafl on Windows + #[cfg(windows)] + cc.add_link_arg("-lws2_32".into()) + .unwrap() + .add_link_arg("-lBcrypt".into()) + .unwrap() + .add_link_arg("-lAdvapi32".into()) + .unwrap(); + cc.run().unwrap(); + } else { + panic!("LibAFL CC: No Arguments given"); + } +} diff --git a/fuzzers/libfuzzer_reachability/src/lib.rs b/fuzzers/libfuzzer_reachability/src/lib.rs new file mode 100644 index 0000000000..702c516c1d --- /dev/null +++ b/fuzzers/libfuzzer_reachability/src/lib.rs @@ -0,0 +1,177 @@ +//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts +//! The example harness is built for libpng. + +use core::time::Duration; +use std::{env, path::PathBuf}; + +use libafl::{ + bolts::tuples::tuple_list, + corpus::{ + Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, + QueueCorpusScheduler, + }, + events::{setup_restarting_mgr_std, EventManager}, + executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback, ReachabilityFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + mutators::scheduled::{havoc_mutations, StdScheduledMutator}, + mutators::token_mutations::Tokens, + observers::{HitcountsMapObserver, StdMapObserver, TimeObserver, ReachabilityObserver}, + stages::mutational::StdMutationalStage, + state::{HasCorpus, HasMetadata, State}, + stats::SimpleStats, + utils::{current_nanos, StdRand}, + Error, +}; + +use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM}; + +extern "C" { + static mut libafl_target_ctr: usize; +} + +/// The main fn, no_mangle as it is a C main +#[no_mangle] +pub fn main() { + // Registry the metadata types used in this fuzzer + // Needed only on no_std + //RegistryBuilder::register::(); + + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + fuzz( + vec![PathBuf::from("./corpus")], + PathBuf::from("./crashes"), + 1337, + ) + .expect("An error occurred while fuzzing"); +} + +/// The actual fuzzer +fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { + // 'While the stats are state, they are usually used in the broker - which is likely never restarted + let stats = SimpleStats::new(|s| println!("{}", s)); + + // The restarting state will spawn the same process again as child, then restarted it each time it crashes. + let (state, mut restarting_mgr) = match setup_restarting_mgr_std(stats, broker_port) { + Ok(res) => res, + Err(err) => match err { + Error::ShuttingDown => { + return Ok(()); + } + _ => { + panic!("Failed to setup the restarter: {}", err); + } + }, + }; + + // Create an observation channel using the coverage map + let edges_observer = HitcountsMapObserver::new(unsafe { + StdMapObserver::new("edges", &mut EDGES_MAP, MAX_EDGES_NUM) + }); + + let reachability_observer = + ReachabilityObserver::new("png()", unsafe { &mut libafl_target_ctr }); + + // If not restarting, create a State from scratch + let mut state = state.unwrap_or_else(|| { + State::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Feedbacks to rate the interestingness of an input + tuple_list!( + MaxMapFeedback::new_with_observer_track(&edges_observer, true, false), + ReachabilityFeedback::new(), + TimeFeedback::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(objective_dir).unwrap(), + // Feedbacks to recognize an input as solution + tuple_list!(CrashFeedback::new(), TimeoutFeedback::new()), + ) + }); + + println!("We're a client, let's fuzz :)"); + + // Create a PNG dictionary if not existing + if state.metadata().get::().is_none() { + state.add_metadata(Tokens::new(vec![ + vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header + "IHDR".as_bytes().to_vec(), + "IDAT".as_bytes().to_vec(), + "PLTE".as_bytes().to_vec(), + "IEND".as_bytes().to_vec(), + ])); + } + + // Setup a basic mutator with a mutational stage + let mutator = StdScheduledMutator::new(havoc_mutations()); + let stage = StdMutationalStage::new(mutator); + + // A fuzzer with just one stage + let mut fuzzer = StdFuzzer::new(tuple_list!(stage)); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); + + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = |buf: &[u8]| { + libfuzzer_test_one_input(buf); + ExitKind::Ok + }; + + // Create the executor for an in-process function with one observer for edge coverage and one for the execution time + let mut executor = TimeoutExecutor::new( + InProcessExecutor::new( + "in-process(edges,time)", + &mut harness, + tuple_list!(edges_observer, reachability_observer,TimeObserver::new("time")), + &mut state, + &mut restarting_mgr, + )?, + // 10 seconds timeout + Duration::new(10, 0), + ); + + // The actual target run starts here. + // Call LLVMFUzzerInitialize() if present. + let args: Vec = env::args().collect(); + if libfuzzer_initialize(&args) == -1 { + println!("Warning: LLVMFuzzerInitialize failed with -1") + } + + // In case the corpus is empty (on first run), reset + if state.corpus().count() < 1 { + state + .load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs) + .expect(&format!( + "Failed to load initial corpus at {:?}", + &corpus_dirs + )); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + // This fuzzer restarts after 1 mio `fuzz_one` executions. + // Each fuzz_one will internally do many executions of the target. + // If your target is very instable, setting a low count here may help. + // However, you will lose a lot of performance that way. + let iters = 1_000_000; + fuzzer.fuzz_loop_for( + &mut state, + &mut executor, + &mut restarting_mgr, + &scheduler, + iters, + )?; + + // It's important, that we store the state before restarting! + // Else, the parent will not respawn a new child and quit. + restarting_mgr.on_restart(&mut state)?; + + Ok(()) +} diff --git a/fuzzers/libfuzzer_reachability/test.sh b/fuzzers/libfuzzer_reachability/test.sh new file mode 100755 index 0000000000..7d3873dc68 --- /dev/null +++ b/fuzzers/libfuzzer_reachability/test.sh @@ -0,0 +1,10 @@ +cargo build --release +./target/release/libafl_cxx ./harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer_libpng -lz -lm + +taskset -c 0 ./fuzzer_libpng & +sleep 1 +taskset -c 1 ./fuzzer_libpng 2>/dev/null + + +killall ./fuzzer_libpng +rm -rf ./fuzzer_libpng From ce9ad8c59e772c14906cf5169866802fbf102281 Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Sat, 1 May 2021 13:05:07 +0200 Subject: [PATCH 03/12] fmt --- libafl/src/feedbacks/mod.rs | 26 +++++++++++++------------- libafl/src/observers/mod.rs | 19 +++++++++++-------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index cef7110074..f0746c6a39 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -11,7 +11,7 @@ use crate::{ corpus::Testcase, executors::ExitKind, inputs::Input, - observers::{ObserversTuple, TimeObserver, ReachabilityObserver}, + observers::{ObserversTuple, ReachabilityObserver, TimeObserver}, Error, }; @@ -254,12 +254,11 @@ impl Default for TimeFeedback { } #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ReachabilityFeedback { -} +pub struct ReachabilityFeedback {} -impl ReachabilityFeedback{ - pub fn new() -> Self{ - Self{} +impl ReachabilityFeedback { + pub fn new() -> Self { + Self {} } } @@ -273,19 +272,20 @@ where observers: &OT, _exit_kind: &ExitKind, ) -> Result { - let observer = observers.match_first_type::().unwrap(); - if observer.has_reached(){ + let observer = observers + .match_first_type::() + .unwrap(); + if observer.has_reached() { Ok(1) - } - else{ + } else { Ok(0) } } } -impl Named for ReachabilityFeedback{ +impl Named for ReachabilityFeedback { #[inline] - fn name(&self) -> &str{ + fn name(&self) -> &str { "ReachabilityFeedback" } -} \ No newline at end of file +} diff --git a/libafl/src/observers/mod.rs b/libafl/src/observers/mod.rs index 320c745299..13bd49c1e0 100644 --- a/libafl/src/observers/mod.rs +++ b/libafl/src/observers/mod.rs @@ -11,7 +11,10 @@ use core::time::Duration; use serde::{Deserialize, Serialize}; use crate::{ - bolts::{tuples::{MatchFirstType, MatchNameAndType, MatchType, Named}, ownedref::OwnedPtrMut}, + bolts::{ + ownedref::OwnedPtrMut, + tuples::{MatchFirstType, MatchNameAndType, MatchType, Named}, + }, utils::current_time, Error, }; @@ -133,14 +136,14 @@ impl Named for TimeObserver { } #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ReachabilityObserver{ +pub struct ReachabilityObserver { name: String, counter: OwnedPtrMut, } -impl ReachabilityObserver{ +impl ReachabilityObserver { pub fn new(name: &'static str, ctr: &'static mut usize) -> Self { - Self{ + Self { name: name.to_string(), counter: OwnedPtrMut::Ptr(ctr), } @@ -151,15 +154,15 @@ impl ReachabilityObserver{ } } -impl Observer for ReachabilityObserver{ - fn pre_exec(&mut self) -> Result<(), Error>{ +impl Observer for ReachabilityObserver { + fn pre_exec(&mut self) -> Result<(), Error> { *self.counter.as_mut() = 0; Ok(()) } } -impl Named for ReachabilityObserver{ - fn name(&self) -> &str{ +impl Named for ReachabilityObserver { + fn name(&self) -> &str { &self.name } } From ef34ce8f4c89aad3ec38297f6291133a77671a94 Mon Sep 17 00:00:00 2001 From: tokatoka Date: Mon, 3 May 2021 15:08:49 +0900 Subject: [PATCH 04/12] remove reachabilityobserver, use stdmapobserver instead --- fuzzers/libfuzzer_reachability/src/lib.rs | 19 ++++-- libafl/src/feedbacks/map.rs | 79 +++++++++++++++++++++++ libafl/src/feedbacks/mod.rs | 39 +---------- libafl/src/observers/mod.rs | 37 +---------- 4 files changed, 93 insertions(+), 81 deletions(-) diff --git a/fuzzers/libfuzzer_reachability/src/lib.rs b/fuzzers/libfuzzer_reachability/src/lib.rs index 702c516c1d..d4aaa2d6a2 100644 --- a/fuzzers/libfuzzer_reachability/src/lib.rs +++ b/fuzzers/libfuzzer_reachability/src/lib.rs @@ -16,7 +16,7 @@ use libafl::{ fuzzer::{Fuzzer, StdFuzzer}, mutators::scheduled::{havoc_mutations, StdScheduledMutator}, mutators::token_mutations::Tokens, - observers::{HitcountsMapObserver, StdMapObserver, TimeObserver, ReachabilityObserver}, + observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, stages::mutational::StdMutationalStage, state::{HasCorpus, HasMetadata, State}, stats::SimpleStats, @@ -26,8 +26,12 @@ use libafl::{ use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM}; +#[cfg(unix)] +const TARGET_SIZE: usize = 4; + +#[cfg(unix)] extern "C" { - static mut libafl_target_ctr: usize; + static __libafl_target_list: *mut usize; } /// The main fn, no_mangle as it is a C main @@ -72,8 +76,10 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> StdMapObserver::new("edges", &mut EDGES_MAP, MAX_EDGES_NUM) }); - let reachability_observer = - ReachabilityObserver::new("png()", unsafe { &mut libafl_target_ctr }); + let reachability_observer = + unsafe{ + StdMapObserver::new_from_ptr("png.c", __libafl_target_list, TARGET_SIZE) + }; // If not restarting, create a State from scratch let mut state = state.unwrap_or_else(|| { @@ -85,14 +91,13 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // Feedbacks to rate the interestingness of an input tuple_list!( MaxMapFeedback::new_with_observer_track(&edges_observer, true, false), - ReachabilityFeedback::new(), TimeFeedback::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(objective_dir).unwrap(), // Feedbacks to recognize an input as solution - tuple_list!(CrashFeedback::new(), TimeoutFeedback::new()), + tuple_list!(CrashFeedback::new(), TimeoutFeedback::new(), ReachabilityFeedback::new_with_observer(&reachability_observer)), ) }); @@ -130,7 +135,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> InProcessExecutor::new( "in-process(edges,time)", &mut harness, - tuple_list!(edges_observer, reachability_observer,TimeObserver::new("time")), + tuple_list!(edges_observer, reachability_observer, TimeObserver::new("time")), &mut state, &mut restarting_mgr, )?, diff --git a/libafl/src/feedbacks/map.rs b/libafl/src/feedbacks/map.rs index 89374e154d..39f4f479b6 100644 --- a/libafl/src/feedbacks/map.rs +++ b/libafl/src/feedbacks/map.rs @@ -333,3 +333,82 @@ where } } } + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ReachabilityFeedback { + name: String, + target_idx: Vec, + phantom: PhantomData, +} + +impl ReachabilityFeedback +where + O: MapObserver, +{ + pub fn new_with_observer(map_observer: &O) -> Self { + Self { + name: map_observer.name().to_string(), + target_idx: vec![], + phantom: PhantomData, + } + } + pub fn new(name: &'static str) -> Self { + Self { + name: name.to_string(), + target_idx: vec![], + phantom: PhantomData, + } + } +} + +impl Feedback for ReachabilityFeedback +where + I: Input, + O: MapObserver, +{ + fn is_interesting( + &mut self, + _input: &I, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result { + let observer = observers.match_name_type::(&self.name).unwrap(); + let size = observer.usable_count(); + let mut hit_target: bool = false; + //check if we've hit any targets. + for i in 0..size { + if observer.map()[i] > 0 { + self.target_idx.push(i); + hit_target = true; + } + } + if hit_target { + Ok(1) + } else { + Ok(0) + } + } + + fn append_metadata(&mut self, testcase: &mut Testcase) -> Result<(), Error> { + if self.target_idx.len() > 0 { + let meta = MapIndexesMetadata::new(core::mem::take(self.target_idx.as_mut())); + testcase.add_metadata(meta); + }; + Ok(()) + } + + fn discard_metadata(&mut self, _input: &I) -> Result<(), Error> { + self.target_idx.clear(); + Ok(()) + } +} + +impl Named for ReachabilityFeedback +where + O: MapObserver, +{ + #[inline] + fn name(&self) -> &str { + self.name.as_str() + } +} diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index f0746c6a39..6cb8261383 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -11,7 +11,7 @@ use crate::{ corpus::Testcase, executors::ExitKind, inputs::Input, - observers::{ObserversTuple, ReachabilityObserver, TimeObserver}, + observers::{ObserversTuple, TimeObserver}, Error, }; @@ -252,40 +252,3 @@ impl Default for TimeFeedback { Self::new() } } - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ReachabilityFeedback {} - -impl ReachabilityFeedback { - pub fn new() -> Self { - Self {} - } -} - -impl Feedback for ReachabilityFeedback -where - I: Input, -{ - fn is_interesting( - &mut self, - _input: &I, - observers: &OT, - _exit_kind: &ExitKind, - ) -> Result { - let observer = observers - .match_first_type::() - .unwrap(); - if observer.has_reached() { - Ok(1) - } else { - Ok(0) - } - } -} - -impl Named for ReachabilityFeedback { - #[inline] - fn name(&self) -> &str { - "ReachabilityFeedback" - } -} diff --git a/libafl/src/observers/mod.rs b/libafl/src/observers/mod.rs index 13bd49c1e0..2ff66ca5fc 100644 --- a/libafl/src/observers/mod.rs +++ b/libafl/src/observers/mod.rs @@ -11,10 +11,7 @@ use core::time::Duration; use serde::{Deserialize, Serialize}; use crate::{ - bolts::{ - ownedref::OwnedPtrMut, - tuples::{MatchFirstType, MatchNameAndType, MatchType, Named}, - }, + bolts::tuples::{MatchFirstType, MatchNameAndType, MatchType, Named}, utils::current_time, Error, }; @@ -135,38 +132,6 @@ impl Named for TimeObserver { } } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ReachabilityObserver { - name: String, - counter: OwnedPtrMut, -} - -impl ReachabilityObserver { - pub fn new(name: &'static str, ctr: &'static mut usize) -> Self { - Self { - name: name.to_string(), - counter: OwnedPtrMut::Ptr(ctr), - } - } - - pub fn has_reached(&self) -> bool { - *self.counter.as_ref() > 0 - } -} - -impl Observer for ReachabilityObserver { - fn pre_exec(&mut self) -> Result<(), Error> { - *self.counter.as_mut() = 0; - Ok(()) - } -} - -impl Named for ReachabilityObserver { - fn name(&self) -> &str { - &self.name - } -} - #[cfg(feature = "std")] #[cfg(test)] mod tests { From 27570ecc2d3e738ba11114904a3d8e1973ddd0c9 Mon Sep 17 00:00:00 2001 From: tokatoka Date: Mon, 3 May 2021 15:10:04 +0900 Subject: [PATCH 05/12] update diff.patch --- fuzzers/libfuzzer_reachability/diff.patch | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/fuzzers/libfuzzer_reachability/diff.patch b/fuzzers/libfuzzer_reachability/diff.patch index a60d43bf54..8c0ca68b83 100644 --- a/fuzzers/libfuzzer_reachability/diff.patch +++ b/fuzzers/libfuzzer_reachability/diff.patch @@ -1,12 +1,9 @@ -385,393d384 -< -< -< #include -< size_t libafl_target_ctr = 0; -< void hook_func(){ -< uintptr_t k = (uintptr_t)__builtin_return_address(0); -< libafl_target_ctr += 1; -< } -< -400d390 -< hook_func(); +15a16,19 +> #define TARGET_SIZE 4 +> +> size_t __lafl_dummy_list[TARGET_SIZE] = {0}; +> size_t *__libafl_target_list = __lafl_dummy_list; +2562a2567 +> __lafl_dummy_list[0] = 1; +2584a2590 +> __lafl_dummy_list[1] = 1; From e2ee7f27163755df3b3608e702f8cbc310a91ff4 Mon Sep 17 00:00:00 2001 From: tokatoka Date: Mon, 3 May 2021 15:17:50 +0900 Subject: [PATCH 06/12] update README --- fuzzers/libfuzzer_reachability/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzzers/libfuzzer_reachability/README.md b/fuzzers/libfuzzer_reachability/README.md index 922667e8fe..87ad627715 100644 --- a/fuzzers/libfuzzer_reachability/README.md +++ b/fuzzers/libfuzzer_reachability/README.md @@ -27,7 +27,7 @@ Then download libpng, and unpack the archive: wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz tar -xvf libpng-1.6.37.tar.xz ``` - +Run `patch libpng-1.6.37/png.c diff.patch` before compiling the libpng Now compile libpng, using the libafl_cc compiler wrapper: ```bash From 490ccd7a66f546359a38a7d94c41f7c000c2ea6c Mon Sep 17 00:00:00 2001 From: tokatoka Date: Mon, 3 May 2021 15:28:46 +0900 Subject: [PATCH 07/12] fix the clippy warning --- libafl/src/feedbacks/map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libafl/src/feedbacks/map.rs b/libafl/src/feedbacks/map.rs index 39f4f479b6..d17323b0d7 100644 --- a/libafl/src/feedbacks/map.rs +++ b/libafl/src/feedbacks/map.rs @@ -390,7 +390,7 @@ where } fn append_metadata(&mut self, testcase: &mut Testcase) -> Result<(), Error> { - if self.target_idx.len() > 0 { + if !self.target_idx.is_empty() { let meta = MapIndexesMetadata::new(core::mem::take(self.target_idx.as_mut())); testcase.add_metadata(meta); }; From 2b85d9de77261b5dd390cbd8c356b5a4b57871ae Mon Sep 17 00:00:00 2001 From: tokatoka Date: Wed, 5 May 2021 05:16:57 +0900 Subject: [PATCH 08/12] Squashed commit of the following: commit f20524ebd77011481e86b420c925e8504bd11308 Author: Andrea Fioraldi Date: Tue May 4 16:00:39 2021 +0200 Composing feedback (#85) * composing feedbacks as logic operations and bump to 0.2 * adapt fuzzers and libafl_frida * fix windows build commit e06efaa03bc96ef71740d7376c7381572bf11c6c Author: Andrea Fioraldi Date: Tue May 4 13:54:46 2021 +0200 Observers refactor (#84) * new observer structure with HasExecHooks * adapt libafl_frida to new observers * docstrings commit 17c6fcd31cb746c099654be2b7a168bd04d46381 Merge: 08a2d43 a78a4b7 Author: Andrea Fioraldi Date: Mon May 3 11:16:49 2021 +0200 Merge branch 'main' into dev commit 08a2d43790797d8864565fec99e7043289a46283 Author: David CARLIER Date: Mon May 3 10:15:28 2021 +0100 Build warning fix proposal, mostly about reference to packed fields. (#79) commit 88fe8fa532ac34cbc10782f5f71264f620385dda Merge: d5d46ad d2e7719 Author: Andrea Fioraldi Date: Mon May 3 11:05:42 2021 +0200 Merge pull request #80 from marcograss/book-typos fixed some minor typos in the book commit a78a4b73fa798c1ed7a3d053369cca435e57aa07 Author: s1341 Date: Mon May 3 10:34:15 2021 +0300 frida-asan: Un-inline report funclet to reduce code bloat (#81) * frida-asan: Outline report funclet to reduce code bloat * fmt commit d2e7719a8bea3a993394c187e2183d3e91f02c75 Author: Marco Grassi Date: Sun May 2 21:58:33 2021 +0800 fixed some minor typos in the book commit d5d46ad7e440fd4a2925352ed1ccb9ced5d9463d Author: Dominik Maier Date: Sat May 1 23:09:10 2021 +0200 make clippy less pedantic commit 52d25e979e23589587c885803641058dc36aa998 Author: Dominik Maier Date: Sat May 1 22:23:59 2021 +0200 fixing clippy::match-same-arms commit cd66f880dea830d1e38e89fd1bf3c20fd89c9d70 Author: Dominik Maier Date: Sat May 1 14:02:07 2021 +0200 fixed clippy run in workflow commit ddcf086acde2b703c36e4ec3976588313fc3d591 Author: Dominik Maier Date: Sat May 1 13:53:29 2021 +0200 Update README.md commit c715f1fe6e42942e53bd13ea6a23214620f6c829 Author: Dominik Maier Date: Sat May 1 13:48:38 2021 +0200 using clippy.sh commit 9374b26b1d2d44c6042fdd653a8d960ce698592c Author: Dominik Maier Date: Sat May 1 13:47:44 2021 +0200 some clippy warning ignored commit b9e75c0c98fdfb1e70778e6f3612a94b71dcd21a Author: Dominik Maier Date: Sat May 1 13:24:02 2021 +0200 Tcp Broker to Broker Communication (#66) * initial b2b implementation * no_std and clippy fixes * b2b testcase added * more correct testcases * fixed b2b * typo * fixed unused warning --- .github/workflows/build_and_test.yml | 5 +- README.md | 8 + clippy.sh | 1 + docs/src/design/core_concepts.md | 2 +- docs/src/getting_started/build.md | 2 +- docs/src/getting_started/crates.md | 2 +- docs/src/introduction.md | 2 +- fuzzers/baby_fuzzer/Cargo.toml | 2 +- fuzzers/baby_fuzzer/src/main.rs | 7 +- fuzzers/frida_libpng/Cargo.toml | 13 +- fuzzers/frida_libpng/harness.cc | 3 +- fuzzers/frida_libpng/src/fuzzer.rs | 87 ++-- fuzzers/libfuzzer_libmozjpeg/Cargo.toml | 2 +- fuzzers/libfuzzer_libmozjpeg/src/lib.rs | 6 +- fuzzers/libfuzzer_libpng/Cargo.toml | 2 +- fuzzers/libfuzzer_libpng/src/lib.rs | 6 +- fuzzers/libfuzzer_stb_image/Cargo.toml | 2 +- fuzzers/libfuzzer_stb_image/src/main.rs | 8 +- libafl/Cargo.toml | 6 +- libafl/examples/llmp_test/main.rs | 23 +- libafl/src/bolts/compress.rs | 1 + libafl/src/bolts/llmp.rs | 652 ++++++++++++++++++++---- libafl/src/bolts/os/unix_signals.rs | 2 +- libafl/src/bolts/serdeany.rs | 2 +- libafl/src/bolts/shmem.rs | 15 +- libafl/src/corpus/testcase.rs | 36 -- libafl/src/events/llmp.rs | 21 +- libafl/src/executors/inprocess.rs | 155 +++--- libafl/src/executors/mod.rs | 129 +++-- libafl/src/executors/timeout.rs | 96 ++-- libafl/src/feedbacks/map.rs | 61 +-- libafl/src/feedbacks/mod.rs | 275 ++++++++-- libafl/src/lib.rs | 1 - libafl/src/observers/map.rs | 40 +- libafl/src/observers/mod.rs | 68 +-- libafl/src/stages/mutational.rs | 24 +- libafl/src/state/mod.rs | 280 +++++----- libafl_frida/Cargo.toml | 10 +- libafl_frida/src/asan_rt.rs | 523 ++++++------------- libafl_frida/src/helper.rs | 34 +- 40 files changed, 1507 insertions(+), 1107 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 3625f6c283..f675a790db 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -59,10 +59,7 @@ jobs: - name: Test Docs run: cargo test --doc - name: Run clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --all + run: ./clippy.sh windows: runs-on: windows-latest steps: diff --git a/README.md b/README.md index cc16b5da95..c9b452463a 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,14 @@ Check the [TODO.md](./TODO.md) file for features that we plan to support. For bugs, feel free to open issues or contact us directly. Thank you for your support. <3 +Even though we will gladly assist you in finishing up your PR, try to +- use *stable* rust +- run `cargo fmt` on your code before pushing +- check the output of `cargo clippy --all` or `./clippy.sh` +- run `cargo build --no-default-features` to check for `no_std` compatibility (and possibly add `#[cfg(feature = "std")]`) to hide parts of your code. + +Some of the parts in this list may be hard, don't be afraid to open a PR if you cannot fix them by yourself, so we can help. + #### License diff --git a/clippy.sh b/clippy.sh index 7f4e6ac479..c0583fd117 100755 --- a/clippy.sh +++ b/clippy.sh @@ -9,6 +9,7 @@ RUST_BACKTRACE=full cargo clippy --all -- \ -W clippy::cast_possible_wrap \ -W clippy::unused_self \ -W clippy::too_many_lines \ + -W clippy::option_if_let_else \ -A missing-docs \ -A clippy::doc_markdown \ -A clippy::must-use-candidate \ diff --git a/docs/src/design/core_concepts.md b/docs/src/design/core_concepts.md index ea87125e2e..f70dd78f6c 100644 --- a/docs/src/design/core_concepts.md +++ b/docs/src/design/core_concepts.md @@ -80,7 +80,7 @@ Generators are traditionally less used in Feedback-driven Fuzzing, but there are A Stage is an entity that operates on a single Input got from the Corpus. -For instamce, a Mutational Stage, given an input of the corpus, applies a Mutator and executes the generated input one or more time. How many times this has to be done can be scheduled, AFL for instance use a performance score of the input to choose how many times the havoc mutator should be invoked. This can depends also on other parameters, for instance, the length of the input if we want to just apply a sequential bitflip, or be a fixed value. +For instance, a Mutational Stage, given an input of the corpus, applies a Mutator and executes the generated input one or more time. How many times this has to be done can be scheduled, AFL for instance use a performance score of the input to choose how many times the havoc mutator should be invoked. This can depends also on other parameters, for instance, the length of the input if we want to just apply a sequential bitflip, or be a fixed value. A stage can be also an analysis stage, for instance, the Colorization stage of Redqueen that aims to introduce more entropy in a testcase or the Trimming stage of AFL that aims to reduce the size of a testcase. diff --git a/docs/src/getting_started/build.md b/docs/src/getting_started/build.md index b4649a6999..7a38b1b005 100644 --- a/docs/src/getting_started/build.md +++ b/docs/src/getting_started/build.md @@ -20,6 +20,6 @@ The directory contains a set of crates that are not part of the workspace. Each of these example fuzzers uses particular features of LibAFL, sometimes combined with different instrumentation backends (e.g. [SanitizerCoverage](https://clang.llvm.org/docs/SanitizerCoverage.html), [Frida](https://frida.re/), ...). -You can use these crates as examples and as skeletons for custom fuzzers with similar featuresets. +You can use these crates as examples and as skeletons for custom fuzzers with similar feature sets. To build an example fuzzer you have to invoke cargo from its respective folder (`fuzzers/[FUZZER_NAME]). diff --git a/docs/src/getting_started/crates.md b/docs/src/getting_started/crates.md index 841094e076..8a29e7b7e7 100644 --- a/docs/src/getting_started/crates.md +++ b/docs/src/getting_started/crates.md @@ -11,7 +11,7 @@ This is the main crate that contains all the components needed to build a fuzzer This crate has the following feature flags: -- std, that enables the parts of the code that use the Rust standard library. Without this flags, libafl is no_std. +- std, that enables the parts of the code that use the Rust standard library. Without this flag, libafl is no_std. - derive, that enables the usage of the `derive(...)` macros defined in libafl_derive from libafl. By default, std and derive are both set. diff --git a/docs/src/introduction.md b/docs/src/introduction.md index c35050b44b..f4acea8ad2 100644 --- a/docs/src/introduction.md +++ b/docs/src/introduction.md @@ -25,5 +25,5 @@ To give an example, as opposed to old-skool fuzzers, a `BytesInput` is just one feel free to use and mutate an Abstract Syntax Tree instead, for structured fuzzing. - `scalable`: As part of LibAFL, we developed `Low Level Message Passing`, `LLMP` for short, which allows LibAFL to scale almost linearly over cores. That is, if you chose to use this feature - it is your fuzzer, after all. Scaling to multiple machines over TCP is on the near road-map. - `fast`: We do everything we can at compiletime so that the runtime overhead is as minimal as it can get. -- `bring your own target`: We support binary-only modes, like Frida-Mode with ASAN and CmpLog, as well as multiple compilation passes for sourced-based instrumentation, and of course supoprt custom instrumentation. +- `bring your own target`: We support binary-only modes, like Frida-Mode with ASAN and CmpLog, as well as multiple compilation passes for sourced-based instrumentation, and of course support custom instrumentation. - `usable`: This one is on you to decide. Dig right in! \ No newline at end of file diff --git a/fuzzers/baby_fuzzer/Cargo.toml b/fuzzers/baby_fuzzer/Cargo.toml index b464bafe45..cddd5b7b6e 100644 --- a/fuzzers/baby_fuzzer/Cargo.toml +++ b/fuzzers/baby_fuzzer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "baby_fuzzer" -version = "0.1.0" +version = "0.2.0" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" diff --git a/fuzzers/baby_fuzzer/src/main.rs b/fuzzers/baby_fuzzer/src/main.rs index d53dfec70a..e5f026fc42 100644 --- a/fuzzers/baby_fuzzer/src/main.rs +++ b/fuzzers/baby_fuzzer/src/main.rs @@ -56,13 +56,13 @@ pub fn main() { StdRand::with_seed(current_nanos()), // Corpus that will be evolved, we keep it in memory for performance InMemoryCorpus::new(), - // Feedbacks to rate the interestingness of an input - tuple_list!(MaxMapFeedback::new_with_observer(&observer)), + // Feedback to rate the interestingness of an input + MaxMapFeedback::new_with_observer(&observer), // 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(), // Feedbacks to recognize an input as solution - tuple_list!(CrashFeedback::new()), + CrashFeedback::new(), ); // Setup a basic mutator with a mutational stage @@ -77,7 +77,6 @@ pub fn main() { // Create the executor for an in-process function with just one observer let mut executor = InProcessExecutor::new( - "in-process(signals)", &mut harness, tuple_list!(observer), &mut state, diff --git a/fuzzers/frida_libpng/Cargo.toml b/fuzzers/frida_libpng/Cargo.toml index c59b89c88c..572a42b0bb 100644 --- a/fuzzers/frida_libpng/Cargo.toml +++ b/fuzzers/frida_libpng/Cargo.toml @@ -1,14 +1,13 @@ [package] name = "frida_libpng" -version = "0.1.0" +version = "0.2.0" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" build = "build.rs" [features] -default = ["std", "frida"] +default = ["std"] std = [] -frida = ["frida-gum", "frida-gum-sys"] [profile.release] lto = true @@ -22,11 +21,11 @@ num_cpus = "1.0" which = "4.1" [target.'cfg(unix)'.dependencies] -libafl = { path = "../../libafl/", features = [ "std" ] } #, "llmp_small_maps", "llmp_debug"]} +libafl = { path = "../../libafl/", features = [ "std", "llmp_compression" ] } #, "llmp_small_maps", "llmp_debug"]} capstone = "0.8.0" -frida-gum = { version = "0.4", optional = true, features = [ "auto-download", "event-sink", "invocation-listener"] } -frida-gum-sys = { version = "0.2.4", optional = true, features = [ "auto-download", "event-sink", "invocation-listener"] } -libafl_frida = { path = "../../libafl_frida", version = "0.1.0" } +frida-gum = { version = "0.4", git = "https://github.com/s1341/frida-rust", features = [ "auto-download", "event-sink", "invocation-listener"] } +#frida-gum = { version = "0.4", path = "../../../frida-rust/frida-gum", features = [ "auto-download", "event-sink", "invocation-listener"] } +libafl_frida = { path = "../../libafl_frida", version = "0.2.0" } 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 b5e3e6eda8..625ab3b18f 100644 --- a/fuzzers/frida_libpng/harness.cc +++ b/fuzzers/frida_libpng/harness.cc @@ -89,7 +89,8 @@ __attribute__((noinline)) void func3( char * alloc) { printf("func3\n"); if (random() == 0) { - alloc[0xff] = 0xde; + alloc[0x1ff] = 0xde; + printf("alloc[0x200]: %d\n", alloc[0x200]); } } __attribute__((noinline)) diff --git a/fuzzers/frida_libpng/src/fuzzer.rs b/fuzzers/frida_libpng/src/fuzzer.rs index d7b0c8cde4..caeb3e279f 100644 --- a/fuzzers/frida_libpng/src/fuzzer.rs +++ b/fuzzers/frida_libpng/src/fuzzer.rs @@ -2,15 +2,17 @@ //! The example harness is built for libpng. use libafl::{ - bolts::tuples::{tuple_list, Named}, + bolts::tuples::tuple_list, corpus::{ ondisk::OnDiskMetadataFormat, Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler, }, - events::{setup_restarting_mgr_std, EventManager}, + events::setup_restarting_mgr_std, executors::{ - inprocess::InProcessExecutor, timeout::TimeoutExecutor, Executor, ExitKind, HasObservers, + inprocess::InProcessExecutor, timeout::TimeoutExecutor, Executor, ExitKind, HasExecHooks, + HasExecHooksTuple, HasObservers, HasObserversHooks, }, + feedback_or, feedbacks::{CrashFeedback, MaxMapFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, StdFuzzer}, inputs::{HasTargetBytes, Input}, @@ -37,14 +39,14 @@ use libafl_frida::{ FridaOptions, }; -struct FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT> +struct FridaInProcessExecutor<'a, 'b, 'c, EM, FH, H, I, OT, S> where FH: FridaHelper<'b>, H: FnMut(&[u8]) -> ExitKind, I: Input + HasTargetBytes, OT: ObserversTuple, { - base: TimeoutExecutor, I, OT>, + base: TimeoutExecutor, I>, /// Frida's dynamic rewriting engine stalker: Stalker<'a>, /// User provided callback for instrumentation @@ -53,19 +55,17 @@ where _phantom: PhantomData<&'b u8>, } -impl<'a, 'b, 'c, FH, H, I, OT> Executor for FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT> +impl<'a, 'b, 'c, EM, FH, H, I, OT, S> Executor + for FridaInProcessExecutor<'a, 'b, 'c, EM, FH, H, I, OT, S> where FH: FridaHelper<'b>, H: FnMut(&[u8]) -> ExitKind, I: Input + HasTargetBytes, OT: ObserversTuple, { - /// Called right before exexution starts + /// Instruct the target about the input and run #[inline] - fn pre_exec(&mut self, state: &mut S, event_mgr: &mut EM, input: &I) -> Result<(), Error> - where - EM: EventManager, - { + fn run_target(&mut self, input: &I) -> Result { if self.helper.stalker_enabled() { if !self.followed { self.followed = true; @@ -77,15 +77,6 @@ where )) } } - - self.helper.pre_exec(input); - - self.base.pre_exec(state, event_mgr, input) - } - - /// Instruct the target about the input and run - #[inline] - fn run_target(&mut self, input: &I) -> Result { let res = self.base.run_target(input); if unsafe { ASAN_ERRORS.is_some() && !ASAN_ERRORS.as_ref().unwrap().is_empty() } { println!("Crashing target as it had ASAN errors"); @@ -93,29 +84,38 @@ where libc::raise(libc::SIGABRT); } } + if self.helper.stalker_enabled() { + self.stalker.deactivate(); + } res } +} + +impl<'a, 'b, 'c, EM, FH, H, I, OT, S> HasExecHooks + for FridaInProcessExecutor<'a, 'b, 'c, EM, FH, H, I, OT, S> +where + FH: FridaHelper<'b>, + H: FnMut(&[u8]) -> ExitKind, + I: Input + HasTargetBytes, + OT: ObserversTuple, +{ + /// Called right before exexution starts + #[inline] + fn pre_exec(&mut self, state: &mut S, event_mgr: &mut EM, input: &I) -> Result<(), Error> { + self.helper.pre_exec(input); + self.base.pre_exec(state, event_mgr, input) + } /// Called right after execution finished. #[inline] - fn post_exec( - &mut self, - state: &mut S, - event_mgr: &mut EM, - input: &I, - ) -> Result<(), Error> - where - EM: EventManager, - { - if self.helper.stalker_enabled() { - self.stalker.deactivate(); - } + fn post_exec(&mut self, state: &mut S, event_mgr: &mut EM, input: &I) -> Result<(), Error> { self.helper.post_exec(input); self.base.post_exec(state, event_mgr, input) } } -impl<'a, 'b, 'c, FH, H, I, OT> HasObservers for FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT> +impl<'a, 'b, 'c, EM, FH, H, I, OT, S> HasObservers + for FridaInProcessExecutor<'a, 'b, 'c, EM, FH, H, I, OT, S> where FH: FridaHelper<'b>, H: FnMut(&[u8]) -> ExitKind, @@ -133,19 +133,17 @@ where } } -impl<'a, 'b, 'c, FH, H, I, OT> Named for FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT> +impl<'a, 'b, 'c, EM, FH, H, I, OT, S> HasObserversHooks + for FridaInProcessExecutor<'a, 'b, 'c, EM, FH, H, I, OT, S> where FH: FridaHelper<'b>, H: FnMut(&[u8]) -> ExitKind, I: Input + HasTargetBytes, - OT: ObserversTuple, + OT: ObserversTuple + HasExecHooksTuple, { - fn name(&self) -> &str { - self.base.name() - } } -impl<'a, 'b, 'c, FH, H, I, OT> FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT> +impl<'a, 'b, 'c, EM, FH, H, I, OT, S> FridaInProcessExecutor<'a, 'b, 'c, EM, FH, H, I, OT, S> where FH: FridaHelper<'b>, H: FnMut(&[u8]) -> ExitKind, @@ -154,7 +152,7 @@ where { pub fn new( gum: &'a Gum, - base: InProcessExecutor<'a, H, I, OT>, + base: InProcessExecutor<'a, EM, H, I, OT, S>, helper: &'c mut FH, timeout: Duration, ) -> Self { @@ -279,17 +277,13 @@ unsafe fn fuzz( // Corpus that will be evolved, we keep it in memory for performance InMemoryCorpus::new(), // Feedbacks to rate the interestingness of an input - tuple_list!(MaxMapFeedback::new_with_observer_track( - &edges_observer, - true, - false - )), + MaxMapFeedback::new_with_observer_track(&edges_observer, true, false), // Corpus in which we store solutions (crashes in this example), // on disk so the user can get them after stopping the fuzzer OnDiskCorpus::new_save_meta(objective_dir, Some(OnDiskMetadataFormat::JsonPretty)) .unwrap(), // Feedbacks to recognize an input as solution - tuple_list!( + feedback_or!( CrashFeedback::new(), TimeoutFeedback::new(), AsanErrorsFeedback::new() @@ -324,7 +318,6 @@ unsafe fn fuzz( let mut executor = FridaInProcessExecutor::new( &gum, InProcessExecutor::new( - "in-process(edges)", &mut frida_harness, tuple_list!(edges_observer, AsanErrorsObserver::new(&ASAN_ERRORS)), &mut state, diff --git a/fuzzers/libfuzzer_libmozjpeg/Cargo.toml b/fuzzers/libfuzzer_libmozjpeg/Cargo.toml index 76658ebb41..93d64aa431 100644 --- a/fuzzers/libfuzzer_libmozjpeg/Cargo.toml +++ b/fuzzers/libfuzzer_libmozjpeg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfuzzer_libmozjpeg" -version = "0.1.0" +version = "0.2.0" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" diff --git a/fuzzers/libfuzzer_libmozjpeg/src/lib.rs b/fuzzers/libfuzzer_libmozjpeg/src/lib.rs index c3e99c2dac..dd94e71205 100644 --- a/fuzzers/libfuzzer_libmozjpeg/src/lib.rs +++ b/fuzzers/libfuzzer_libmozjpeg/src/lib.rs @@ -8,6 +8,7 @@ use libafl::{ corpus::{Corpus, InMemoryCorpus, OnDiskCorpus, RandCorpusScheduler}, events::setup_restarting_mgr_std, executors::{inprocess::InProcessExecutor, ExitKind}, + feedback_or, feedbacks::{CrashFeedback, MaxMapFeedback}, fuzzer::{Fuzzer, StdFuzzer}, mutators::scheduled::{havoc_mutations, StdScheduledMutator}, @@ -76,7 +77,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // Corpus that will be evolved, we keep it in memory for performance InMemoryCorpus::new(), // Feedbacks to rate the interestingness of an input - tuple_list!( + feedback_or!( MaxMapFeedback::new_with_observer(&edges_observer), MaxMapFeedback::new_with_observer(&cmps_observer), MaxMapFeedback::new_with_observer(&allocs_observer) @@ -85,7 +86,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // on disk so the user can get them after stopping the fuzzer OnDiskCorpus::new(objective_dir).unwrap(), // Feedbacks to recognize an input as solution - tuple_list!(CrashFeedback::new()), + CrashFeedback::new(), ) }); @@ -113,7 +114,6 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // Create the executor for an in-process function with observers for edge coverage, value-profile and allocations sizes let mut executor = InProcessExecutor::new( - "in-process(edges,cmp,alloc)", &mut harness, tuple_list!(edges_observer, cmps_observer, allocs_observer), &mut state, diff --git a/fuzzers/libfuzzer_libpng/Cargo.toml b/fuzzers/libfuzzer_libpng/Cargo.toml index 589fc6bb6b..e678766469 100644 --- a/fuzzers/libfuzzer_libpng/Cargo.toml +++ b/fuzzers/libfuzzer_libpng/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfuzzer_libpng" -version = "0.1.0" +version = "0.2.0" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" diff --git a/fuzzers/libfuzzer_libpng/src/lib.rs b/fuzzers/libfuzzer_libpng/src/lib.rs index 4d1b78ecd5..578c9287ad 100644 --- a/fuzzers/libfuzzer_libpng/src/lib.rs +++ b/fuzzers/libfuzzer_libpng/src/lib.rs @@ -12,6 +12,7 @@ use libafl::{ }, events::{setup_restarting_mgr_std, EventManager}, executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, + feedback_or, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, StdFuzzer}, mutators::scheduled::{havoc_mutations, StdScheduledMutator}, @@ -76,7 +77,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // Corpus that will be evolved, we keep it in memory for performance InMemoryCorpus::new(), // Feedbacks to rate the interestingness of an input - tuple_list!( + feedback_or!( MaxMapFeedback::new_with_observer_track(&edges_observer, true, false), TimeFeedback::new() ), @@ -84,7 +85,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // on disk so the user can get them after stopping the fuzzer OnDiskCorpus::new(objective_dir).unwrap(), // Feedbacks to recognize an input as solution - tuple_list!(CrashFeedback::new(), TimeoutFeedback::new()), + feedback_or!(CrashFeedback::new(), TimeoutFeedback::new()), ) }); @@ -120,7 +121,6 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // Create the executor for an in-process function with one observer for edge coverage and one for the execution time let mut executor = TimeoutExecutor::new( InProcessExecutor::new( - "in-process(edges,time)", &mut harness, tuple_list!(edges_observer, TimeObserver::new("time")), &mut state, diff --git a/fuzzers/libfuzzer_stb_image/Cargo.toml b/fuzzers/libfuzzer_stb_image/Cargo.toml index 3b3cd143ce..db0a4b5e25 100644 --- a/fuzzers/libfuzzer_stb_image/Cargo.toml +++ b/fuzzers/libfuzzer_stb_image/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfuzzer_stb_image" -version = "0.1.0" +version = "0.2.0" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" build = "build.rs" diff --git a/fuzzers/libfuzzer_stb_image/src/main.rs b/fuzzers/libfuzzer_stb_image/src/main.rs index 1f018de01f..3221034b8b 100644 --- a/fuzzers/libfuzzer_stb_image/src/main.rs +++ b/fuzzers/libfuzzer_stb_image/src/main.rs @@ -11,6 +11,7 @@ use libafl::{ }, events::setup_restarting_mgr_std, executors::{inprocess::InProcessExecutor, ExitKind}, + feedback_or, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, fuzzer::{Fuzzer, StdFuzzer}, mutators::scheduled::{havoc_mutations, StdScheduledMutator}, @@ -73,15 +74,15 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // Corpus that will be evolved, we keep it in memory for performance InMemoryCorpus::new(), // Feedbacks to rate the interestingness of an input - tuple_list!( + feedback_or!( MaxMapFeedback::new_with_observer_track(&edges_observer, true, false), TimeFeedback::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(objective_dir).unwrap(), - // Feedbacks to recognize an input as solution - tuple_list!(CrashFeedback::new()), + // Feedback to recognize an input as solution + CrashFeedback::new(), ) }); @@ -116,7 +117,6 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // Create the executor for an in-process function with just one observer for edge coverage let mut executor = InProcessExecutor::new( - "in-process(edges,time)", &mut harness, tuple_list!(edges_observer, TimeObserver::new("time")), &mut state, diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 12c79f9405..b0296623b0 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl" -version = "0.1.0" +version = "0.2.0" authors = ["Andrea Fioraldi ", "Dominik Maier "] description = "Slot your own fuzzers together and extend their features using Rust" documentation = "https://docs.rs/libafl" @@ -40,7 +40,8 @@ anymap_debug = ["serde_json"] # uses serde_json to Debug the anymap trait. Disab derive = ["libafl_derive"] # provide derive(SerdeAny) macro. llmp_small_maps = [] # reduces initial map size for llmp llmp_debug = ["backtrace"] # Enables debug output for LLMP -llmp_compression = [] #llmp compression using GZip +llmp_compression = [] # llmp compression using GZip +llmp_bind_public = [] # If set, llmp will bind to 0.0.0.0, allowing cross-device communication. Binds to localhost by default. [[example]] name = "llmp_test" @@ -61,6 +62,7 @@ libafl_derive = { version = "0.1.0", optional = true, path = "../libafl_derive" serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } # an easy way to debug print SerdeAnyMap compression = { version = "0.1.5" } num_enum = "0.5.1" +hostname = "^0.3" # Is there really no gethostname in the stdlib? [target.'cfg(target_os = "android")'.dependencies] backtrace = { version = "0.3", optional = true, default-features = false, features = ["std", "libbacktrace"] } # for llmp_debug diff --git a/libafl/examples/llmp_test/main.rs b/libafl/examples/llmp_test/main.rs index 9b6cde1ecb..0324dace4f 100644 --- a/libafl/examples/llmp_test/main.rs +++ b/libafl/examples/llmp_test/main.rs @@ -83,7 +83,7 @@ fn large_msg_loop(port: u16) -> ! { fn broker_message_hook( client_id: u32, tag: llmp::Tag, - _flags: llmp::Flag, + _flags: llmp::Flags, message: &[u8], ) -> Result { match tag { @@ -120,22 +120,31 @@ fn main() { let mode = std::env::args() .nth(1) - .expect("no mode specified, chose 'broker', 'ctr', 'adder', or 'large'"); + .expect("no mode specified, chose 'broker', 'b2b', 'ctr', 'adder', or 'large'"); let port: u16 = std::env::args() .nth(2) .unwrap_or("1337".into()) .parse::() .unwrap(); + // in the b2b use-case, this is our "own" port, we connect to the "normal" broker node on startup. + let b2b_port: u16 = std::env::args() + .nth(3) + .unwrap_or("4242".into()) + .parse::() + .unwrap(); println!("Launching in mode {} on port {}", mode, port); match mode.as_str() { "broker" => { let mut broker = llmp::LlmpBroker::new(StdShMemProvider::new().unwrap()).unwrap(); - broker - .launch_listener(llmp::Listener::Tcp( - std::net::TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap(), - )) - .unwrap(); + broker.launch_tcp_listener_on(port).unwrap(); + broker.loop_forever(&mut broker_message_hook, Some(Duration::from_millis(5))) + } + "b2b" => { + let mut broker = llmp::LlmpBroker::new(StdShMemProvider::new().unwrap()).unwrap(); + broker.launch_tcp_listener_on(b2b_port).unwrap(); + // connect back to the main broker. + broker.connect_b2b(("127.0.0.1", port)).unwrap(); broker.loop_forever(&mut broker_message_hook, Some(Duration::from_millis(5))) } "ctr" => { diff --git a/libafl/src/bolts/compress.rs b/libafl/src/bolts/compress.rs index 1da1f84cb8..9b34cc682d 100644 --- a/libafl/src/bolts/compress.rs +++ b/libafl/src/bolts/compress.rs @@ -42,6 +42,7 @@ impl GzipCompressor { /// Decompression. /// Flag is used to indicate if it's compressed or not + #[allow(clippy::unused_self)] pub fn decompress(&self, buf: &[u8]) -> Result, Error> { Ok(buf .iter() diff --git a/libafl/src/bolts/llmp.rs b/libafl/src/bolts/llmp.rs index 565f6c769c..5bbdcc870b 100644 --- a/libafl/src/bolts/llmp.rs +++ b/libafl/src/bolts/llmp.rs @@ -2,9 +2,9 @@ A library for low level message passing To send new messages, the clients place a new message at the end of their -client_out_map. If the ringbuf is filled up, they start place a +client_out_map. If the current map is filled up, they place a LLMP_AGE_END_OF_PAGE_V1 msg and alloc a new shmap. -Once the broker mapped a page, it flags it save for unmapping. +Once the broker mapped this same page, it flags it as safe for unmapping. ```text [client0] [client1] ... [clientN] @@ -41,7 +41,7 @@ current map. [client0] [client1] ... [clientN] ``` -In the future, if we need zero copy, the current_broadcast_map could instead +In the future, if we would need zero copy, the current_broadcast_map could instead list the client_out_map ID an offset for each message. In that case, the clients also need to create new shmaps once their bufs are filled up. @@ -50,11 +50,14 @@ To use, you will have to create a broker using llmp_broker_new(). Then register some clientloops using llmp_broker_register_threaded_clientloop (or launch them as seperate processes) and call llmp_broker_run(); +For broker2broker communication, all messages are forwarded via network sockets. + */ use alloc::{string::String, vec::Vec}; use core::{ cmp::max, + convert::TryFrom, fmt::Debug, mem::size_of, ptr, slice, @@ -64,9 +67,11 @@ use core::{ use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use std::{ + convert::TryInto, env, io::{Read, Write}, - net::{SocketAddr, TcpListener, TcpStream}, + net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs}, + sync::mpsc::channel, thread, }; @@ -104,8 +109,23 @@ const LLMP_TAG_NEW_SHM_CLIENT: Tag = 0xC11E471; /// The sender on this map is exiting (if broker exits, clients should exit gracefully); const LLMP_TAG_EXITING: Tag = 0x13C5171; -pub const LLMP_FLAG_INITIALIZED: Flag = 0x0; -pub const LLMP_FLAG_COMPRESSED: Flag = 0x1; +/// Unused... +pub const LLMP_FLAG_INITIALIZED: Flags = 0x0; +/// This message was compressed in transit +pub const LLMP_FLAG_COMPRESSED: Flags = 0x1; +/// From another broker. +pub const LLMP_FLAG_FROM_B2B: Flags = 0x2; + +/// Timt the broker 2 broker connection waits for incoming data, +/// before checking for own data to forward again. +const _LLMP_B2B_BLOCK_TIME: Duration = Duration::from_millis(3_000); + +/// If broker2broker is enabled, bind to public IP +#[cfg(feature = "llmp_bind_public")] +const _LLMP_BIND_ADDR: &str = "0.0.0.0"; +/// If broker2broker is disabled, bind to localhost +#[cfg(not(feature = "llmp_bind_public"))] +const _LLMP_BIND_ADDR: &str = "127.0.0.1"; /// An env var of this value indicates that the set value was a NULL PTR const _NULL_ENV_STR: &str = "_NULL"; @@ -127,29 +147,89 @@ static mut GLOBAL_SIGHANDLER_STATE: LlmpBrokerSignalHandler = LlmpBrokerSignalHa /// TAGs used thorughout llmp pub type Tag = u32; -pub type Flag = u64; +/// The client ID == the sender id. +pub type ClientId = u32; +/// The broker ID, for broker 2 broker communication. +pub type BrokerId = u32; +/// The flags, indicating, for example, enabled compression. +pub type Flags = u32; +/// The message ID, an ever-increasing number, unique only to a sharedmap/page. +pub type MessageId = u64; /// This is for the server the broker will spawn. /// If an llmp connection is local - use sharedmaps /// or remote (broker2broker) - forwarded via tcp +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum TcpRequest { - LocalClientHello { shmem: ShMemDescription }, - RemoteBrokerHello, - RemoteNewMessage { tag: Tag, payload: Vec }, + /// We would like to be a local client. + LocalClientHello { shmem_description: ShMemDescription }, + /// We would like to establish a b2b connection. + RemoteBrokerHello { hostname: String }, +} + +impl TryFrom<&Vec> for TcpRequest { + type Error = crate::Error; + + fn try_from(bytes: &Vec) -> Result { + Ok(postcard::from_bytes(bytes)?) + } +} + +/// Messages for broker 2 broker connection. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TcpRemoteNewMessage { + // The client ID of the original broker + client_id: ClientId, + // The message tag + tag: Tag, + // The flags + flags: Flags, + // The actual content of the message + payload: Vec, +} + +impl TryFrom<&Vec> for TcpRemoteNewMessage { + type Error = crate::Error; + + fn try_from(bytes: &Vec) -> Result { + Ok(postcard::from_bytes(bytes)?) + } } /// Responses for requests to the server. +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum TcpResponse { + /// After receiving a new connection, the broker immediately sends a Hello. + BrokerConnectHello { + /// The broker page a new local client can listen on + broker_map_description: ShMemDescription, + /// This broker's hostname + hostname: String, + }, LocalClientAccepted { - client_id: u32, - shmem: ShMemDescription, + /// The ClientId this client should send messages as + /// Mainly used for client-side deduplication of incoming messages + client_id: ClientId, }, RemoteBrokerAccepted { - broker_id: u32, - hostname: String, + /// The broker id of this element + broker_id: BrokerId, + }, + /// Something went wrong when processing the request. + Error { + /// Error description + description: String, }, } +impl TryFrom<&Vec> for TcpResponse { + type Error = crate::Error; + + fn try_from(bytes: &Vec) -> Result { + Ok(postcard::from_bytes(bytes)?) + } +} + /// Abstraction for listeners #[cfg(feature = "std")] pub enum Listener { @@ -226,6 +306,59 @@ fn msg_offset_from_env(env_name: &str) -> Result, Error> { }) } +/// Send one message as `u32` len and `[u8;len]` bytes +#[cfg(feature = "std")] +fn send_tcp_msg(stream: &mut TcpStream, msg: &T) -> Result<(), Error> +where + T: Serialize, +{ + let msg = postcard::to_allocvec(msg)?; + if msg.len() > u32::MAX as usize { + return Err(Error::IllegalState(format!( + "Trying to send message a tcp message > u32! (size: {})", + msg.len() + ))); + } + + #[cfg(feature = "llmp_debug")] + println!("LLMP TCP: Sending {} bytes", msg.len()); + + let size_bytes = (msg.len() as u32).to_be_bytes(); + stream.write_all(&size_bytes)?; + stream.write_all(&msg)?; + + #[cfg(feature = "llmp_debug")] + println!("LLMP TCP: Sending {} bytes finished.", msg.len()); + + Ok(()) +} + +/// Receive one message of `u32` len and `[u8; len]` bytes +#[cfg(feature = "std")] +fn recv_tcp_msg(stream: &mut TcpStream) -> Result, Error> { + // Always receive one be u32 of size, then the command. + + #[cfg(feature = "llmp_debug")] + println!( + "LLMP TCP: Waiting for packet... (Timeout: {:?})", + stream.read_timeout().unwrap_or(None) + ); + + let mut size_bytes = [0u8; 4]; + stream.read_exact(&mut size_bytes)?; + let size = u32::from_be_bytes(size_bytes); + let mut bytes = vec![]; + bytes.resize(size as usize, 0u8); + + #[cfg(feature = "llmp_debug")] + println!("LLMP TCP: Receiving payload of size {}", size); + + stream + .read_exact(&mut bytes) + .expect("Failed to read message body"); + Ok(bytes) +} + /// In case we don't have enough space, make sure the next page will be large /// enough. For now, we want to have at least enough space to store 2 of the /// largest messages we encountered (plus message one new_page message). @@ -255,15 +388,15 @@ unsafe fn _llmp_page_init(shmem: &mut SHM, sender: u32, allow_reinit }; (*page).magic = PAGE_INITIALIZED_MAGIC; (*page).sender = sender; - ptr::write_volatile(&mut (*page).current_msg_id, 0); + ptr::write_volatile(ptr::addr_of_mut!((*page).current_msg_id), 0); (*page).max_alloc_size = 0; // Don't forget to subtract our own header size (*page).size_total = map_size - LLMP_PAGE_HEADER_LEN; (*page).size_used = 0; (*(*page).messages.as_mut_ptr()).message_id = 0; (*(*page).messages.as_mut_ptr()).tag = LLMP_TAG_UNSET; - ptr::write_volatile(&mut (*page).save_to_unmap, 0); - ptr::write_volatile(&mut (*page).sender_dead, 0); + ptr::write_volatile(ptr::addr_of_mut!((*page).save_to_unmap), 0); + ptr::write_volatile(ptr::addr_of_mut!((*page).sender_dead), 0); assert!((*page).size_total != 0); } @@ -324,18 +457,22 @@ pub enum LlmpMsgHookResult { #[repr(C, packed)] pub struct LlmpMsg { /// A tag - pub tag: Tag, + pub tag: Tag, //u32 /// Sender of this messge - pub sender: u32, + pub sender: ClientId, //u32 + /// ID of another Broker, for b2b messages + pub broker: BrokerId, //u32 /// flags, currently only used for indicating compression - pub flags: Flag, + pub flags: Flags, //u32 /// The message ID, unique per page - pub message_id: u64, + pub message_id: MessageId, //u64 /// Buffer length as specified by the user pub buf_len: u64, /// (Actual) buffer length after padding + // Padding makes sure the next msg is aligned. pub buf_len_padded: u64, - /// The buf + /// The actual payload buf + // We try to keep the start of buf 64-bit aligned! pub buf: [u8; 0], } @@ -399,7 +536,7 @@ where #[cfg(feature = "std")] /// Creates either a broker, if the tcp port is not bound, or a client, connected to this port. pub fn on_port(shmem_provider: SP, port: u16) -> Result { - match TcpListener::bind(format!("127.0.0.1:{}", port)) { + match TcpListener::bind(format!("{}:{}", _LLMP_BIND_ADDR, port)) { Ok(listener) => { // We got the port. We are the broker! :) dbg!("We're the broker"); @@ -449,7 +586,7 @@ where } } - pub fn send_buf_with_flags(&mut self, tag: Tag, buf: &[u8], flags: Flag) -> Result<(), Error> { + pub fn send_buf_with_flags(&mut self, tag: Tag, buf: &[u8], flags: Flags) -> Result<(), Error> { match self { LlmpConnection::IsBroker { broker } => broker.send_buf_with_flags(tag, flags, buf), LlmpConnection::IsClient { client } => client.send_buf_with_flags(tag, flags, buf), @@ -582,7 +719,7 @@ where unsafe { compiler_fence(Ordering::SeqCst); // println!("Reading save_to_unmap from {:?}", current_out_map.page() as *const _); - ptr::read_volatile(&(*current_out_map.page()).save_to_unmap) != 0 + ptr::read_volatile(ptr::addr_of!((*current_out_map.page()).save_to_unmap)) != 0 } } @@ -638,7 +775,7 @@ where let last_msg = self.last_msg_sent; if (*page).size_used + EOP_MSG_SIZE > (*page).size_total { panic!("PROGRAM ABORT : BUG: EOP does not fit in page! page {:?}, size_current {:?}, size_total {:?}", page, - (*page).size_used, (*page).size_total); + ptr::addr_of!((*page).size_used), ptr::addr_of!((*page).size_total)); } let mut ret: *mut LlmpMsg = if !last_msg.is_null() { llmp_next_msg_ptr_checked(&mut map, last_msg, EOP_MSG_SIZE)? @@ -700,7 +837,7 @@ where #[cfg(all(feature = "llmp_debug", feature = "std"))] dbg!( page, - (*page), + *page, (*page).size_used, complete_msg_size, EOP_MSG_SIZE, @@ -719,7 +856,7 @@ where } } else if (*page).current_msg_id != (*last_msg).message_id { /* Oops, wrong usage! */ - panic!("BUG: The current message never got commited using send! (page->current_msg_id {:?}, last_msg->message_id: {})", (*page).current_msg_id, (*last_msg).message_id); + panic!("BUG: The current message never got commited using send! (page->current_msg_id {:?}, last_msg->message_id: {})", ptr::addr_of!((*page).current_msg_id), *ptr::addr_of!((*last_msg).message_id)); } else { buf_len_padded = complete_msg_size - size_of::(); /* DBG("XXX ret %p id %u buf_len_padded %lu complete_msg_size %lu\n", ret, ret->message_id, buf_len_padded, @@ -754,7 +891,7 @@ where || ((ret as usize) - (*page).messages.as_mut_ptr() as usize) != (*page).size_used { panic!("Allocated new message without calling send() inbetween. ret: {:?}, page: {:?}, complete_msg_size: {:?}, size_used: {:?}, last_msg: {:?}", ret, page, - buf_len_padded, (*page).size_used, last_msg); + buf_len_padded, ptr::addr_of!((*page).size_used), last_msg); } (*page).size_used += complete_msg_size; (*ret).buf_len_padded = buf_len_padded as u64; @@ -777,7 +914,10 @@ where panic!("Message sent twice!"); } if (*msg).tag == LLMP_TAG_UNSET { - panic!("No tag set on message with id {}", (*msg).message_id); + panic!( + "No tag set on message with id {}", + *ptr::addr_of!((*msg).message_id) + ); } let page = self.out_maps.last_mut().unwrap().page_mut(); if msg.is_null() || !llmp_msg_in_page(page, msg) { @@ -788,7 +928,7 @@ where } (*msg).message_id = (*page).current_msg_id + 1; compiler_fence(Ordering::SeqCst); - ptr::write_volatile(&mut (*page).current_msg_id, (*msg).message_id); + ptr::write_volatile(ptr::addr_of_mut!((*page).current_msg_id), (*msg).message_id); compiler_fence(Ordering::SeqCst); self.last_msg_sent = msg; Ok(()) @@ -827,7 +967,10 @@ where #[cfg(all(feature = "llmp_debug", feature = "std"))] println!("got new map at: {:?}", new_map); - ptr::write_volatile(&mut (*new_map).current_msg_id, (*old_map).current_msg_id); + ptr::write_volatile( + ptr::addr_of_mut!((*new_map).current_msg_id), + (*old_map).current_msg_id, + ); #[cfg(all(feature = "llmp_debug", feature = "std"))] println!("Setting max alloc size: {:?}", (*old_map).max_alloc_size); @@ -918,7 +1061,7 @@ where } } - pub fn send_buf_with_flags(&mut self, tag: Tag, flags: Flag, buf: &[u8]) -> Result<(), Error> { + pub fn send_buf_with_flags(&mut self, tag: Tag, flags: Flags, buf: &[u8]) -> Result<(), Error> { // Make sure we don't reuse already allocated tags if tag == LLMP_TAG_NEW_SHM_CLIENT || tag == LLMP_TAG_END_OF_PAGE @@ -1037,7 +1180,7 @@ where compiler_fence(Ordering::SeqCst); let mut page = self.current_recv_map.page_mut(); let last_msg = self.last_msg_recvd; - let current_msg_id = ptr::read_volatile(&(*page).current_msg_id); + let current_msg_id = ptr::read_volatile(ptr::addr_of!((*page).current_msg_id)); // Read the message from the page let ret = if current_msg_id == 0 { @@ -1068,7 +1211,7 @@ where LLMP_TAG_UNSET => panic!("BUG: Read unallocated msg"), LLMP_TAG_EXITING => { // The other side is done. - assert_eq!((*msg).buf_len, 0); + assert_eq!(*ptr::addr_of!((*msg).buf_len), 0); return Err(Error::ShuttingDown); } LLMP_TAG_END_OF_PAGE => { @@ -1078,8 +1221,8 @@ where if (*msg).buf_len < size_of::() as u64 { panic!( "Illegal message length for EOP (is {}/{}, expected {})", - (*msg).buf_len, - (*msg).buf_len_padded, + *ptr::addr_of!((*msg).buf_len), + *ptr::addr_of!((*msg).buf_len_padded), size_of::() ); } @@ -1093,7 +1236,7 @@ where self.last_msg_recvd = ptr::null(); // Mark the old page save to unmap, in case we didn't so earlier. - ptr::write_volatile(&mut (*page).save_to_unmap, 1); + ptr::write_volatile(ptr::addr_of_mut!((*page).save_to_unmap), 1); // Map the new page. The old one should be unmapped by Drop self.current_recv_map = @@ -1103,7 +1246,7 @@ where )?); page = self.current_recv_map.page_mut(); // Mark the new page save to unmap also (it's mapped by us, the broker now) - ptr::write_volatile(&mut (*page).save_to_unmap, 1); + ptr::write_volatile(ptr::addr_of_mut!((*page).save_to_unmap), 1); #[cfg(all(feature = "llmp_debug", feature = "std"))] println!( @@ -1139,7 +1282,7 @@ where } loop { compiler_fence(Ordering::SeqCst); - if ptr::read_volatile(&(*page).current_msg_id) != current_msg_id { + if ptr::read_volatile(ptr::addr_of!((*page).current_msg_id)) != current_msg_id { return match self.recv()? { Some(msg) => Ok(msg), None => panic!("BUG: blocking llmp message should never be NULL"), @@ -1159,8 +1302,10 @@ where } } + /// Receive the buffer, also reading the LLMP internal message flags + #[allow(clippy::type_complexity)] #[inline] - pub fn recv_buf_with_flags(&mut self) -> Result, Error> { + pub fn recv_buf_with_flags(&mut self) -> Result, Error> { unsafe { Ok(match self.recv()? { Some(msg) => Some(( @@ -1176,7 +1321,7 @@ where /// Returns the next sender, tag, buf, looping until it becomes available #[inline] - pub fn recv_buf_blocking(&mut self) -> Result<(u32, Tag, &[u8]), Error> { + pub fn recv_buf_blocking(&mut self) -> Result<(ClientId, Tag, &[u8]), Error> { unsafe { let msg = self.recv_blocking()?; Ok(( @@ -1233,7 +1378,7 @@ where SHM: ShMem, { /// Creates a new page, initializing the passed shared mem struct - pub fn new(sender: u32, mut new_map: SHM) -> Self { + pub fn new(sender: ClientId, mut new_map: SHM) -> Self { #[cfg(all(feature = "llmp_debug", feature = "std"))] println!( "LLMP_DEBUG: Initializing map on {} with size {}", @@ -1280,7 +1425,7 @@ where /// This indicates, that the page may safely be unmapped by the sender. pub fn mark_save_to_unmap(&mut self) { unsafe { - ptr::write_volatile(&mut (*self.page_mut()).save_to_unmap, 1); + ptr::write_volatile(ptr::addr_of_mut!((*self.page_mut()).save_to_unmap), 1); } } @@ -1403,7 +1548,7 @@ impl Handler for LlmpBrokerSignalHandler { /// It may intercept messages passing through. impl LlmpBroker where - SP: ShMemProvider, + SP: ShMemProvider + 'static, { /// Create and initialize a new llmp_broker pub fn new(mut shmem_provider: SP) -> Result { @@ -1447,6 +1592,69 @@ where }); } + /// Connects to a broker running on another machine. + /// This will spawn a new background thread, registered as client, that proxies all messages to a remote machine. + /// Returns the description of the new page that still needs to be announced/added to the broker afterwards. + #[cfg(feature = "std")] + pub fn connect_b2b(&mut self, addr: A) -> Result<(), Error> + where + A: ToSocketAddrs, + { + let mut stream = TcpStream::connect(addr)?; + println!("B2B: Connected to {:?}", stream); + + match (&recv_tcp_msg(&mut stream)?).try_into()? { + TcpResponse::BrokerConnectHello { + broker_map_description: _, + hostname, + } => println!("B2B: Connected to {}", hostname), + _ => { + return Err(Error::IllegalState( + "Unexpected response from B2B server received.".to_string(), + )) + } + }; + + let hostname = hostname::get() + .unwrap_or_else(|_| "".into()) + .to_string_lossy() + .into(); + + send_tcp_msg(&mut stream, &TcpRequest::RemoteBrokerHello { hostname })?; + + let broker_id = match (&recv_tcp_msg(&mut stream)?).try_into()? { + TcpResponse::RemoteBrokerAccepted { broker_id } => { + println!("B2B: Got Connection Ack, broker_id {}", broker_id); + broker_id + } + _ => { + return Err(Error::IllegalState( + "Unexpected response from B2B server received.".to_string(), + )); + } + }; + + // TODO: use broker ids! + println!("B2B: We are broker {}", broker_id); + + // TODO: handle broker_ids properly/at all. + let map_description = Self::b2b_thread_on( + stream, + &self.shmem_provider, + self.llmp_clients.len() as ClientId, + &self.llmp_out.out_maps.first().unwrap().shmem.description(), + )?; + + let new_map = + LlmpSharedMap::existing(self.shmem_provider.from_description(map_description)?); + + { + self.register_client(new_map); + } + + Ok(()) + } + /// For internal use: Forward the current message to the out map. unsafe fn forward_msg(&mut self, msg: *mut LlmpMsg) -> Result<(), Error> { let mut out: *mut LlmpMsg = self.alloc_next((*msg).buf_len_padded as usize)?; @@ -1471,7 +1679,7 @@ where #[inline] pub fn once(&mut self, on_new_msg: &mut F) -> Result<(), Error> where - F: FnMut(u32, Tag, Flag, &[u8]) -> Result, + F: FnMut(ClientId, Tag, Flags, &[u8]) -> Result, { compiler_fence(Ordering::SeqCst); for i in 0..self.llmp_clients.len() { @@ -1502,7 +1710,7 @@ where /// 5 millis of sleep can't hurt to keep busywait not at 100% pub fn loop_forever(&mut self, on_new_msg: &mut F, sleep_time: Option) where - F: FnMut(u32, Tag, Flag, &[u8]) -> Result, + F: FnMut(ClientId, Tag, Flags, &[u8]) -> Result, { #[cfg(unix)] if let Err(_e) = unsafe { setup_signal_handler(&mut GLOBAL_SIGHANDLER_STATE) } { @@ -1539,20 +1747,222 @@ where self.llmp_out.send_buf(tag, buf) } - pub fn send_buf_with_flags(&mut self, tag: Tag, flags: Flag, buf: &[u8]) -> Result<(), Error> { + pub fn send_buf_with_flags(&mut self, tag: Tag, flags: Flags, buf: &[u8]) -> Result<(), Error> { self.llmp_out.send_buf_with_flags(tag, flags, buf) } - #[cfg(feature = "std")] /// Launches a thread using a tcp listener socket, on which new clients may connect to this broker /// Does so on the given port. + #[cfg(feature = "std")] pub fn launch_tcp_listener_on(&mut self, port: u16) -> Result, Error> { - let listener = TcpListener::bind(format!("127.0.0.1:{}", port))?; + let listener = TcpListener::bind(format!("{}:{}", _LLMP_BIND_ADDR, port))?; // accept connections and process them, spawning a new thread for each one println!("Server listening on port {}", port); self.launch_listener(Listener::Tcp(listener)) } + /// Announces a new client on the given shared map. + /// Called from a background thread, typically. + /// Upon receiving this message, the broker should map the announced page and start trckang it for new messages. + #[allow(dead_code)] + fn announce_new_client( + sender: &mut LlmpSender, + shmem_description: &ShMemDescription, + ) -> Result<(), Error> { + unsafe { + let msg = sender + .alloc_next(size_of::()) + .expect("Could not allocate a new message in shared map."); + (*msg).tag = LLMP_TAG_NEW_SHM_CLIENT; + let pageinfo = (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo; + (*pageinfo).shm_str = *shmem_description.id.as_slice(); + (*pageinfo).map_size = shmem_description.size; + sender.send(msg) + } + } + + /// For broker to broker connections: + /// Launches a proxy thread. + /// It will read outgoing messages from the given broker map (and handle EOP by mapping a new page). + /// This function returns the ShMemDescription the client uses to place incoming messages. + /// The thread exits, when the remote broker disconnects. + #[cfg(feature = "std")] + #[allow(clippy::let_and_return)] + fn b2b_thread_on( + mut stream: TcpStream, + shmem_provider: &SP, + b2b_client_id: ClientId, + broker_map_description: &ShMemDescription, + ) -> Result { + let broker_map_description = *broker_map_description; + let mut shmem_provider_clone = shmem_provider.clone(); + + // A channel to get the new "client's" sharedmap id from + let (send, recv) = channel(); + + // (For now) the thread remote broker 2 broker just acts like a "normal" llmp client, except it proxies all messages to the attached socket, in both directions. + thread::spawn(move || { + // as always, call post_fork to potentially reconnect the provider (for threaded/forked use) + shmem_provider_clone.post_fork(); + + #[cfg(fature = "llmp_debug")] + println!("B2b: Spawned proxy thread"); + + // The background thread blocks on the incoming connection for 15 seconds (if no data is available), then checks if it should forward own messages, then blocks some more. + stream + .set_read_timeout(Some(_LLMP_B2B_BLOCK_TIME)) + .expect("Failed to set tcp stream timeout"); + + let mut new_sender = + match LlmpSender::new(shmem_provider_clone.clone(), b2b_client_id, false) { + Ok(new_sender) => new_sender, + Err(e) => { + panic!("B2B: Could not map shared map: {}", e); + } + }; + + send.send(new_sender.out_maps.first().unwrap().shmem.description()) + .expect("B2B: Error sending map description to channel!"); + + // the receiver receives from the local broker, and forwards it to the tcp stream. + let mut local_receiver = LlmpReceiver::on_existing_from_description( + shmem_provider_clone, + &LlmpDescription { + last_message_offset: None, + shmem: broker_map_description, + }, + ) + .expect("Failed to map local page in broker 2 broker thread!"); + + #[cfg(all(feature = "llmp_debug", feature = "std"))] + dbg!("B2B: Starting proxy loop :)"); + + loop { + // first, forward all data we have. + while let Some((client_id, tag, flags, payload)) = local_receiver + .recv_buf_with_flags() + .expect("Error reading from local page!") + { + if client_id == b2b_client_id { + dbg!("Ignored message we probably sent earlier (same id)", tag); + continue; + } + + #[cfg(all(feature = "llmp_debug", feature = "std"))] + dbg!( + "Fowarding message via broker2broker connection", + payload.len() + ); + // We got a new message! Forward... + send_tcp_msg( + &mut stream, + &TcpRemoteNewMessage { + client_id, + tag, + flags, + payload: payload.to_vec(), + }, + ) + .expect("Error sending message via broker 2 broker"); + } + + // Then, see if we can receive something. + // We set a timeout on the receive earlier. + // This makes sure we will still forward our own stuff. + // Forwarding happens between each recv, too, as simplification. + // We ignore errors completely as they may be timeout, or stream closings. + // Instead, we catch stream close when/if we next try to send. + if let Ok(val) = recv_tcp_msg(&mut stream) { + let msg: TcpRemoteNewMessage = (&val).try_into().expect( + "Illegal message received from broker 2 broker connection - shutting down.", + ); + + #[cfg(all(feature = "llmp_debug", feature = "std"))] + dbg!( + "Fowarding incoming message from broker2broker connection", + msg.payload.len() + ); + + // TODO: Could probably optimize this somehow to forward all queued messages between locks... oh well. + // Todo: somehow mangle in the other broker id? ClientId? + new_sender + .send_buf_with_flags(msg.tag, msg.flags | LLMP_FLAG_FROM_B2B, &msg.payload) + .expect("B2B: Error forwarding message. Exiting."); + } else { + #[cfg(all(feature = "llmp_debug", feature = "std"))] + dbg!("Received no input, timeout or closed. Looping back up :)"); + } + } + }); + + let ret = recv.recv().map_err(|_| { + Error::Unknown("Error launching background thread for b2b communcation".to_string()) + }); + + #[cfg(all(feature = "llmp_debug", feature = "std"))] + dbg!("B2B: returning from loop. Success: {}", ret.is_ok()); + + ret + } + + /// handles a single tcp request in the current context. + #[cfg(feature = "std")] + fn handle_tcp_request( + mut stream: TcpStream, + request: &TcpRequest, + current_client_id: &mut u32, + sender: &mut LlmpSender, + shmem_provider: &SP, + broker_map_description: &ShMemDescription, + ) { + match request { + TcpRequest::LocalClientHello { shmem_description } => { + match Self::announce_new_client(sender, shmem_description) { + Ok(()) => (), + Err(e) => println!("Error forwarding client on map: {:?}", e), + }; + + if let Err(e) = send_tcp_msg( + &mut stream, + &TcpResponse::LocalClientAccepted { + client_id: *current_client_id, + }, + ) { + println!("An error occurred sending via tcp {}", e); + }; + *current_client_id += 1; + } + TcpRequest::RemoteBrokerHello { hostname } => { + println!("B2B new client: {}", hostname); + + // TODO: Clean up broker ids. + if send_tcp_msg( + &mut stream, + &TcpResponse::RemoteBrokerAccepted { + broker_id: *current_client_id, + }, + ) + .is_err() + { + println!("Error accepting broker, ignoring."); + return; + } + + if let Ok(shmem_description) = Self::b2b_thread_on( + stream, + shmem_provider, + *current_client_id, + &broker_map_description, + ) { + if Self::announce_new_client(sender, &shmem_description).is_err() { + println!("B2B: Error announcing client {:?}", shmem_description); + }; + *current_client_id += 1; + } + } + }; + } + #[cfg(feature = "std")] /// Launches a thread using a listener socket, on which new clients may connect to this broker pub fn launch_listener(&mut self, listener: Listener) -> Result, Error> { @@ -1562,37 +1972,43 @@ where // to read from the initial map id. let client_out_map_mem = &self.llmp_out.out_maps.first().unwrap().shmem; - let broadcast_map_description = postcard::to_allocvec(&client_out_map_mem.description())?; - - let mut incoming_map_description_serialized = vec![0u8; broadcast_map_description.len()]; + let broker_map_description = client_out_map_mem.description(); + let hostname = hostname::get() + .unwrap_or_else(|_| "".into()) + .to_string_lossy() + .into(); + let broker_hello = TcpResponse::BrokerConnectHello { + broker_map_description, + hostname, + }; - let llmp_tcp_id = self.llmp_clients.len() as u32; + let llmp_tcp_id = self.llmp_clients.len() as ClientId; // Tcp out map sends messages from background thread tcp server to foreground client let tcp_out_map = LlmpSharedMap::new( llmp_tcp_id, self.shmem_provider.new_map(LLMP_CFG_INITIAL_MAP_SIZE)?, ); - let shmem_id = tcp_out_map.shmem.id(); - let tcp_out_map_str = *shmem_id.as_slice(); - let tcp_out_map_size = tcp_out_map.shmem.len(); + let tcp_out_map_description = tcp_out_map.shmem.description(); self.register_client(tcp_out_map); let mut shmem_provider_clone = self.shmem_provider.clone(); Ok(thread::spawn(move || { + // Call `post_fork` (even though this is not forked) so we get a new connection to the cloned `ShMemServer` if we are using a `ServedShMemProvider` shmem_provider_clone.post_fork(); - // Clone so we get a new connection to the AshmemServer if we are using - // ServedShMemProvider - let mut new_client_sender = LlmpSender { - id: 0, + + let mut current_client_id = llmp_tcp_id + 1; + + let mut tcp_incoming_sender = LlmpSender { + id: llmp_tcp_id, last_msg_sent: ptr::null_mut(), out_maps: vec![LlmpSharedMap::existing( shmem_provider_clone - .from_id_and_size(ShMemId::from_slice(&tcp_out_map_str), tcp_out_map_size) + .from_description(tcp_out_map_description) .unwrap(), )], - // drop pages to the broker if it already read them + // drop pages to the broker, if it already read them. keep_pages_forever: false, shmem_provider: shmem_provider_clone.clone(), }; @@ -1601,38 +2017,40 @@ where match listener.accept() { ListenerStream::Tcp(mut stream, addr) => { dbg!("New connection", addr, stream.peer_addr().unwrap()); - match stream.write(&broadcast_map_description) { - Ok(_) => {} // fire & forget + + // Send initial information, without anyone asking. + // This makes it a tiny bit easier to map the broker map for new Clients. + match send_tcp_msg(&mut stream, &broker_hello) { + Ok(()) => {} Err(e) => { - dbg!("Could not send to shmap to client", e); + dbg!("Error sending initial hello: {:?}", e); continue; } - }; - match stream.read_exact(&mut incoming_map_description_serialized) { - Ok(()) => (), + } + + let buf = match recv_tcp_msg(&mut stream) { + Ok(buf) => buf, Err(e) => { - dbg!("Ignoring failed read from client", e); + dbg!("Error receving from tcp", e); continue; } }; - if let Ok(incoming_map_description) = postcard::from_bytes::( - &incoming_map_description_serialized, - ) { - unsafe { - let msg = new_client_sender - .alloc_next(size_of::()) - .expect("Could not allocate a new message in shared map."); - (*msg).tag = LLMP_TAG_NEW_SHM_CLIENT; - let pageinfo = - (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo; - (*pageinfo).shm_str = *incoming_map_description.id.as_slice(); - (*pageinfo).map_size = incoming_map_description.size; - match new_client_sender.send(msg) { - Ok(()) => (), - Err(e) => println!("Error forwarding client on map: {:?}", e), - }; + let req = match (&buf).try_into() { + Ok(req) => req, + Err(e) => { + dbg!("Could not deserialize tcp message", e); + continue; } - } + }; + + Self::handle_tcp_request( + stream, + &req, + &mut current_client_id, + &mut tcp_incoming_sender, + &shmem_provider_clone, + &broker_map_description, + ); } ListenerStream::Empty() => { continue; @@ -1646,7 +2064,7 @@ where #[inline] unsafe fn handle_new_msgs(&mut self, client_id: u32, on_new_msg: &mut F) -> Result<(), Error> where - F: FnMut(u32, Tag, Flag, &[u8]) -> Result, + F: FnMut(ClientId, Tag, Flags, &[u8]) -> Result, { let mut next_id = self.llmp_clients.len() as u32; @@ -1666,15 +2084,16 @@ where if (*msg).tag == LLMP_TAG_NEW_SHM_CLIENT { /* This client informs us about yet another new client add it to the list! Also, no need to forward this msg. */ + let msg_buf_len_padded = *ptr::addr_of!((*msg).buf_len_padded); if (*msg).buf_len < size_of::() as u64 { #[cfg(feature = "std")] println!("Ignoring broken CLIENT_ADDED msg due to incorrect size. Expected {} but got {}", - (*msg).buf_len_padded, + msg_buf_len_padded, size_of::() ); #[cfg(not(feature = "std"))] return Err(Error::Unknown(format!("Broken CLIENT_ADDED msg with incorrect size received. Expected {} but got {}", - (*msg).buf_len_padded, + msg_buf_len_padded, size_of::() ))); } else { @@ -1880,7 +2299,7 @@ where self.sender.send_buf(tag, buf) } - pub fn send_buf_with_flags(&mut self, tag: Tag, flags: Flag, buf: &[u8]) -> Result<(), Error> { + pub fn send_buf_with_flags(&mut self, tag: Tag, flags: Flags, buf: &[u8]) -> Result<(), Error> { self.sender.send_buf_with_flags(tag, flags, buf) } @@ -1943,7 +2362,8 @@ where self.receiver.recv_buf_blocking() } - pub fn recv_buf_with_flags(&mut self) -> Result, Error> { + #[allow(clippy::type_complexity)] + pub fn recv_buf_with_flags(&mut self) -> Result, Error> { self.receiver.recv_buf_with_flags() } @@ -1957,26 +2377,48 @@ where #[cfg(feature = "std")] /// Create a LlmpClient, getting the ID from a given port pub fn create_attach_to_tcp(mut shmem_provider: SP, port: u16) -> Result { - let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port))?; + let mut stream = TcpStream::connect(format!("{}:{}", _LLMP_BIND_ADDR, port))?; println!("Connected to port {}", port); - // First, get the serialized description size by serializing a dummy. - let dummy_description = ShMemDescription { - size: 0, - id: ShMemId::default(), + let broker_map_description = if let TcpResponse::BrokerConnectHello { + broker_map_description, + hostname: _, + } = (&recv_tcp_msg(&mut stream)?).try_into()? + { + broker_map_description + } else { + return Err(Error::IllegalState( + "Received unexpected Broker Hello".to_string(), + )); }; - let mut new_broker_map_str = postcard::to_allocvec(&dummy_description)?; - stream.read_exact(&mut new_broker_map_str)?; + let map = LlmpSharedMap::existing(shmem_provider.from_description(broker_map_description)?); + let mut ret = Self::new(shmem_provider, map)?; - let broker_map_description: ShMemDescription = postcard::from_bytes(&new_broker_map_str)?; + let client_hello_req = TcpRequest::LocalClientHello { + shmem_description: ret.sender.out_maps.first().unwrap().shmem.description(), + }; - let map = LlmpSharedMap::existing(shmem_provider.from_description(broker_map_description)?); - let ret = Self::new(shmem_provider, map)?; + send_tcp_msg(&mut stream, &client_hello_req)?; + + let client_id = if let TcpResponse::LocalClientAccepted { client_id } = + (&recv_tcp_msg(&mut stream)?).try_into()? + { + client_id + } else { + return Err(Error::IllegalState( + "Unexpected Response from Broker".to_string(), + )); + }; + + // Set our ID to the one the broker sent us.. + // This is mainly so we can filter out our own msgs later. + ret.sender.id = client_id; + // Also set the sender on our initial llmp map correctly. + unsafe { + (*ret.sender.out_maps.first_mut().unwrap().page_mut()).sender = client_id; + } - let own_map_description_bytes = - postcard::to_allocvec(&ret.sender.out_maps.first().unwrap().shmem.description())?; - stream.write_all(&own_map_description_bytes)?; Ok(ret) } } diff --git a/libafl/src/bolts/os/unix_signals.rs b/libafl/src/bolts/os/unix_signals.rs index 0254b32657..b661f6985d 100644 --- a/libafl/src/bolts/os/unix_signals.rs +++ b/libafl/src/bolts/os/unix_signals.rs @@ -25,7 +25,7 @@ pub use libc::{c_void, siginfo_t}; #[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)] #[repr(i32)] -#[allow(clippy::clippy::pub_enum_variant_names)] +#[allow(clippy::pub_enum_variant_names)] pub enum Signal { SigAbort = SIGABRT, SigBus = SIGBUS, diff --git a/libafl/src/bolts/serdeany.rs b/libafl/src/bolts/serdeany.rs index 8782238756..6771eaea39 100644 --- a/libafl/src/bolts/serdeany.rs +++ b/libafl/src/bolts/serdeany.rs @@ -58,7 +58,7 @@ where where D: serde::de::Deserializer<'de>, { - let mut erased = erased_serde::Deserializer::erase(deserializer); + let mut erased = ::erase(deserializer); (self.cb)(&mut erased).map_err(serde::de::Error::custom) } } diff --git a/libafl/src/bolts/shmem.rs b/libafl/src/bolts/shmem.rs index 2b64dfc04f..9640e60212 100644 --- a/libafl/src/bolts/shmem.rs +++ b/libafl/src/bolts/shmem.rs @@ -171,8 +171,8 @@ pub trait ShMemProvider: Send + Clone + Default + Debug { )) } - /// This method should be called after a fork or thread creation event, allowing the ShMem to - /// reset thread specific info. + /// This method should be called after a fork or after cloning/a thread creation event, allowing the ShMem to + /// reset thread specific info, and potentially reconnect. fn post_fork(&mut self) { // do nothing } @@ -183,6 +183,9 @@ pub trait ShMemProvider: Send + Clone + Default + Debug { } } +/// A Refernce Counted shared map, +/// that can use internal mutability. +/// Useful if the `ShMemProvider` needs to keep local state. #[derive(Debug, Clone)] pub struct RcShMem { internal: ManuallyDrop, @@ -216,6 +219,9 @@ impl Drop for RcShMem { } } +/// A Refernce Counted `ShMemProvider`, +/// that can use internal mutability. +/// Useful if the `ShMemProvider` needs to keep local state. #[derive(Debug, Clone)] pub struct RcShMemProvider { internal: Rc>, @@ -274,6 +280,11 @@ where } } +/// A Unix sharedmem implementation. +/// +/// On Android, this is partially reused to wrap `Ashmem`, +/// Although for an `AshmemShMemProvider using a unix domain socket +/// Is needed on top. #[cfg(all(unix, feature = "std"))] pub mod unix_shmem { diff --git a/libafl/src/corpus/testcase.rs b/libafl/src/corpus/testcase.rs index bae9860ed6..8de755ebef 100644 --- a/libafl/src/corpus/testcase.rs +++ b/libafl/src/corpus/testcase.rs @@ -23,8 +23,6 @@ where input: Option, /// Filename, if this testcase is backed by a file in the filesystem filename: Option, - /// Accumulated fitness from all the feedbacks - fitness: u32, /// Map of metadata associated with this testcase metadata: SerdeAnyMap, /// Time needed to execute the input @@ -120,24 +118,6 @@ where self.filename = Some(filename); } - /// Get the fitness - #[inline] - pub fn fitness(&self) -> u32 { - self.fitness - } - - /// Get the fitness (mutable) - #[inline] - pub fn fitness_mut(&mut self) -> &mut u32 { - &mut self.fitness - } - - /// Set the fitness - #[inline] - pub fn set_fitness(&mut self, fitness: u32) { - self.fitness = fitness; - } - /// Get the execution time of the testcase pub fn exec_time(&self) -> &Option { &self.exec_time @@ -157,7 +137,6 @@ where Testcase { input: Some(input.into()), filename: None, - fitness: 0, metadata: SerdeAnyMap::new(), exec_time: None, cached_len: None, @@ -170,20 +149,6 @@ where Testcase { input: Some(input), filename: Some(filename), - fitness: 0, - metadata: SerdeAnyMap::new(), - exec_time: None, - cached_len: None, - } - } - - /// Create a new Testcase instace given an input and a fitness - #[inline] - pub fn with_fitness(input: I, fitness: u32) -> Self { - Testcase { - input: Some(input), - filename: None, - fitness, metadata: SerdeAnyMap::new(), exec_time: None, cached_len: None, @@ -195,7 +160,6 @@ where Testcase { input: None, filename: None, - fitness: 0, metadata: SerdeAnyMap::new(), exec_time: None, cached_len: None, diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index 90fd00f864..a57532866e 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -5,7 +5,7 @@ use core::{marker::PhantomData, time::Duration}; use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "std")] -use core::ptr::read_volatile; +use core::ptr::{addr_of, read_volatile}; #[cfg(feature = "std")] use crate::bolts::{ @@ -15,7 +15,7 @@ use crate::bolts::{ use crate::{ bolts::{ - llmp::{self, Flag, LlmpClientDescription, LlmpSender, Tag}, + llmp::{self, Flags, LlmpClientDescription, LlmpSender, Tag}, shmem::ShMemProvider, }, corpus::CorpusScheduler, @@ -173,7 +173,7 @@ where #[cfg(feature = "llmp_compression")] let compressor = &self.compressor; broker.loop_forever( - &mut |sender_id: u32, tag: Tag, _flags: Flag, msg: &[u8]| { + &mut |sender_id: u32, tag: Tag, _flags: Flags, msg: &[u8]| { if tag == LLMP_TAG_EVENT_TO_BOTH { #[cfg(not(feature = "llmp_compression"))] let event_bytes = msg; @@ -292,11 +292,10 @@ where let observers: OT = postcard::from_bytes(&observers_buf)?; // TODO include ExitKind in NewTestcase - let fitness = state.is_interesting(&input, &observers, &ExitKind::Ok)?; - if fitness > 0 - && state - .add_if_interesting(&input, fitness, scheduler)? - .is_some() + let is_interesting = state.is_interesting(&input, &observers, &ExitKind::Ok)?; + if state + .add_if_interesting(&input, is_interesting, scheduler)? + .is_some() { #[cfg(feature = "std")] println!("Added received Testcase"); @@ -376,7 +375,7 @@ where #[cfg(feature = "llmp_compression")] fn fire(&mut self, _state: &mut S, event: Event) -> Result<(), Error> { let serialized = postcard::to_allocvec(&event)?; - let flags: Flag = LLMP_FLAG_INITIALIZED; + let flags: Flags = LLMP_FLAG_INITIALIZED; match self.compressor.compress(&serialized)? { Some(comp_buf) => { @@ -610,7 +609,9 @@ where #[cfg(windows)] let child_status = startable_self()?.status()?; - if unsafe { read_volatile(&(*receiver.current_recv_map.page()).size_used) } == 0 { + if unsafe { read_volatile(addr_of!((*receiver.current_recv_map.page()).size_used)) } + == 0 + { #[cfg(unix)] if child_status == 137 { // Out of Memory, see https://tldp.org/LDP/abs/html/exitcodes.html diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index 0b21ca6745..8bc152bbb1 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -14,46 +14,54 @@ use crate::bolts::os::unix_signals::setup_signal_handler; use crate::bolts::os::windows_exceptions::setup_exception_handler; use crate::{ - bolts::tuples::Named, corpus::Corpus, events::EventManager, - executors::{Executor, ExitKind, HasObservers}, - feedbacks::FeedbacksTuple, + executors::{ + Executor, ExitKind, HasExecHooks, HasExecHooksTuple, HasObservers, HasObserversHooks, + }, + feedbacks::Feedback, inputs::{HasTargetBytes, Input}, observers::ObserversTuple, - state::{HasObjectives, HasSolutions}, + state::{HasObjective, HasSolutions}, Error, }; /// The inmem executor simply calls a target function, then returns afterwards. -pub struct InProcessExecutor<'a, H, I, OT> +pub struct InProcessExecutor<'a, EM, H, I, OT, S> where H: FnMut(&[u8]) -> ExitKind, I: Input + HasTargetBytes, OT: ObserversTuple, { - /// The name of this executor instance, to address it from other components - name: &'static str, /// The harness function, being executed for each fuzzing loop execution harness_fn: &'a mut H, /// The observers, observing each run observers: OT, - phantom: PhantomData, + phantom: PhantomData<(EM, I, S)>, } -impl<'a, H, I, OT> Executor for InProcessExecutor<'a, H, I, OT> +impl<'a, EM, H, I, OT, S> Executor for InProcessExecutor<'a, EM, H, I, OT, S> where H: FnMut(&[u8]) -> ExitKind, I: Input + HasTargetBytes, OT: ObserversTuple, { #[inline] - fn pre_exec( - &mut self, - _state: &mut S, - _event_mgr: &mut EM, - _input: &I, - ) -> Result<(), Error> { + fn run_target(&mut self, input: &I) -> Result { + let bytes = input.target_bytes(); + let ret = (self.harness_fn)(bytes.as_slice()); + Ok(ret) + } +} + +impl<'a, EM, H, I, OT, S> HasExecHooks for InProcessExecutor<'a, EM, H, I, OT, S> +where + H: FnMut(&[u8]) -> ExitKind, + I: Input + HasTargetBytes, + OT: ObserversTuple, +{ + #[inline] + fn pre_exec(&mut self, _state: &mut S, _event_mgr: &mut EM, _input: &I) -> Result<(), Error> { #[cfg(unix)] unsafe { let data = &mut unix_signal_handler::GLOBAL_STATE; @@ -92,19 +100,7 @@ where } #[inline] - fn run_target(&mut self, input: &I) -> Result { - let bytes = input.target_bytes(); - let ret = (self.harness_fn)(bytes.as_slice()); - Ok(ret) - } - - #[inline] - fn post_exec( - &mut self, - _state: &mut S, - _event_mgr: &mut EM, - _input: &I, - ) -> Result<(), Error> { + fn post_exec(&mut self, _state: &mut S, _event_mgr: &mut EM, _input: &I) -> Result<(), Error> { #[cfg(unix)] unsafe { write_volatile( @@ -125,18 +121,7 @@ where } } -impl<'a, H, I, OT> Named for InProcessExecutor<'a, H, I, OT> -where - H: FnMut(&[u8]) -> ExitKind, - I: Input + HasTargetBytes, - OT: ObserversTuple, -{ - fn name(&self) -> &str { - self.name - } -} - -impl<'a, H, I, OT> HasObservers for InProcessExecutor<'a, H, I, OT> +impl<'a, EM, H, I, OT, S> HasObservers for InProcessExecutor<'a, EM, H, I, OT, S> where H: FnMut(&[u8]) -> ExitKind, I: Input + HasTargetBytes, @@ -153,7 +138,15 @@ where } } -impl<'a, H, I, OT> InProcessExecutor<'a, H, I, OT> +impl<'a, EM, H, I, OT, S> HasObserversHooks for InProcessExecutor<'a, EM, H, I, OT, S> +where + H: FnMut(&[u8]) -> ExitKind, + I: Input + HasTargetBytes, + OT: ObserversTuple + HasExecHooksTuple, +{ +} + +impl<'a, EM, H, I, OT, S> InProcessExecutor<'a, EM, H, I, OT, S> where H: FnMut(&[u8]) -> ExitKind, I: Input + HasTargetBytes, @@ -162,12 +155,10 @@ where /// Create a new in mem executor. /// Caution: crash and restart in one of them will lead to odd behavior if multiple are used, /// depending on different corpus or state. - /// * `name` - the name of this executor (to address it along the way) /// * `harness_fn` - the harness, executiong the function /// * `observers` - the observers observing the target during execution /// This may return an error on unix, if signal handler setup fails - pub fn new( - name: &'static str, + pub fn new( harness_fn: &'a mut H, observers: OT, _state: &mut S, @@ -176,19 +167,19 @@ where where EM: EventManager, OC: Corpus, - OFT: FeedbacksTuple, - S: HasObjectives + HasSolutions, + OF: Feedback, + S: HasObjective + HasSolutions, { #[cfg(unix)] unsafe { let data = &mut unix_signal_handler::GLOBAL_STATE; write_volatile( &mut data.crash_handler, - unix_signal_handler::inproc_crash_handler::, + unix_signal_handler::inproc_crash_handler::, ); write_volatile( &mut data.timeout_handler, - unix_signal_handler::inproc_timeout_handler::, + unix_signal_handler::inproc_timeout_handler::, ); setup_signal_handler(data)?; @@ -199,11 +190,11 @@ where let data = &mut windows_exception_handler::GLOBAL_STATE; write_volatile( &mut data.crash_handler, - windows_exception_handler::inproc_crash_handler::, + windows_exception_handler::inproc_crash_handler::, ); //write_volatile( // &mut data.timeout_handler, - // windows_exception_handler::inproc_timeout_handler::, + // windows_exception_handler::inproc_timeout_handler::, //); setup_exception_handler(data)?; @@ -213,7 +204,6 @@ where Ok(Self { harness_fn, observers, - name, phantom: PhantomData, }) } @@ -244,10 +234,10 @@ mod unix_signal_handler { corpus::{Corpus, Testcase}, events::{Event, EventManager}, executors::ExitKind, - feedbacks::FeedbacksTuple, + feedbacks::Feedback, inputs::{HasTargetBytes, Input}, observers::ObserversTuple, - state::{HasObjectives, HasSolutions}, + state::{HasObjective, HasSolutions}, }; // TODO merge GLOBAL_STATE with the Windows one @@ -318,7 +308,7 @@ mod unix_signal_handler { } #[cfg(unix)] - pub unsafe fn inproc_timeout_handler( + pub unsafe fn inproc_timeout_handler( _signal: Signal, _info: siginfo_t, _context: &mut ucontext_t, @@ -327,8 +317,8 @@ mod unix_signal_handler { EM: EventManager, OT: ObserversTuple, OC: Corpus, - OFT: FeedbacksTuple, - S: HasObjectives + HasSolutions, + OF: Feedback, + S: HasObjective + HasSolutions, I: Input + HasTargetBytes, { let state = (data.state_ptr as *mut S).as_mut().unwrap(); @@ -347,11 +337,11 @@ mod unix_signal_handler { let input = (data.current_input_ptr as *const I).as_ref().unwrap(); data.current_input_ptr = ptr::null(); - let obj_fitness = state - .objectives_mut() - .is_interesting_all(&input, observers, &ExitKind::Timeout) - .expect("In timeout handler objectives failure."); - if obj_fitness > 0 { + let interesting = state + .objective_mut() + .is_interesting(&input, observers, &ExitKind::Timeout) + .expect("In timeout handler objective failure."); + if interesting { state .solutions_mut() .add(Testcase::new(input.clone())) @@ -380,7 +370,11 @@ mod unix_signal_handler { } } - pub unsafe fn inproc_crash_handler( + /// Crash-Handler for in-process fuzzing. + /// Will be used for signal handling. + /// It will store the current State to shmem, then exit. + #[allow(clippy::too_many_lines)] + pub unsafe fn inproc_crash_handler( _signal: Signal, _info: siginfo_t, _context: &mut ucontext_t, @@ -389,8 +383,8 @@ mod unix_signal_handler { EM: EventManager, OT: ObserversTuple, OC: Corpus, - OFT: FeedbacksTuple, - S: HasObjectives + HasSolutions, + OF: Feedback, + S: HasObjective + HasSolutions, I: Input + HasTargetBytes, { #[cfg(all(target_os = "android", target_arch = "aarch64"))] @@ -452,11 +446,11 @@ mod unix_signal_handler { // Make sure we don't crash in the crash handler forever. data.current_input_ptr = ptr::null(); - let obj_fitness = state - .objectives_mut() - .is_interesting_all(&input, observers, &ExitKind::Crash) - .expect("In crash handler objectives failure."); - if obj_fitness > 0 { + let interesting = state + .objective_mut() + .is_interesting(&input, observers, &ExitKind::Crash) + .expect("In crash handler objective failure."); + if interesting { let new_input = input.clone(); state .solutions_mut() @@ -534,10 +528,10 @@ mod windows_exception_handler { corpus::{Corpus, Testcase}, events::{Event, EventManager}, executors::ExitKind, - feedbacks::FeedbacksTuple, + feedbacks::Feedback, inputs::{HasTargetBytes, Input}, observers::ObserversTuple, - state::{HasObjectives, HasSolutions}, + state::{HasObjective, HasSolutions}, }; /// Signal handling on unix systems needs some nasty unsafe. @@ -588,7 +582,7 @@ mod windows_exception_handler { } } - pub unsafe fn inproc_crash_handler( + pub unsafe fn inproc_crash_handler( code: ExceptionCode, exception_pointers: *mut EXCEPTION_POINTERS, data: &mut InProcessExecutorHandlerData, @@ -596,8 +590,8 @@ mod windows_exception_handler { EM: EventManager, OT: ObserversTuple, OC: Corpus, - OFT: FeedbacksTuple, - S: HasObjectives + HasSolutions, + OF: Feedback, + S: HasObjective + HasSolutions, I: Input + HasTargetBytes, { #[cfg(feature = "std")] @@ -616,11 +610,11 @@ mod windows_exception_handler { // Make sure we don't crash in the crash handler forever. data.current_input_ptr = ptr::null(); - let obj_fitness = state - .objectives_mut() - .is_interesting_all(&input, observers, &ExitKind::Crash) - .expect("In crash handler objectives failure."); - if obj_fitness > 0 { + let interesting = state + .objective_mut() + .is_interesting(&input, observers, &ExitKind::Crash) + .expect("In crash handler objective failure."); + if interesting { let new_input = input.clone(); state .solutions_mut() @@ -692,10 +686,9 @@ mod tests { fn test_inmem_exec() { let mut harness = |_buf: &[u8]| ExitKind::Ok; - let mut in_process_executor = InProcessExecutor::<_, NopInput, ()> { + let mut in_process_executor = InProcessExecutor::<(), _, NopInput, (), ()> { harness_fn: &mut harness, observers: tuple_list!(), - name: "main", phantom: PhantomData, }; let mut input = NopInput {}; diff --git a/libafl/src/executors/mod.rs b/libafl/src/executors/mod.rs index 08b26ed602..b79dc5b259 100644 --- a/libafl/src/executors/mod.rs +++ b/libafl/src/executors/mod.rs @@ -8,8 +8,7 @@ pub use timeout::TimeoutExecutor; use core::marker::PhantomData; use crate::{ - bolts::{serdeany::SerdeAny, tuples::Named}, - events::EventManager, + bolts::serdeany::SerdeAny, inputs::{HasTargetBytes, Input}, observers::ObserversTuple, Error, @@ -29,6 +28,57 @@ pub enum ExitKind { Custom(Box), } +/// Pre and post exec hooks +pub trait HasExecHooks { + /// Called right before exexution starts + #[inline] + fn pre_exec(&mut self, _state: &mut S, _mgr: &mut EM, _input: &I) -> Result<(), Error> { + Ok(()) + } + + /// Called right after execution finished. + #[inline] + fn post_exec(&mut self, _state: &mut S, _mgr: &mut EM, _input: &I) -> Result<(), Error> { + Ok(()) + } +} + +/// A haskell-style tuple of objects that have pre and post exec hooks +pub trait HasExecHooksTuple { + /// This is called right before the next execution. + fn pre_exec_all(&mut self, state: &mut S, mgr: &mut EM, input: &I) -> Result<(), Error>; + + /// This is called right after the last execution + fn post_exec_all(&mut self, state: &mut S, mgr: &mut EM, input: &I) -> Result<(), Error>; +} + +impl HasExecHooksTuple for () { + fn pre_exec_all(&mut self, _state: &mut S, _mgr: &mut EM, _input: &I) -> Result<(), Error> { + Ok(()) + } + + fn post_exec_all(&mut self, _state: &mut S, _mgr: &mut EM, _input: &I) -> Result<(), Error> { + Ok(()) + } +} + +impl HasExecHooksTuple for (Head, Tail) +where + Head: HasExecHooks, + Tail: HasExecHooksTuple, +{ + fn pre_exec_all(&mut self, state: &mut S, mgr: &mut EM, input: &I) -> Result<(), Error> { + self.0.pre_exec(state, mgr, input)?; + self.1.pre_exec_all(state, mgr, input) + } + + fn post_exec_all(&mut self, state: &mut S, mgr: &mut EM, input: &I) -> Result<(), Error> { + self.0.post_exec(state, mgr, input)?; + self.1.post_exec_all(state, mgr, input) + } +} + +/// Holds a tuple of Observers pub trait HasObservers where OT: ObserversTuple, @@ -38,27 +88,41 @@ where /// Get the linked observers fn observers_mut(&mut self) -> &mut OT; +} - /// Reset the state of all the observes linked to this executor +/// Execute the exec hooks of the observers if they all implement HasExecHooks +pub trait HasObserversHooks: HasObservers +where + OT: ObserversTuple + HasExecHooksTuple, +{ #[inline] - fn pre_exec_observers(&mut self) -> Result<(), Error> { - self.observers_mut().pre_exec_all() + fn pre_exec_observers(&mut self, state: &mut S, mgr: &mut EM, input: &I) -> Result<(), Error> { + self.observers_mut().pre_exec_all(state, mgr, input) } /// Run the post exec hook for all the observes linked to this executor #[inline] - fn post_exec_observers(&mut self) -> Result<(), Error> { - self.observers_mut().post_exec_all() + fn post_exec_observers(&mut self, state: &mut S, mgr: &mut EM, input: &I) -> Result<(), Error> { + self.observers_mut().post_exec_all(state, mgr, input) } } +/// An executor takes the given inputs, and runs the harness/target. +pub trait Executor +where + I: Input, +{ + /// Instruct the target about the input and run + fn run_target(&mut self, input: &I) -> Result; +} + /// A simple executor that does nothing. /// If intput len is 0, `run_target` will return Err -struct NopExecutor { - phantom: PhantomData, +struct NopExecutor { + phantom: PhantomData<(EM, I, S)>, } -impl Executor for NopExecutor +impl Executor for NopExecutor where I: Input + HasTargetBytes, { @@ -71,48 +135,7 @@ where } } -impl Named for NopExecutor { - fn name(&self) -> &str { - &"NopExecutor" - } -} - -/// An executor takes the given inputs, and runs the harness/target. -pub trait Executor: Named -where - I: Input, -{ - /// Called right before exexution starts - #[inline] - fn pre_exec( - &mut self, - _state: &mut S, - _event_mgr: &mut EM, - _input: &I, - ) -> Result<(), Error> - where - EM: EventManager, - { - Ok(()) - } - - /// Called right after execution finished. - #[inline] - fn post_exec( - &mut self, - _state: &mut S, - _event_mgr: &mut EM, - _input: &I, - ) -> Result<(), Error> - where - EM: EventManager, - { - Ok(()) - } - - /// Instruct the target about the input and run - fn run_target(&mut self, input: &I) -> Result; -} +impl HasExecHooks for NopExecutor where I: Input + HasTargetBytes {} #[cfg(test)] mod test { @@ -125,7 +148,7 @@ mod test { fn nop_executor() { let empty_input = BytesInput::new(vec![]); let nonempty_input = BytesInput::new(vec![1u8]); - let mut executor = NopExecutor { + let mut executor = NopExecutor::<(), _, ()> { phantom: PhantomData, }; assert!(executor.run_target(&empty_input).is_err()); diff --git a/libafl/src/executors/timeout.rs b/libafl/src/executors/timeout.rs index 0b428aa855..cc90cfabe3 100644 --- a/libafl/src/executors/timeout.rs +++ b/libafl/src/executors/timeout.rs @@ -3,10 +3,10 @@ use core::{marker::PhantomData, time::Duration}; use crate::{ - bolts::tuples::Named, - events::EventManager, - executors::{Executor, ExitKind, HasObservers}, - inputs::{HasTargetBytes, Input}, + executors::{ + Executor, ExitKind, HasExecHooks, HasExecHooksTuple, HasObservers, HasObserversHooks, + }, + inputs::Input, observers::ObserversTuple, Error, }; @@ -39,32 +39,48 @@ extern "C" { const ITIMER_REAL: c_int = 0; /// The timeout excutor is a wrapper that set a timeout before each run -pub struct TimeoutExecutor +pub struct TimeoutExecutor where - E: Executor + HasObservers, - I: Input + HasTargetBytes, - OT: ObserversTuple, + E: Executor, + I: Input, { executor: E, exec_tmout: Duration, - phantom: PhantomData<(I, OT)>, + phantom: PhantomData, } -impl Named for TimeoutExecutor +impl TimeoutExecutor where - E: Executor + HasObservers, - I: Input + HasTargetBytes, - OT: ObserversTuple, + E: Executor, + I: Input, { - fn name(&self) -> &str { - self.executor.name() + pub fn new(executor: E, exec_tmout: Duration) -> Self { + Self { + executor, + exec_tmout, + phantom: PhantomData, + } + } + + pub fn inner(&mut self) -> &mut E { + &mut self.executor } } -impl HasObservers for TimeoutExecutor +impl Executor for TimeoutExecutor +where + E: Executor, + I: Input, +{ + fn run_target(&mut self, input: &I) -> Result { + self.executor.run_target(input) + } +} + +impl HasObservers for TimeoutExecutor where E: Executor + HasObservers, - I: Input + HasTargetBytes, + I: Input, OT: ObserversTuple, { #[inline] @@ -78,38 +94,21 @@ where } } -impl TimeoutExecutor +impl HasObserversHooks for TimeoutExecutor where E: Executor + HasObservers, - I: Input + HasTargetBytes, - OT: ObserversTuple, + I: Input, + OT: ObserversTuple + HasExecHooksTuple, { - pub fn new(executor: E, exec_tmout: Duration) -> Self { - Self { - executor, - exec_tmout, - phantom: PhantomData, - } - } - - pub fn inner(&mut self) -> &mut E { - &mut self.executor - } } -impl Executor for TimeoutExecutor +impl HasExecHooks for TimeoutExecutor where - E: Executor + HasObservers, - I: Input + HasTargetBytes, - OT: ObserversTuple, + E: Executor + HasExecHooks, + I: Input, { #[inline] - fn pre_exec, S>( - &mut self, - _state: &mut S, - _event_mgr: &mut EM, - _input: &I, - ) -> Result<(), Error> { + fn pre_exec(&mut self, state: &mut S, mgr: &mut EM, input: &I) -> Result<(), Error> { #[cfg(unix)] unsafe { let milli_sec = self.exec_tmout.as_millis(); @@ -135,16 +134,11 @@ where // TODO let _ = self.exec_tmout.as_millis(); } - self.executor.pre_exec(_state, _event_mgr, _input) + self.executor.pre_exec(state, mgr, input) } #[inline] - fn post_exec, S>( - &mut self, - _state: &mut S, - _event_mgr: &mut EM, - _input: &I, - ) -> Result<(), Error> { + fn post_exec(&mut self, state: &mut S, mgr: &mut EM, input: &I) -> Result<(), Error> { #[cfg(unix)] unsafe { let it_value = Timeval { @@ -168,10 +162,6 @@ where { // TODO } - self.executor.post_exec(_state, _event_mgr, _input) - } - - fn run_target(&mut self, input: &I) -> Result { - self.executor.run_target(input) + self.executor.post_exec(state, mgr, input) } } diff --git a/libafl/src/feedbacks/map.rs b/libafl/src/feedbacks/map.rs index d17323b0d7..cc21332783 100644 --- a/libafl/src/feedbacks/map.rs +++ b/libafl/src/feedbacks/map.rs @@ -14,14 +14,14 @@ use crate::{ executors::ExitKind, feedbacks::Feedback, inputs::Input, - observers::{MapObserver, Observer, ObserversTuple}, + observers::{MapObserver, ObserversTuple}, state::HasMetadata, utils::AsSlice, Error, }; -pub type MaxMapFeedback = MapFeedback, O>; -pub type MinMapFeedback = MapFeedback, O>; +pub type MaxMapFeedback = MapFeedback; +pub type MinMapFeedback = MapFeedback; /// A Reducer function is used to aggregate values for the novelty search pub trait Reducer: Serialize + serde::de::DeserializeOwned + 'static @@ -32,14 +32,9 @@ where } #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct MaxReducer -where - T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, -{ - phantom: PhantomData, -} +pub struct MaxReducer {} -impl Reducer for MaxReducer +impl Reducer for MaxReducer where T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, { @@ -54,14 +49,9 @@ where } #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct MinReducer -where - T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, -{ - phantom: PhantomData, -} +pub struct MinReducer {} -impl Reducer for MinReducer +impl Reducer for MinReducer where T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, { @@ -119,7 +109,7 @@ impl MapNoveltiesMetadata { /// The most common AFL-like feedback type #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(bound = "T: serde::de::DeserializeOwned")] -pub struct MapFeedback +pub struct MapFeedback where T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, R: Reducer, @@ -137,26 +127,23 @@ where phantom: PhantomData<(R, O)>, } -impl Feedback for MapFeedback +impl Feedback for MapFeedback where - T: Integer - + Default - + Copy - + 'static - + serde::Serialize - + serde::de::DeserializeOwned - + core::fmt::Debug, + T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, R: Reducer, O: MapObserver, I: Input, { - fn is_interesting( + fn is_interesting( &mut self, _input: &I, observers: &OT, _exit_kind: &ExitKind, - ) -> Result { - let mut interesting = 0; + ) -> Result + where + OT: ObserversTuple, + { + let mut interesting = false; // TODO optimize let observer = observers.match_name_type::(&self.name).unwrap(); let size = observer.usable_count(); @@ -170,7 +157,7 @@ where let reduced = R::reduce(history, item); if history != reduced { self.history_map[i] = reduced; - interesting += 1; + interesting = true; } } } else if self.indexes.is_some() && self.novelties.is_none() { @@ -184,7 +171,7 @@ where let reduced = R::reduce(history, item); if history != reduced { self.history_map[i] = reduced; - interesting += 1; + interesting = true; } } } else if self.indexes.is_none() && self.novelties.is_some() { @@ -195,7 +182,7 @@ where let reduced = R::reduce(history, item); if history != reduced { self.history_map[i] = reduced; - interesting += 1; + interesting = true; self.novelties.as_mut().unwrap().push(i); } } @@ -210,7 +197,7 @@ where let reduced = R::reduce(history, item); if history != reduced { self.history_map[i] = reduced; - interesting += 1; + interesting = true; self.novelties.as_mut().unwrap().push(i); } } @@ -243,7 +230,7 @@ where } } -impl Named for MapFeedback +impl Named for MapFeedback where T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, R: Reducer, @@ -255,11 +242,11 @@ where } } -impl MapFeedback +impl MapFeedback where T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, R: Reducer, - O: MapObserver + Observer, + O: MapObserver, { /// Create new `MapFeedback` pub fn new(name: &'static str, map_size: usize) -> Self { @@ -315,7 +302,7 @@ where } } -impl MapFeedback +impl MapFeedback where T: Integer + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, R: Reducer, diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 6cb8261383..a50d397534 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -15,22 +15,24 @@ use crate::{ Error, }; -use core::time::Duration; +use core::{marker::PhantomData, time::Duration}; /// Feedbacks evaluate the observers. /// Basically, they reduce the information provided by an observer to a value, /// indicating the "interestingness" of the last run. -pub trait Feedback: Named + serde::Serialize + serde::de::DeserializeOwned + 'static +pub trait Feedback: Named + serde::Serialize + serde::de::DeserializeOwned where I: Input, { /// `is_interesting ` should return the "Interestingness" from 0 to 255 (percent times 2.55) - fn is_interesting( + fn is_interesting( &mut self, input: &I, observers: &OT, exit_kind: &ExitKind, - ) -> Result; + ) -> Result + where + OT: ObserversTuple; /// Append to the testcase the generated metadata in case of a new corpus item #[inline] @@ -45,74 +47,239 @@ where } } -pub trait FeedbacksTuple: serde::Serialize + serde::de::DeserializeOwned +/// Compose feedbacks with an AND operation +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(bound = "I: serde::de::DeserializeOwned")] +pub struct AndFeedback +where + A: Feedback, + B: Feedback, + I: Input, +{ + pub first: A, + pub second: B, + phantom: PhantomData, +} + +impl Feedback for AndFeedback where + A: Feedback, + B: Feedback, I: Input, { - /// Get the total interestingness value from all feedbacks - fn is_interesting_all( + fn is_interesting( &mut self, input: &I, observers: &OT, exit_kind: &ExitKind, - ) -> Result; + ) -> Result + where + OT: ObserversTuple, + { + let a = self.first.is_interesting(input, observers, exit_kind)?; + let b = self.second.is_interesting(input, observers, exit_kind)?; + Ok(a && b) + } +} - /// Write metadata for this testcase - fn append_metadata_all(&mut self, testcase: &mut Testcase) -> Result<(), Error>; +impl Named for AndFeedback +where + A: Feedback, + B: Feedback, + I: Input, +{ + #[inline] + fn name(&self) -> &str { + //format!("And({}, {})", self.first.name(), self.second.name()) + "AndFeedback" + } +} + +impl AndFeedback +where + A: Feedback, + B: Feedback, + I: Input, +{ + pub fn new(first: A, second: B) -> Self { + Self { + first, + second, + phantom: PhantomData, + } + } +} - /// Discards metadata - the end of this input's execution - fn discard_metadata_all(&mut self, input: &I) -> Result<(), Error>; +/// Compose feedbacks with an OR operation +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(bound = "I: serde::de::DeserializeOwned")] +pub struct OrFeedback +where + A: Feedback, + B: Feedback, + I: Input, +{ + pub first: A, + pub second: B, + phantom: PhantomData, } -impl FeedbacksTuple for () +impl Feedback for OrFeedback where + A: Feedback, + B: Feedback, I: Input, { - #[inline] - fn is_interesting_all( + fn is_interesting( &mut self, - _: &I, - _: &OT, - _: &ExitKind, - ) -> Result { - Ok(0) + input: &I, + observers: &OT, + exit_kind: &ExitKind, + ) -> Result + where + OT: ObserversTuple, + { + let a = self.first.is_interesting(input, observers, exit_kind)?; + let b = self.second.is_interesting(input, observers, exit_kind)?; + Ok(a || b) } +} +impl Named for OrFeedback +where + A: Feedback, + B: Feedback, + I: Input, +{ #[inline] - fn append_metadata_all(&mut self, _testcase: &mut Testcase) -> Result<(), Error> { - Ok(()) + fn name(&self) -> &str { + //format!("Or({}, {})", self.first.name(), self.second.name()) + "OrFeedback" } +} - #[inline] - fn discard_metadata_all(&mut self, _input: &I) -> Result<(), Error> { - Ok(()) +impl OrFeedback +where + A: Feedback, + B: Feedback, + I: Input, +{ + pub fn new(first: A, second: B) -> Self { + Self { + first, + second, + phantom: PhantomData, + } } } -impl FeedbacksTuple for (Head, Tail) +/// Compose feedbacks with an OR operation +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(bound = "I: serde::de::DeserializeOwned")] +pub struct NotFeedback where - Head: Feedback, - Tail: FeedbacksTuple, + A: Feedback, I: Input, { - fn is_interesting_all( + pub first: A, + phantom: PhantomData, +} + +impl Feedback for NotFeedback +where + A: Feedback, + I: Input, +{ + fn is_interesting( &mut self, input: &I, observers: &OT, exit_kind: &ExitKind, - ) -> Result { - Ok(self.0.is_interesting(input, observers, exit_kind)? - + self.1.is_interesting_all(input, observers, exit_kind)?) + ) -> Result + where + OT: ObserversTuple, + { + Ok(!self.first.is_interesting(input, observers, exit_kind)?) } +} - fn append_metadata_all(&mut self, testcase: &mut Testcase) -> Result<(), Error> { - self.0.append_metadata(testcase)?; - self.1.append_metadata_all(testcase) +impl Named for NotFeedback +where + A: Feedback, + I: Input, +{ + #[inline] + fn name(&self) -> &str { + //format!("Not({})", self.first.name()) + "NotFeedback" + } +} + +impl NotFeedback +where + A: Feedback, + I: Input, +{ + pub fn new(first: A) -> Self { + Self { + first, + phantom: PhantomData, + } } +} - fn discard_metadata_all(&mut self, input: &I) -> Result<(), Error> { - self.0.discard_metadata(input)?; - self.1.discard_metadata_all(input) +/// Variadic macro to create a chain of AndFeedback +#[macro_export] +macro_rules! feedback_and { + ( $last:expr ) => { $last }; + + ( $head:expr, $($tail:expr), +) => { + // recursive call + $crate::feedbacks::AndFeedback::new($head , feedback_and!($($tail),+)) + }; +} + +/// Variadic macro to create a chain of OrFeedback +#[macro_export] +macro_rules! feedback_or { + ( $last:expr ) => { $last }; + + ( $head:expr, $($tail:expr), +) => { + // recursive call + $crate::feedbacks::OrFeedback::new($head , feedback_or!($($tail),+)) + }; +} + +/// Variadic macro to create a NotFeedback +#[macro_export] +macro_rules! feedback_not { + ( $last:expr ) => { + $crate::feedbacks::NotFeedback::new($last) + }; +} + +/// Hack to use () as empty Feedback +impl Feedback for () +where + I: Input, +{ + fn is_interesting( + &mut self, + _input: &I, + _observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + OT: ObserversTuple, + { + Ok(false) + } +} + +impl Named for () { + #[inline] + fn name(&self) -> &str { + "Empty" } } @@ -124,16 +291,19 @@ impl Feedback for CrashFeedback where I: Input, { - fn is_interesting( + fn is_interesting( &mut self, _input: &I, _observers: &OT, exit_kind: &ExitKind, - ) -> Result { + ) -> Result + where + OT: ObserversTuple, + { if let ExitKind::Crash = exit_kind { - Ok(1) + Ok(true) } else { - Ok(0) + Ok(false) } } } @@ -164,16 +334,19 @@ impl Feedback for TimeoutFeedback where I: Input, { - fn is_interesting( + fn is_interesting( &mut self, _input: &I, _observers: &OT, exit_kind: &ExitKind, - ) -> Result { + ) -> Result + where + OT: ObserversTuple, + { if let ExitKind::Timeout = exit_kind { - Ok(1) + Ok(true) } else { - Ok(0) + Ok(false) } } } @@ -198,6 +371,7 @@ impl Default for TimeoutFeedback { } /// Nop feedback that annotates execution time in the new testcase, if any +/// For this Feedback, the testcase is never interesting (use with an OR) #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TimeFeedback { exec_time: Option, @@ -207,15 +381,18 @@ impl Feedback for TimeFeedback where I: Input, { - fn is_interesting( + fn is_interesting( &mut self, _input: &I, observers: &OT, _exit_kind: &ExitKind, - ) -> Result { + ) -> Result + where + OT: ObserversTuple, + { let observer = observers.match_first_type::().unwrap(); self.exec_time = *observer.last_runtime(); - Ok(0) + Ok(false) } /// Append to the testcase the generated metadata in case of a new corpus item diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index 421506380d..bf7037bba0 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -194,7 +194,6 @@ mod tests { let mut harness = |_buf: &[u8]| ExitKind::Ok; let mut executor = InProcessExecutor::new( - "main", &mut harness, tuple_list!(), //Box::new(|_, _, _, _, _| ()), diff --git a/libafl/src/observers/map.rs b/libafl/src/observers/map.rs index 2188105f55..169cac1206 100644 --- a/libafl/src/observers/map.rs +++ b/libafl/src/observers/map.rs @@ -11,6 +11,7 @@ use crate::{ ownedref::{OwnedArrayPtrMut, OwnedPtr}, tuples::Named, }, + executors::HasExecHooks, observers::Observer, Error, }; @@ -68,12 +69,18 @@ where name: String, } -impl Observer for StdMapObserver +impl Observer for StdMapObserver where + T: Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned +{ +} + +impl HasExecHooks for StdMapObserver where T: Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, + Self: MapObserver, { #[inline] - fn pre_exec(&mut self) -> Result<(), Error> { + fn pre_exec(&mut self, _state: &mut S, _mgr: &mut EM, _input: &I) -> Result<(), Error> { self.reset_map() } } @@ -170,12 +177,17 @@ where name: String, } -impl Observer for VariableMapObserver +impl Observer for VariableMapObserver where + T: Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned +{ +} + +impl HasExecHooks for VariableMapObserver where T: Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, { #[inline] - fn pre_exec(&mut self) -> Result<(), Error> { + fn pre_exec(&mut self, _state: &mut S, _mgr: &mut EM, _input: &I) -> Result<(), Error> { self.reset_map() } } @@ -264,7 +276,7 @@ where #[serde(bound = "M: serde::de::DeserializeOwned")] pub struct HitcountsMapObserver where - M: MapObserver, + M: serde::Serialize + serde::de::DeserializeOwned, { base: M, } @@ -284,27 +296,29 @@ static COUNT_CLASS_LOOKUP: [u8; 256] = [ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ]; -impl Observer for HitcountsMapObserver +impl Observer for HitcountsMapObserver where M: MapObserver {} + +impl HasExecHooks for HitcountsMapObserver where - M: MapObserver, + M: MapObserver + HasExecHooks, { #[inline] - fn pre_exec(&mut self) -> Result<(), Error> { - self.reset_map() + fn pre_exec(&mut self, state: &mut S, mgr: &mut EM, input: &I) -> Result<(), Error> { + self.base.pre_exec(state, mgr, input) } #[inline] - fn post_exec(&mut self) -> Result<(), Error> { + fn post_exec(&mut self, state: &mut S, mgr: &mut EM, input: &I) -> Result<(), Error> { for x in self.map_mut().iter_mut() { *x = COUNT_CLASS_LOOKUP[*x as usize]; } - Ok(()) + self.base.post_exec(state, mgr, input) } } impl Named for HitcountsMapObserver where - M: MapObserver, + M: Named + serde::Serialize + serde::de::DeserializeOwned, { #[inline] fn name(&self) -> &str { @@ -349,7 +363,7 @@ where impl HitcountsMapObserver where - M: MapObserver, + M: serde::Serialize + serde::de::DeserializeOwned, { /// Creates a new MapObserver pub fn new(base: M) -> Self { diff --git a/libafl/src/observers/mod.rs b/libafl/src/observers/mod.rs index 2ff66ca5fc..ef81ccd012 100644 --- a/libafl/src/observers/mod.rs +++ b/libafl/src/observers/mod.rs @@ -3,15 +3,13 @@ pub mod map; pub use map::*; -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; +use alloc::string::{String, ToString}; use core::time::Duration; use serde::{Deserialize, Serialize}; use crate::{ bolts::tuples::{MatchFirstType, MatchNameAndType, MatchType, Named}, + executors::HasExecHooks, utils::current_time, Error, }; @@ -20,74 +18,26 @@ use crate::{ /// They can then be used by various sorts of feedback. pub trait Observer: Named + serde::Serialize + serde::de::DeserializeOwned + 'static { /// The testcase finished execution, calculate any changes. + /// Reserved for future use. #[inline] fn flush(&mut self) -> Result<(), Error> { Ok(()) } - - /// Resets the observer - fn pre_exec(&mut self) -> Result<(), Error>; - - /// This function is executed after each fuzz run - #[inline] - fn post_exec(&mut self) -> Result<(), Error> { - Ok(()) - } - - /// Serialize this observer's state only, to be restored later using deserialize_state - /// As opposed to completely serializing the observer, this is only needed when the fuzzer is to be restarted - /// If no state is needed to be kept, just return an empty vec. - /// Example: - /// >> The virgin_bits map in AFL needs to be in sync with the corpus - #[inline] - fn serialize_state(&mut self) -> Result, Error> { - Ok(vec![]) - } - - /// Restore the state from a given vec, priviously stored using `serialize_state` - #[inline] - fn deserialize_state(&mut self, serialized_state: &[u8]) -> Result<(), Error> { - let _ = serialized_state; - Ok(()) - } } -/// A hastkel-style tuple of observers +/// A haskell-style tuple of observers pub trait ObserversTuple: MatchNameAndType + MatchType + MatchFirstType + serde::Serialize + serde::de::DeserializeOwned { - /// Reset all executors in the tuple - /// This is called right before the next execution. - fn pre_exec_all(&mut self) -> Result<(), Error>; - - /// Do whatever you need to do after a run. - /// This is called right after the last execution - fn post_exec_all(&mut self) -> Result<(), Error>; } -impl ObserversTuple for () { - fn pre_exec_all(&mut self) -> Result<(), Error> { - Ok(()) - } - fn post_exec_all(&mut self) -> Result<(), Error> { - Ok(()) - } -} +impl ObserversTuple for () {} impl ObserversTuple for (Head, Tail) where Head: Observer, Tail: ObserversTuple, { - fn pre_exec_all(&mut self) -> Result<(), Error> { - self.0.pre_exec()?; - self.1.pre_exec_all() - } - - fn post_exec_all(&mut self) -> Result<(), Error> { - self.0.post_exec()?; - self.1.post_exec_all() - } } /// A simple observer, just overlooking the runtime of the target. @@ -113,14 +63,16 @@ impl TimeObserver { } } -impl Observer for TimeObserver { - fn pre_exec(&mut self) -> Result<(), Error> { +impl Observer for TimeObserver {} + +impl HasExecHooks for TimeObserver { + fn pre_exec(&mut self, _state: &mut S, _mgr: &mut EM, _input: &I) -> Result<(), Error> { self.last_runtime = None; self.start_time = current_time(); Ok(()) } - fn post_exec(&mut self) -> Result<(), Error> { + fn post_exec(&mut self, _state: &mut S, _mgr: &mut EM, _input: &I) -> Result<(), Error> { self.last_runtime = Some(current_time() - self.start_time); Ok(()) } diff --git a/libafl/src/stages/mutational.rs b/libafl/src/stages/mutational.rs index 0809f938d8..1af825afe2 100644 --- a/libafl/src/stages/mutational.rs +++ b/libafl/src/stages/mutational.rs @@ -3,7 +3,7 @@ use core::marker::PhantomData; use crate::{ corpus::{Corpus, CorpusScheduler}, events::EventManager, - executors::{Executor, HasObservers}, + executors::{Executor, HasExecHooks, HasExecHooksTuple, HasObservers, HasObserversHooks}, inputs::Input, mutators::Mutator, observers::ObserversTuple, @@ -25,8 +25,8 @@ where S: HasCorpus + Evaluator, C: Corpus, EM: EventManager, - E: Executor + HasObservers, - OT: ObserversTuple, + E: Executor + HasObservers + HasExecHooks + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, CS: CorpusScheduler, { /// The mutator registered for this stage @@ -39,7 +39,7 @@ where fn iterations(&self, state: &mut S) -> usize; /// Runs this (mutational) stage for the given testcase - #[allow(clippy::clippy::cast_possible_wrap)] // more than i32 stages on 32 bit system - highly unlikely... + #[allow(clippy::cast_possible_wrap)] // more than i32 stages on 32 bit system - highly unlikely... fn perform_mutational( &mut self, state: &mut S, @@ -76,8 +76,8 @@ where S: HasCorpus + Evaluator + HasRand, C: Corpus, EM: EventManager, - E: Executor + HasObservers, - OT: ObserversTuple, + E: Executor + HasObservers + HasExecHooks + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, CS: CorpusScheduler, R: Rand, { @@ -94,8 +94,8 @@ where S: HasCorpus + Evaluator + HasRand, C: Corpus, EM: EventManager, - E: Executor + HasObservers, - OT: ObserversTuple, + E: Executor + HasObservers + HasExecHooks + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, CS: CorpusScheduler, R: Rand, { @@ -125,8 +125,8 @@ where S: HasCorpus + Evaluator + HasRand, C: Corpus, EM: EventManager, - E: Executor + HasObservers, - OT: ObserversTuple, + E: Executor + HasObservers + HasExecHooks + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, CS: CorpusScheduler, R: Rand, { @@ -150,8 +150,8 @@ where S: HasCorpus + Evaluator + HasRand, C: Corpus, EM: EventManager, - E: Executor + HasObservers, - OT: ObserversTuple, + E: Executor + HasObservers + HasExecHooks + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, CS: CorpusScheduler, R: Rand, { diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index cd295ff9ea..5f2876d45d 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -12,8 +12,10 @@ use crate::{ bolts::serdeany::{SerdeAny, SerdeAnyMap}, corpus::{Corpus, CorpusScheduler, Testcase}, events::{Event, EventManager, LogSeverity}, - executors::{Executor, ExitKind, HasObservers}, - feedbacks::FeedbacksTuple, + executors::{ + Executor, ExitKind, HasExecHooks, HasExecHooksTuple, HasObservers, HasObserversHooks, + }, + feedbacks::Feedback, generators::Generator, inputs::Input, observers::ObserversTuple, @@ -96,49 +98,30 @@ pub trait HasMetadata { } } -/// Trait for elements offering a feedbacks tuple -pub trait HasFeedbacks +/// Trait for elements offering a feedback +pub trait HasFeedback: Sized where - FT: FeedbacksTuple, + F: Feedback, I: Input, { - /// The feedbacks tuple - fn feedbacks(&self) -> &FT; + /// The feedback + fn feedback(&self) -> &F; - /// The feedbacks tuple (mut) - fn feedbacks_mut(&mut self) -> &mut FT; - - /// Resets all metadata holds by feedbacks - #[inline] - fn discard_feedbacks_metadata(&mut self, input: &I) -> Result<(), Error> { - // TODO: This could probably be automatic in the feedback somehow? - self.feedbacks_mut().discard_metadata_all(&input) - } - - /// Creates a new testcase, appending the metadata from each feedback - #[inline] - fn testcase_with_feedbacks_metadata( - &mut self, - input: I, - fitness: u32, - ) -> Result, Error> { - let mut testcase = Testcase::with_fitness(input, fitness); - self.feedbacks_mut().append_metadata_all(&mut testcase)?; - Ok(testcase) - } + /// The feedback (mut) + fn feedback_mut(&mut self) -> &mut F; } -/// Trait for elements offering an objective feedbacks tuple -pub trait HasObjectives +/// Trait for elements offering an objective feedback tuple +pub trait HasObjective: Sized where - FT: FeedbacksTuple, + OF: Feedback, I: Input, { - /// The objective feedbacks tuple - fn objectives(&self) -> &FT; + /// The objective feedback + fn objective(&self) -> &OF; - /// The objective feedbacks tuple (mut) - fn objectives_mut(&mut self) -> &mut FT; + /// The objective feedback (mut) + fn objective_mut(&mut self) -> &mut OF; } /// Trait for the execution counter @@ -160,7 +143,7 @@ pub trait HasStartTime { } /// Add to the state if interesting -pub trait IfInteresting +pub trait IfInteresting: Sized where I: Input, { @@ -170,7 +153,7 @@ where input: &I, observers: &OT, exit_kind: &ExitKind, - ) -> Result + ) -> Result where OT: ObserversTuple; @@ -178,7 +161,7 @@ where fn add_if_interesting( &mut self, input: &I, - fitness: u32, + is_interesting: bool, scheduler: &CS, ) -> Result, Error> where @@ -186,7 +169,7 @@ where Self: Sized; } -/// Evaluate an input modyfing the state of the fuzzer and returning a fitness +/// Evaluate an input modyfing the state of the fuzzer pub trait Evaluator: Sized where I: Input, @@ -198,25 +181,28 @@ where executor: &mut E, manager: &mut EM, scheduler: &CS, - ) -> Result<(u32, Option), Error> + ) -> Result<(bool, Option), Error> where - E: Executor + HasObservers, - OT: ObserversTuple, + E: Executor + + HasObservers + + HasExecHooks + + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, EM: EventManager, CS: CorpusScheduler; } /// The state a fuzz run. #[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(bound = "FT: serde::de::DeserializeOwned")] -pub struct State +#[serde(bound = "F: serde::de::DeserializeOwned")] +pub struct State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { /// RNG instance rand: R, @@ -227,11 +213,11 @@ where /// The corpus corpus: C, /// Feedbacks used to evaluate an input - feedbacks: FT, + feedback: F, // Solutions corpus solutions: SC, /// Objective Feedbacks - objectives: OFT, + objective: OF, /// Metadata stored for this state by one of the components metadata: SerdeAnyMap, /// MaxSize testcase size for mutators that appreciate it @@ -240,14 +226,14 @@ where phantom: PhantomData, } -impl HasRand for State +impl HasRand for State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { /// The rand instance #[inline] @@ -262,14 +248,14 @@ where } } -impl HasCorpus for State +impl HasCorpus for State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { /// Returns the corpus #[inline] @@ -284,14 +270,14 @@ where } } -impl HasSolutions for State +impl HasSolutions for State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { /// Returns the solutions corpus #[inline] @@ -306,14 +292,14 @@ where } } -impl HasMetadata for State +impl HasMetadata for State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { /// Get all the metadata into an HashMap #[inline] @@ -328,58 +314,58 @@ where } } -impl HasFeedbacks for State +impl HasFeedback for State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { - /// The feedbacks tuple + /// The feedback #[inline] - fn feedbacks(&self) -> &FT { - &self.feedbacks + fn feedback(&self) -> &F { + &self.feedback } - /// The feedbacks tuple (mut) + /// The feedback (mut) #[inline] - fn feedbacks_mut(&mut self) -> &mut FT { - &mut self.feedbacks + fn feedback_mut(&mut self) -> &mut F { + &mut self.feedback } } -impl HasObjectives for State +impl HasObjective for State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { - /// The objective feedbacks tuple + /// The objective feedback #[inline] - fn objectives(&self) -> &OFT { - &self.objectives + fn objective(&self) -> &OF { + &self.objective } - /// The objective feedbacks tuple (mut) + /// The objective feedback (mut) #[inline] - fn objectives_mut(&mut self) -> &mut OFT { - &mut self.objectives + fn objective_mut(&mut self) -> &mut OF { + &mut self.objective } } -impl HasExecutions for State +impl HasExecutions for State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { /// The executions counter #[inline] @@ -394,14 +380,14 @@ where } } -impl HasMaxSize for State +impl HasMaxSize for State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { fn max_size(&self) -> usize { self.max_size @@ -412,14 +398,14 @@ where } } -impl HasStartTime for State +impl HasStartTime for State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { /// The starting time #[inline] @@ -434,14 +420,14 @@ where } } -impl IfInteresting for State +impl IfInteresting for State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { /// Evaluate if a set of observation channels has an interesting state fn is_interesting( @@ -449,12 +435,12 @@ where input: &I, observers: &OT, exit_kind: &ExitKind, - ) -> Result + ) -> Result where OT: ObserversTuple, { - self.feedbacks_mut() - .is_interesting_all(input, observers, exit_kind) + self.feedback_mut() + .is_interesting(input, observers, exit_kind) } /// Adds this input to the corpus, if it's intersting, and return the index @@ -462,32 +448,33 @@ where fn add_if_interesting( &mut self, input: &I, - fitness: u32, + is_interesting: bool, scheduler: &CS, ) -> Result, Error> where CS: CorpusScheduler, { - if fitness > 0 { - let testcase = self.testcase_with_feedbacks_metadata(input.clone(), fitness)?; + if is_interesting { + let mut testcase = Testcase::new(input.clone()); + self.feedback_mut().append_metadata(&mut testcase)?; let idx = self.corpus.add(testcase)?; scheduler.on_add(self, idx)?; Ok(Some(idx)) } else { - self.discard_feedbacks_metadata(input)?; + self.feedback_mut().discard_metadata(&input)?; Ok(None) } } } -impl Evaluator for State +impl Evaluator for State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { /// Process one input, adding to the respective corpuses if needed and firing the right events #[inline] @@ -498,27 +485,30 @@ where executor: &mut E, manager: &mut EM, scheduler: &CS, - ) -> Result<(u32, Option), Error> + ) -> Result<(bool, Option), Error> where - E: Executor + HasObservers, - OT: ObserversTuple, + E: Executor + + HasObservers + + HasExecHooks + + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, C: Corpus, EM: EventManager, CS: CorpusScheduler, { - let (fitness, is_solution) = self.execute_input(&input, executor, manager)?; + let (is_interesting, is_solution) = self.execute_input(&input, executor, manager)?; let observers = executor.observers(); if is_solution { // If the input is a solution, add it to the respective corpus let mut testcase = Testcase::new(input.clone()); - self.objectives_mut().append_metadata_all(&mut testcase)?; + self.objective_mut().append_metadata(&mut testcase)?; self.solutions_mut().add(testcase)?; } else { - self.objectives_mut().discard_metadata_all(&input)?; + self.objective_mut().discard_metadata(&input)?; } - let corpus_idx = self.add_if_interesting(&input, fitness, scheduler)?; + let corpus_idx = self.add_if_interesting(&input, is_interesting, scheduler)?; if corpus_idx.is_some() { let observers_buf = manager.serialize_observers(observers)?; manager.fire( @@ -534,18 +524,18 @@ where )?; } - Ok((fitness, corpus_idx)) + Ok((is_interesting, corpus_idx)) } } #[cfg(feature = "std")] -impl State +impl State where C: Corpus, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { pub fn load_from_directory( &mut self, @@ -555,8 +545,11 @@ where in_dir: &Path, ) -> Result<(), Error> where - E: Executor + HasObservers, - OT: ObserversTuple, + E: Executor + + HasObservers + + HasExecHooks + + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, EM: EventManager, CS: CorpusScheduler, { @@ -575,9 +568,10 @@ where println!("Loading file {:?} ...", &path); let bytes = fs::read(&path)?; let input = BytesInput::new(bytes); - let (fitness, is_solution) = self.execute_input(&input, executor, manager)?; + let (is_interesting, is_solution) = + self.execute_input(&input, executor, manager)?; if self - .add_if_interesting(&input, fitness, scheduler)? + .add_if_interesting(&input, is_interesting, scheduler)? .is_none() { println!("File {:?} was not interesting, skipped.", &path); @@ -601,8 +595,11 @@ where in_dirs: &[PathBuf], ) -> Result<(), Error> where - E: Executor + HasObservers, - OT: ObserversTuple, + E: Executor + + HasObservers + + HasExecHooks + + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, EM: EventManager, CS: CorpusScheduler, { @@ -622,14 +619,14 @@ where } } -impl State +impl State where C: Corpus, I: Input, R: Rand, - FT: FeedbacksTuple, + F: Feedback, SC: Corpus, - OFT: FeedbacksTuple, + OF: Feedback, { /// Runs the input and triggers observers and feedback pub fn execute_input( @@ -637,32 +634,34 @@ where input: &I, executor: &mut E, event_mgr: &mut EM, - ) -> Result<(u32, bool), Error> + ) -> Result<(bool, bool), Error> where - E: Executor + HasObservers, - OT: ObserversTuple, + E: Executor + + HasObservers + + HasExecHooks + + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, C: Corpus, EM: EventManager, { - executor.pre_exec_observers()?; + executor.pre_exec_observers(self, event_mgr, input)?; executor.pre_exec(self, event_mgr, input)?; let exit_kind = executor.run_target(input)?; executor.post_exec(self, event_mgr, input)?; *self.executions_mut() += 1; - executor.post_exec_observers()?; + executor.post_exec_observers(self, event_mgr, input)?; let observers = executor.observers(); - let fitness = self - .feedbacks_mut() - .is_interesting_all(&input, observers, &exit_kind)?; + let is_interesting = self + .feedback_mut() + .is_interesting(&input, observers, &exit_kind)?; let is_solution = self - .objectives_mut() - .is_interesting_all(&input, observers, &exit_kind)? - > 0; - Ok((fitness, is_solution)) + .objective_mut() + .is_interesting(&input, observers, &exit_kind)?; + Ok((is_interesting, is_solution)) } pub fn generate_initial_inputs( @@ -676,16 +675,19 @@ where where G: Generator, C: Corpus, - E: Executor + HasObservers, - OT: ObserversTuple, + E: Executor + + HasObservers + + HasExecHooks + + HasObserversHooks, + OT: ObserversTuple + HasExecHooksTuple, EM: EventManager, CS: CorpusScheduler, { let mut added = 0; for _ in 0..num { let input = generator.generate(self.rand_mut())?; - let (fitness, _) = self.evaluate_input(input, executor, manager, scheduler)?; - if fitness > 0 { + let (is_interesting, _) = self.evaluate_input(input, executor, manager, scheduler)?; + if is_interesting { added += 1; } } @@ -701,16 +703,16 @@ where Ok(()) } - pub fn new(rand: R, corpus: C, feedbacks: FT, solutions: SC, objectives: OFT) -> Self { + pub fn new(rand: R, corpus: C, feedback: F, solutions: SC, objective: OF) -> Self { Self { rand, executions: 0, start_time: Duration::from_millis(0), metadata: SerdeAnyMap::default(), corpus, - feedbacks, + feedback, solutions, - objectives, + objective, max_size: DEFAULT_MAX_SIZE, phantom: PhantomData, } diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index d5326ef631..2852fa8210 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_frida" -version = "0.1.0" +version = "0.2.0" authors = ["s1341 "] description = "Frida backend library for LibAFL" documentation = "https://docs.rs/libafl_frida" @@ -13,15 +13,17 @@ edition = "2018" cc = { version = "1.0", features = ["parallel"] } [dependencies] -libafl = { path = "../libafl", version = "0.1.0", features = ["std", "libafl_derive"] } +libafl = { path = "../libafl", version = "0.2.0", features = ["std", "libafl_derive"] } libafl_targets = { path = "../libafl_targets", version = "0.1.0" } nix = "0.20.0" libc = "0.2.92" hashbrown = "0.11" libloading = "0.7.0" rangemap = "0.1.10" -frida-gum = { version = "0.4.0", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] } -frida-gum-sys = { version = "0.2.4", features = [ "auto-download", "event-sink", "invocation-listener"] } +frida-gum = { version = "0.4.0", git = "https://github.com/s1341/frida-rust", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] } +frida-gum-sys = { version = "0.2.4", git = "https://github.com/s1341/frida-rust", features = [ "auto-download", "event-sink", "invocation-listener"] } +#frida-gum = { version = "0.4.0", path = "../../frida-rust/frida-gum", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] } +#frida-gum-sys = { version = "0.2.4", path = "../../frida-rust/frida-gum-sys", features = [ "auto-download", "event-sink", "invocation-listener"] } regex = "1.4" dynasmrt = "1.0.1" capstone = "0.8.0" diff --git a/libafl_frida/src/asan_rt.rs b/libafl_frida/src/asan_rt.rs index f1ad2ceda0..5cc7b034ee 100644 --- a/libafl_frida/src/asan_rt.rs +++ b/libafl_frida/src/asan_rt.rs @@ -2,7 +2,7 @@ use hashbrown::HashMap; use libafl::{ bolts::{ownedref::OwnedPtr, tuples::Named}, corpus::Testcase, - executors::{CustomExitKind, ExitKind}, + executors::{CustomExitKind, ExitKind, HasExecHooks}, feedbacks::Feedback, inputs::{HasTargetBytes, Input}, observers::{Observer, ObserversTuple}, @@ -595,6 +595,7 @@ extern "C" { pub struct AsanRuntime { regs: [usize; 32], + blob_report: Option>, blob_check_mem_byte: Option>, blob_check_mem_halfword: Option>, blob_check_mem_dword: Option>, @@ -679,6 +680,7 @@ impl AsanRuntime { pub fn new(options: FridaOptions) -> Rc> { let res = Rc::new(RefCell::new(Self { regs: [0; 32], + blob_report: None, blob_check_mem_byte: None, blob_check_mem_halfword: None, blob_check_mem_dword: None, @@ -1208,80 +1210,6 @@ impl AsanRuntime { } } } - AsanError::Unknown((registers, pc, fault, backtrace)) => { - let (basereg, indexreg, _displacement, fault_address) = fault; - - if let Ok((start, _, _, path)) = find_mapping_for_address(pc) { - writeln!( - output, - " at 0x{:x} ({}:0x{:04x}), faulting address 0x{:x}", - pc, - path, - pc - start, - fault_address - ) - .unwrap(); - } else { - writeln!( - output, - " at 0x{:x}, faulting address 0x{:x}", - pc, fault_address - ) - .unwrap(); - } - output.reset().unwrap(); - - #[allow(clippy::non_ascii_literal)] - writeln!(output, "{:━^100}", " REGISTERS ").unwrap(); - for reg in 0..=30 { - if reg == basereg { - output - .set_color(ColorSpec::new().set_fg(Some(Color::Red))) - .unwrap(); - } else if reg == indexreg { - output - .set_color(ColorSpec::new().set_fg(Some(Color::Yellow))) - .unwrap(); - } - write!(output, "x{:02}: 0x{:016x} ", reg, registers[reg as usize]).unwrap(); - output.reset().unwrap(); - if reg % 4 == 3 { - writeln!(output).unwrap(); - } - } - writeln!(output, "pc : 0x{:016x} ", pc).unwrap(); - - #[allow(clippy::non_ascii_literal)] - writeln!(output, "{:━^100}", " CODE ").unwrap(); - let mut cs = Capstone::new() - .arm64() - .mode(capstone::arch::arm64::ArchMode::Arm) - .build() - .unwrap(); - cs.set_skipdata(true).expect("failed to set skipdata"); - - let start_pc = pc - 4 * 5; - for insn in cs - .disasm_count( - unsafe { std::slice::from_raw_parts(start_pc as *mut u8, 4 * 11) }, - start_pc as u64, - 11, - ) - .expect("failed to disassemble instructions") - .iter() - { - if insn.address() as usize == pc { - output - .set_color(ColorSpec::new().set_fg(Some(Color::Red))) - .unwrap(); - writeln!(output, "\t => {}", insn).unwrap(); - output.reset().unwrap(); - } else { - writeln!(output, "\t {}", insn).unwrap(); - } - } - backtrace_printer.print_trace(&backtrace, output).unwrap(); - } AsanError::DoubleFree((ptr, mut metadata, backtrace)) => { writeln!(output, " of {:?}", ptr).unwrap(); output.reset().unwrap(); @@ -1339,7 +1267,8 @@ impl AsanRuntime { backtrace_printer.print_trace(backtrace, output).unwrap(); } } - AsanError::StackOobRead((registers, pc, fault, backtrace)) + AsanError::Unknown((registers, pc, fault, backtrace)) + | AsanError::StackOobRead((registers, pc, fault, backtrace)) | AsanError::StackOobWrite((registers, pc, fault, backtrace)) => { let (basereg, indexreg, _displacement, fault_address) = fault; @@ -1421,113 +1350,12 @@ impl AsanRuntime { } } - /// Generate the instrumentation blobs for the current arch. - #[allow(clippy::similar_names)] // We allow things like dword and qword - fn generate_instrumentation_blobs(&mut self) { + fn generate_shadow_check_blob(&mut self, bit: u32) -> Box<[u8]> { let shadow_bit = Allocator::get().shadow_bit as u32; macro_rules! shadow_check { ($ops:ident, $bit:expr) => {dynasm!($ops ; .arch aarch64 - //; brk #5 - ; b >skip_report - - ; report: - ; stp x29, x30, [sp, #-0x10]! - ; mov x29, sp - - ; ldr x0, >self_regs_addr - ; stp x2, x3, [x0, #0x10] - ; stp x4, x5, [x0, #0x20] - ; stp x6, x7, [x0, #0x30] - ; stp x8, x9, [x0, #0x40] - ; stp x10, x11, [x0, #0x50] - ; stp x12, x13, [x0, #0x60] - ; stp x14, x15, [x0, #0x70] - ; stp x16, x17, [x0, #0x80] - ; stp x18, x19, [x0, #0x90] - ; stp x20, x21, [x0, #0xa0] - ; stp x22, x23, [x0, #0xb0] - ; stp x24, x25, [x0, #0xc0] - ; stp x26, x27, [x0, #0xd0] - ; stp x28, x29, [x0, #0xe0] - ; stp x30, xzr, [x0, #0xf0] - ; mov x28, x0 - ; .dword (0xd53b4218u32 as i32) // mrs x24, nzcv - //; ldp x0, x1, [sp], #144 - ; ldp x0, x1, [sp, 0x10] - ; stp x0, x1, [x28] - - ; adr x25, >done - ; str x25, [x28, 0xf8] - - ; adr x25, eh_frame_fde - ; adr x27, >fde_address - ; ldr w26, [x27] - ; cmp w26, #0x0 - ; b.ne >skip_register - ; sub x25, x25, x27 - ; str w25, [x27] - ; ldr x1, >register_frame_func - //; brk #11 - ; blr x1 - ; skip_register: - ; ldr x0, >self_addr - ; ldr x1, >trap_func - ; blr x1 - - ; .dword (0xd51b4218u32 as i32) // msr nzcv, x24 - ; ldr x0, >self_regs_addr - ; ldp x2, x3, [x0, #0x10] - ; ldp x4, x5, [x0, #0x20] - ; ldp x6, x7, [x0, #0x30] - ; ldp x8, x9, [x0, #0x40] - ; ldp x10, x11, [x0, #0x50] - ; ldp x12, x13, [x0, #0x60] - ; ldp x14, x15, [x0, #0x70] - ; ldp x16, x17, [x0, #0x80] - ; ldp x18, x19, [x0, #0x90] - ; ldp x20, x21, [x0, #0xa0] - ; ldp x22, x23, [x0, #0xb0] - ; ldp x24, x25, [x0, #0xc0] - ; ldp x26, x27, [x0, #0xd0] - ; ldp x28, x29, [x0, #0xe0] - ; ldp x30, xzr, [x0, #0xf0] - - ; ldp x29, x30, [sp], #0x10 - ; b >done - ; self_addr: - ; .qword self as *mut _ as *mut c_void as i64 - ; self_regs_addr: - ; .qword &mut self.regs as *mut _ as *mut c_void as i64 - ; trap_func: - ; .qword AsanRuntime::handle_trap as *mut c_void as i64 - ; register_frame_func: - ; .qword __register_frame as *mut c_void as i64 - ; eh_frame_cie: - ; .dword 0x14 - ; .dword 0x00 - ; .dword 0x00527a01 - ; .dword 0x011e7c01 - ; .dword 0x001f0c1b - ; eh_frame_fde: - ; .dword 0x14 - ; .dword 0x18 - ; fde_address: - ; .dword 0x0 // <-- address offset goes here - ; .dword 0x104 - //advance_loc 12 - //def_cfa r29 (x29) at offset 16 - //offset r30 (x30) at cfa-8 - //offset r29 (x29) at cfa-16 - ; .dword 0x1d0c4c00 - ; .dword (0x9d029e10 as u32 as i32) - ; .dword 0x04 - // empty next FDE: - ; .dword 0x0 - ; .dword 0x0 - - ; skip_report: + ; mov x1, #1 ; add x1, xzr, x1, lsl #shadow_bit ; add x1, x1, x0, lsr #3 @@ -1539,115 +1367,25 @@ impl AsanRuntime { ; lsr x1, x1, #16 ; lsr x1, x1, x0 ; tbnz x1, #$bit, >done - ; b done + ; nop // will be replaced by b to report ; done: );}; } + let mut ops = dynasmrt::VecAssembler::::new(0); + shadow_check!(ops, bit); + let ops_vec = ops.finalize().unwrap(); + ops_vec[..ops_vec.len() - 4].to_vec().into_boxed_slice() + } + + fn generate_shadow_check_exact_blob(&mut self, val: u32) -> Box<[u8]> { + let shadow_bit = Allocator::get().shadow_bit as u32; macro_rules! shadow_check_exact { ($ops:ident, $val:expr) => {dynasm!($ops ; .arch aarch64 - ; b >skip_report - - ; report: - ; stp x29, x30, [sp, #-0x10]! - ; mov x29, sp - - ; ldr x0, >self_regs_addr - ; stp x2, x3, [x0, #0x10] - ; stp x4, x5, [x0, #0x20] - ; stp x6, x7, [x0, #0x30] - ; stp x8, x9, [x0, #0x40] - ; stp x10, x11, [x0, #0x50] - ; stp x12, x13, [x0, #0x60] - ; stp x14, x15, [x0, #0x70] - ; stp x16, x17, [x0, #0x80] - ; stp x18, x19, [x0, #0x90] - ; stp x20, x21, [x0, #0xa0] - ; stp x22, x23, [x0, #0xb0] - ; stp x24, x25, [x0, #0xc0] - ; stp x26, x27, [x0, #0xd0] - ; stp x28, x29, [x0, #0xe0] - ; stp x30, xzr, [x0, #0xf0] - ; mov x28, x0 - ; .dword (0xd53b4218u32 as i32) // mrs x24, nzcv - ; ldp x0, x1, [sp, 0x10] - ; stp x0, x1, [x28] - - ; adr x25, >done - ; add x25, x25, 4 - ; str x25, [x28, 0xf8] - - ; adr x25, eh_frame_fde - ; adr x27, >fde_address - ; ldr w26, [x27] - ; cmp w26, #0x0 - ; b.ne >skip_register - ; sub x25, x25, x27 - ; str w25, [x27] - ; ldr x1, >register_frame_func - //; brk #11 - ; blr x1 - ; skip_register: - ; ldr x0, >self_addr - ; ldr x1, >trap_func - ; blr x1 - - ; .dword (0xd51b4218u32 as i32) // msr nzcv, x24 - ; ldr x0, >self_regs_addr - ; ldp x2, x3, [x0, #0x10] - ; ldp x4, x5, [x0, #0x20] - ; ldp x6, x7, [x0, #0x30] - ; ldp x8, x9, [x0, #0x40] - ; ldp x10, x11, [x0, #0x50] - ; ldp x12, x13, [x0, #0x60] - ; ldp x14, x15, [x0, #0x70] - ; ldp x16, x17, [x0, #0x80] - ; ldp x18, x19, [x0, #0x90] - ; ldp x20, x21, [x0, #0xa0] - ; ldp x22, x23, [x0, #0xb0] - ; ldp x24, x25, [x0, #0xc0] - ; ldp x26, x27, [x0, #0xd0] - ; ldp x28, x29, [x0, #0xe0] - ; ldp x30, xzr, [x0, #0xf0] - - ; ldp x29, x30, [sp], #0x10 - ; b >done - ; self_addr: - ; .qword self as *mut _ as *mut c_void as i64 - ; self_regs_addr: - ; .qword &mut self.regs as *mut _ as *mut c_void as i64 - ; trap_func: - ; .qword AsanRuntime::handle_trap as *mut c_void as i64 - ; register_frame_func: - ; .qword __register_frame as *mut c_void as i64 - ; eh_frame_cie: - ; .dword 0x14 - ; .dword 0x00 - ; .dword 0x00527a01 - ; .dword 0x011e7c01 - ; .dword 0x001f0c1b - ; eh_frame_fde: - ; .dword 0x14 - ; .dword 0x18 - ; fde_address: - ; .dword 0x0 // <-- address offset goes here - ; .dword 0x104 - //advance_loc 12 - //def_cfa r29 (x29) at offset 16 - //offset r30 (x30) at cfa-8 - //offset r29 (x29) at cfa-16 - ; .dword 0x1d0c4c00 - ; .dword (0x9d029e10 as u32 as i32) - ; .dword 0x04 - // empty next FDE: - ; .dword 0x0 - ; .dword 0x0 - - - ; skip_report: + ; mov x1, #1 ; add x1, xzr, x1, lsl #shadow_bit ; add x1, x1, x0, lsr #3 @@ -1659,92 +1397,153 @@ impl AsanRuntime { ; lsr x1, x1, #16 ; lsr x1, x1, x0 ; .dword -717536768 // 0xd53b4200 //mrs x0, NZCV - ; and x1, x1, #$val + ; and x1, x1, #$val as u64 ; cmp x1, #$val ; b.eq >done - ; b done + ; nop // will be replaced by b to report ; done: - ; .dword -719633920 //0xd51b4200 // msr nvcz, x0 );}; } - let mut ops_check_mem_byte = - dynasmrt::VecAssembler::::new(0); - shadow_check!(ops_check_mem_byte, 0); - self.blob_check_mem_byte = Some(ops_check_mem_byte.finalize().unwrap().into_boxed_slice()); - - let mut ops_check_mem_halfword = - dynasmrt::VecAssembler::::new(0); - shadow_check!(ops_check_mem_halfword, 1); - self.blob_check_mem_halfword = Some( - ops_check_mem_halfword - .finalize() - .unwrap() - .into_boxed_slice(), + let mut ops = dynasmrt::VecAssembler::::new(0); + shadow_check_exact!(ops, val); + let ops_vec = ops.finalize().unwrap(); + ops_vec[..ops_vec.len() - 4].to_vec().into_boxed_slice() + } + + /// + /// Generate the instrumentation blobs for the current arch. + #[allow(clippy::similar_names)] // We allow things like dword and qword + fn generate_instrumentation_blobs(&mut self) { + let mut ops_report = dynasmrt::VecAssembler::::new(0); + dynasm!(ops_report + ; .arch aarch64 + + ; report: + ; stp x29, x30, [sp, #-0x10]! + ; mov x29, sp + // save the nvcz and the 'return-address'/address of instrumented instruction + ; stp x0, x1, [sp, #-0x10]! + + ; ldr x0, >self_regs_addr + ; stp x2, x3, [x0, #0x10] + ; stp x4, x5, [x0, #0x20] + ; stp x6, x7, [x0, #0x30] + ; stp x8, x9, [x0, #0x40] + ; stp x10, x11, [x0, #0x50] + ; stp x12, x13, [x0, #0x60] + ; stp x14, x15, [x0, #0x70] + ; stp x16, x17, [x0, #0x80] + ; stp x18, x19, [x0, #0x90] + ; stp x20, x21, [x0, #0xa0] + ; stp x22, x23, [x0, #0xb0] + ; stp x24, x25, [x0, #0xc0] + ; stp x26, x27, [x0, #0xd0] + ; stp x28, x29, [x0, #0xe0] + ; stp x30, xzr, [x0, #0xf0] + ; mov x28, x0 + + ; mov x25, x1 // address of instrumented instruction. + ; str x25, [x28, 0xf8] + + ; .dword 0xd53b4218u32 as i32 // mrs x24, nzcv + ; ldp x0, x1, [sp, 0x20] + ; stp x0, x1, [x28] + + ; adr x25, eh_frame_fde + ; adr x27, >fde_address + ; ldr w26, [x27] + ; cmp w26, #0x0 + ; b.ne >skip_register + ; sub x25, x25, x27 + ; str w25, [x27] + ; ldr x1, >register_frame_func + //; brk #11 + ; blr x1 + ; skip_register: + ; ldr x0, >self_addr + ; ldr x1, >trap_func + ; blr x1 + + ; .dword 0xd51b4218u32 as i32 // msr nzcv, x24 + ; ldr x0, >self_regs_addr + ; ldp x2, x3, [x0, #0x10] + ; ldp x4, x5, [x0, #0x20] + ; ldp x6, x7, [x0, #0x30] + ; ldp x8, x9, [x0, #0x40] + ; ldp x10, x11, [x0, #0x50] + ; ldp x12, x13, [x0, #0x60] + ; ldp x14, x15, [x0, #0x70] + ; ldp x16, x17, [x0, #0x80] + ; ldp x18, x19, [x0, #0x90] + ; ldp x20, x21, [x0, #0xa0] + ; ldp x22, x23, [x0, #0xb0] + ; ldp x24, x25, [x0, #0xc0] + ; ldp x26, x27, [x0, #0xd0] + ; ldp x28, x29, [x0, #0xe0] + ; ldp x30, xzr, [x0, #0xf0] + + // restore nzcv. and 'return address' + ; ldp x0, x1, [sp], #0x10 + ; ldp x29, x30, [sp], #0x10 + ; br x1 // go back to the 'return address' + + ; self_addr: + ; .qword self as *mut _ as *mut c_void as i64 + ; self_regs_addr: + ; .qword &mut self.regs as *mut _ as *mut c_void as i64 + ; trap_func: + ; .qword AsanRuntime::handle_trap as *mut c_void as i64 + ; register_frame_func: + ; .qword __register_frame as *mut c_void as i64 + ; eh_frame_cie: + ; .dword 0x14 + ; .dword 0x00 + ; .dword 0x00527a01 + ; .dword 0x011e7c01 + ; .dword 0x001f0c1b + ; eh_frame_fde: + ; .dword 0x14 + ; .dword 0x18 + ; fde_address: + ; .dword 0x0 // <-- address offset goes here + ; .dword 0x104 + //advance_loc 12 + //def_cfa r29 (x29) at offset 16 + //offset r30 (x30) at cfa-8 + //offset r29 (x29) at cfa-16 + ; .dword 0x1d0c4c00 + ; .dword 0x9d029e10 as u32 as i32 + ; .dword 0x04 + // empty next FDE: + ; .dword 0x0 + ; .dword 0x0 ); + self.blob_report = Some(ops_report.finalize().unwrap().into_boxed_slice()); + + self.blob_check_mem_byte = Some(self.generate_shadow_check_blob(0)); + self.blob_check_mem_halfword = Some(self.generate_shadow_check_blob(1)); + self.blob_check_mem_dword = Some(self.generate_shadow_check_blob(2)); + self.blob_check_mem_qword = Some(self.generate_shadow_check_blob(3)); + self.blob_check_mem_16bytes = Some(self.generate_shadow_check_blob(4)); - let mut ops_check_mem_dword = - dynasmrt::VecAssembler::::new(0); - shadow_check!(ops_check_mem_dword, 2); - self.blob_check_mem_dword = - Some(ops_check_mem_dword.finalize().unwrap().into_boxed_slice()); - - let mut ops_check_mem_qword = - dynasmrt::VecAssembler::::new(0); - shadow_check!(ops_check_mem_qword, 3); - self.blob_check_mem_qword = - Some(ops_check_mem_qword.finalize().unwrap().into_boxed_slice()); - - let mut ops_check_mem_16bytes = - dynasmrt::VecAssembler::::new(0); - shadow_check!(ops_check_mem_16bytes, 4); - self.blob_check_mem_16bytes = - Some(ops_check_mem_16bytes.finalize().unwrap().into_boxed_slice()); - - let mut ops_check_mem_3bytes = - dynasmrt::VecAssembler::::new(0); - shadow_check_exact!(ops_check_mem_3bytes, 3); - self.blob_check_mem_3bytes = - Some(ops_check_mem_3bytes.finalize().unwrap().into_boxed_slice()); - - let mut ops_check_mem_6bytes = - dynasmrt::VecAssembler::::new(0); - shadow_check_exact!(ops_check_mem_6bytes, 6); - self.blob_check_mem_6bytes = - Some(ops_check_mem_6bytes.finalize().unwrap().into_boxed_slice()); - - let mut ops_check_mem_12bytes = - dynasmrt::VecAssembler::::new(0); - shadow_check_exact!(ops_check_mem_12bytes, 12); - self.blob_check_mem_12bytes = - Some(ops_check_mem_12bytes.finalize().unwrap().into_boxed_slice()); - - let mut ops_check_mem_24bytes = - dynasmrt::VecAssembler::::new(0); - shadow_check_exact!(ops_check_mem_24bytes, 24); - self.blob_check_mem_24bytes = - Some(ops_check_mem_24bytes.finalize().unwrap().into_boxed_slice()); - - let mut ops_check_mem_32bytes = - dynasmrt::VecAssembler::::new(0); - shadow_check_exact!(ops_check_mem_32bytes, 32); - self.blob_check_mem_32bytes = - Some(ops_check_mem_32bytes.finalize().unwrap().into_boxed_slice()); - - let mut ops_check_mem_48bytes = - dynasmrt::VecAssembler::::new(0); - shadow_check_exact!(ops_check_mem_48bytes, 48); - self.blob_check_mem_48bytes = - Some(ops_check_mem_48bytes.finalize().unwrap().into_boxed_slice()); - - let mut ops_check_mem_64bytes = - dynasmrt::VecAssembler::::new(0); - shadow_check_exact!(ops_check_mem_64bytes, 64); - self.blob_check_mem_64bytes = - Some(ops_check_mem_64bytes.finalize().unwrap().into_boxed_slice()); + self.blob_check_mem_3bytes = Some(self.generate_shadow_check_exact_blob(3)); + self.blob_check_mem_6bytes = Some(self.generate_shadow_check_exact_blob(6)); + self.blob_check_mem_12bytes = Some(self.generate_shadow_check_exact_blob(12)); + self.blob_check_mem_24bytes = Some(self.generate_shadow_check_exact_blob(24)); + self.blob_check_mem_32bytes = Some(self.generate_shadow_check_exact_blob(32)); + self.blob_check_mem_48bytes = Some(self.generate_shadow_check_exact_blob(48)); + self.blob_check_mem_64bytes = Some(self.generate_shadow_check_exact_blob(64)); } + /// Get the blob which implements the report funclet + #[inline] + pub fn blob_report(&self) -> &[u8] { + self.blob_report.as_ref().unwrap() + } /// Get the blob which checks a byte access #[inline] pub fn blob_check_mem_byte(&self) -> &[u8] { @@ -1826,8 +1625,10 @@ pub struct AsanErrorsObserver { errors: OwnedPtr>, } -impl Observer for AsanErrorsObserver { - fn pre_exec(&mut self) -> Result<(), Error> { +impl Observer for AsanErrorsObserver {} + +impl HasExecHooks for AsanErrorsObserver { + fn pre_exec(&mut self, _state: &mut S, _mgr: &mut EM, _input: &I) -> Result<(), Error> { unsafe { if ASAN_ERRORS.is_some() { ASAN_ERRORS.as_mut().unwrap().clear(); @@ -1886,18 +1687,18 @@ where _input: &I, observers: &OT, _exit_kind: &ExitKind, - ) -> Result { + ) -> Result { let observer = observers .match_first_type::() .expect("An AsanErrorsFeedback needs an AsanErrorsObserver"); match observer.errors() { - None => Ok(0), + None => Ok(false), Some(errors) => { if !errors.errors.is_empty() { self.errors = Some(errors.clone()); - Ok(1) + Ok(true) } else { - Ok(0) + Ok(false) } } } diff --git a/libafl_frida/src/helper.rs b/libafl_frida/src/helper.rs index a307a0d3ed..fd9a76830e 100644 --- a/libafl_frida/src/helper.rs +++ b/libafl_frida/src/helper.rs @@ -58,6 +58,7 @@ pub struct FridaInstrumentationHelper<'a> { map: [u8; MAP_SIZE], previous_pc: [u64; 1], current_log_impl: u64, + current_report_impl: u64, /// Transformer that has to be passed to FridaInProcessExecutor transformer: Option>, capstone: Capstone, @@ -205,6 +206,7 @@ impl<'a> FridaInstrumentationHelper<'a> { map: [0u8; MAP_SIZE], previous_pc: [0u64; 1], current_log_impl: 0, + current_report_impl: 0, transformer: None, capstone: Capstone::new() .arm64() @@ -315,7 +317,7 @@ impl<'a> FridaInstrumentationHelper<'a> { #[cfg(target_arch = "aarch64")] #[inline] fn emit_shadow_check( - &self, + &mut self, _address: u64, output: &StalkerOutput, basereg: capstone::RegId, @@ -334,6 +336,22 @@ impl<'a> FridaInstrumentationHelper<'a> { None }; + if self.current_report_impl == 0 + || !writer.can_branch_directly_to(self.current_report_impl) + || !writer.can_branch_directly_between(writer.pc() + 128, self.current_report_impl) + { + let after_report_impl = writer.code_offset() + 2; + + #[cfg(target_arch = "x86_64")] + writer.put_jmp_near_label(after_report_impl); + #[cfg(target_arch = "aarch64")] + writer.put_b_label(after_report_impl); + + self.current_report_impl = writer.pc(); + writer.put_bytes(self.asan_runtime.borrow().blob_report()); + + writer.put_label(after_report_impl); + } //writer.put_brk_imm(1); // Preserve x0, x1: @@ -477,9 +495,23 @@ impl<'a> FridaInstrumentationHelper<'a> { 16 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_16bytes()), 24 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_24bytes()), 32 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_32bytes()), + 48 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_48bytes()), + 64 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_64bytes()), _ => false, }; + // Add the branch to report + //writer.put_brk_imm(0x12); + writer.put_branch_address(self.current_report_impl); + + match width { + 3 | 6 | 12 | 24 | 32 | 48 | 64 => { + let msr_nvcz_x0: u32 = 0xd51b4200; + writer.put_bytes(&msr_nvcz_x0.to_le_bytes()); + } + _ => (), + } + // Restore x0, x1 assert!(writer.put_ldp_reg_reg_reg_offset( Aarch64Register::X0, From aea501e4e246cc3421cb52cb50ab820a7962d94a Mon Sep 17 00:00:00 2001 From: tokatoka Date: Wed, 5 May 2021 05:59:30 +0900 Subject: [PATCH 09/12] feedbacks now return a boolean value --- libafl/src/feedbacks/map.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libafl/src/feedbacks/map.rs b/libafl/src/feedbacks/map.rs index cc21332783..cc76a6cf63 100644 --- a/libafl/src/feedbacks/map.rs +++ b/libafl/src/feedbacks/map.rs @@ -358,7 +358,7 @@ where _input: &I, observers: &OT, _exit_kind: &ExitKind, - ) -> Result { + ) -> Result { let observer = observers.match_name_type::(&self.name).unwrap(); let size = observer.usable_count(); let mut hit_target: bool = false; @@ -370,9 +370,9 @@ where } } if hit_target { - Ok(1) + Ok(true) } else { - Ok(0) + Ok(false) } } From 1dd39bfa637bc04dfad5c565a89d47510e626742 Mon Sep 17 00:00:00 2001 From: tokatoka Date: Wed, 5 May 2021 07:01:16 +0900 Subject: [PATCH 10/12] use feedback_or, and modify Cargo.toml --- fuzzers/libfuzzer_reachability/Cargo.toml | 4 ++-- fuzzers/libfuzzer_reachability/src/lib.rs | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/fuzzers/libfuzzer_reachability/Cargo.toml b/fuzzers/libfuzzer_reachability/Cargo.toml index 589fc6bb6b..86a79eb22f 100644 --- a/fuzzers/libfuzzer_reachability/Cargo.toml +++ b/fuzzers/libfuzzer_reachability/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "libfuzzer_libpng" -version = "0.1.0" +name = "libfuzzer_reachability" +version = "0.2.0" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" diff --git a/fuzzers/libfuzzer_reachability/src/lib.rs b/fuzzers/libfuzzer_reachability/src/lib.rs index d4aaa2d6a2..e1b4b42458 100644 --- a/fuzzers/libfuzzer_reachability/src/lib.rs +++ b/fuzzers/libfuzzer_reachability/src/lib.rs @@ -12,6 +12,7 @@ use libafl::{ }, events::{setup_restarting_mgr_std, EventManager}, executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, + feedback_or, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback, ReachabilityFeedback}, fuzzer::{Fuzzer, StdFuzzer}, mutators::scheduled::{havoc_mutations, StdScheduledMutator}, @@ -80,7 +81,6 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> unsafe{ StdMapObserver::new_from_ptr("png.c", __libafl_target_list, TARGET_SIZE) }; - // If not restarting, create a State from scratch let mut state = state.unwrap_or_else(|| { State::new( @@ -89,7 +89,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // Corpus that will be evolved, we keep it in memory for performance InMemoryCorpus::new(), // Feedbacks to rate the interestingness of an input - tuple_list!( + feedback_or!( MaxMapFeedback::new_with_observer_track(&edges_observer, true, false), TimeFeedback::new() ), @@ -97,7 +97,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // on disk so the user can get them after stopping the fuzzer OnDiskCorpus::new(objective_dir).unwrap(), // Feedbacks to recognize an input as solution - tuple_list!(CrashFeedback::new(), TimeoutFeedback::new(), ReachabilityFeedback::new_with_observer(&reachability_observer)), + feedback_or!(CrashFeedback::new(), TimeoutFeedback::new(), ReachabilityFeedback::new_with_observer(&reachability_observer)), ) }); @@ -133,7 +133,6 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // Create the executor for an in-process function with one observer for edge coverage and one for the execution time let mut executor = TimeoutExecutor::new( InProcessExecutor::new( - "in-process(edges,time)", &mut harness, tuple_list!(edges_observer, reachability_observer, TimeObserver::new("time")), &mut state, From ed9bff73507ec8cbb7a0f7197b1de6b465bf1107 Mon Sep 17 00:00:00 2001 From: tokatoka Date: Wed, 5 May 2021 18:06:04 +0900 Subject: [PATCH 11/12] fix diff between dev and this branch --- libafl/src/bolts/llmp.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/libafl/src/bolts/llmp.rs b/libafl/src/bolts/llmp.rs index c133a38c77..5205b1936f 100644 --- a/libafl/src/bolts/llmp.rs +++ b/libafl/src/bolts/llmp.rs @@ -914,10 +914,7 @@ where panic!("Message sent twice!"); } if (*msg).tag == LLMP_TAG_UNSET { - panic!( - "No tag set on message with id {}", - *ptr::addr_of!((*msg).message_id) - ); + panic!("No tag set on message with id {}", (*msg).message_id); } let page = self.out_maps.last_mut().unwrap().page_mut(); if msg.is_null() || !llmp_msg_in_page(page, msg) { @@ -1211,7 +1208,7 @@ where LLMP_TAG_UNSET => panic!("BUG: Read unallocated msg"), LLMP_TAG_EXITING => { // The other side is done. - assert_eq!(*ptr::addr_of!((*msg).buf_len), 0); + assert_eq!((*msg).buf_len, 0); return Err(Error::ShuttingDown); } LLMP_TAG_END_OF_PAGE => { @@ -1221,8 +1218,8 @@ where if (*msg).buf_len < size_of::() as u64 { panic!( "Illegal message length for EOP (is {}/{}, expected {})", - *ptr::addr_of!((*msg).buf_len), - *ptr::addr_of!((*msg).buf_len_padded), + (*msg).buf_len, + (*msg).buf_len_padded, size_of::() ); } From adf4bc2bd29ee2dc180ba8db00aeb68ccd8556cf Mon Sep 17 00:00:00 2001 From: tokatoka Date: Wed, 5 May 2021 18:06:47 +0900 Subject: [PATCH 12/12] fmt --- fuzzers/libfuzzer_reachability/src/lib.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/fuzzers/libfuzzer_reachability/src/lib.rs b/fuzzers/libfuzzer_reachability/src/lib.rs index e1b4b42458..13015a3bc4 100644 --- a/fuzzers/libfuzzer_reachability/src/lib.rs +++ b/fuzzers/libfuzzer_reachability/src/lib.rs @@ -13,7 +13,9 @@ use libafl::{ events::{setup_restarting_mgr_std, EventManager}, executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, feedback_or, - feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback, ReachabilityFeedback}, + feedbacks::{ + CrashFeedback, MaxMapFeedback, ReachabilityFeedback, TimeFeedback, TimeoutFeedback, + }, fuzzer::{Fuzzer, StdFuzzer}, mutators::scheduled::{havoc_mutations, StdScheduledMutator}, mutators::token_mutations::Tokens, @@ -77,10 +79,8 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> StdMapObserver::new("edges", &mut EDGES_MAP, MAX_EDGES_NUM) }); - let reachability_observer = - unsafe{ - StdMapObserver::new_from_ptr("png.c", __libafl_target_list, TARGET_SIZE) - }; + let reachability_observer = + unsafe { StdMapObserver::new_from_ptr("png.c", __libafl_target_list, TARGET_SIZE) }; // If not restarting, create a State from scratch let mut state = state.unwrap_or_else(|| { State::new( @@ -97,7 +97,11 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // on disk so the user can get them after stopping the fuzzer OnDiskCorpus::new(objective_dir).unwrap(), // Feedbacks to recognize an input as solution - feedback_or!(CrashFeedback::new(), TimeoutFeedback::new(), ReachabilityFeedback::new_with_observer(&reachability_observer)), + feedback_or!( + CrashFeedback::new(), + TimeoutFeedback::new(), + ReachabilityFeedback::new_with_observer(&reachability_observer) + ), ) }); @@ -134,7 +138,11 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> let mut executor = TimeoutExecutor::new( InProcessExecutor::new( &mut harness, - tuple_list!(edges_observer, reachability_observer, TimeObserver::new("time")), + tuple_list!( + edges_observer, + reachability_observer, + TimeObserver::new("time") + ), &mut state, &mut restarting_mgr, )?,