diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 621f174256..a6fd669bf6 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -3,7 +3,7 @@ name: Build and Test on: push: branches: [ main ] - pull_requests: + pull_request: branches: [ main, dev ] env: diff --git a/Cargo.toml b/Cargo.toml index 306fabede1..1c364ac8eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ members = [ #example fuzzers "fuzzers/libfuzzer_libpng", "fuzzers/libfuzzer_libmozjpeg", + "fuzzers/libfuzzer_libpng_cmpalloc", ] diff --git a/README.md b/README.md index 0ff353378b..c313fcca30 100644 --- a/README.md +++ b/README.md @@ -6,26 +6,43 @@ LibAFL is written and maintained by Andrea Fioraldi a It is released as Open Source Software under the [Apache v2](LICENSE-APACHE) or [MIT](LICENSE-MIT) licenses. -## Example usages +## Getting started + +Clone the LibAFL repository with + +``` +git clone https://github.com/AFLplusplus/LibAFL +``` + +Build the library using + +``` +cargo build --release +``` + +Build the documentation with + +``` +cargo doc +``` + +We collect example fuzzers in `./fuzzers`. They can be build using `cargo build --example [fuzzer_name] --release`. -We collect example fuzzers in `./fuzzers`. They can be build using `cargo build --example [fuzzer_name] --release` The best-tested fuzzer is `./fuzzers/libfuzzer_libpng`, a clone of libfuzzer using libafl for a libpng harness. See its readme [here](./fuzzers/libfuzzer_libpng/README.md). -If you want to get a quick overview, run `cargo doc`. -Feel free to open issues or contact us directly. Thank you for your support. <3 - ## The Core Concepts -We're still working on the documentation. In the meantime, you can watch the Video from last year's Rc3, here: +The entire library is based on some core concepts that we think can generalize Fuzz Testing. + +We're still working on extending the documentation. + +In the meantime, you can watch the Video from last year's RC3, here: + [![Video explaining libAFL's core concepts](http://img.youtube.com/vi/3RWkT1Q5IV0/3.jpg)](http://www.youtube.com/watch?v=3RWkT1Q5IV0 "Fuzzers Like LEGO") -## Roadmap for release -+ ~~Minset corpus scheduler~~ still doc missing -+ Win32 shared mem and crash handler to have Windows in-process executor -+ Other feedbacks examples (e.g. maximize allocations to spot OOMs) -+ Other objectives examples (e.g. execution of a given program point) -+ ~~A macro crate with derive directives (e.g. for SerdeAny impl)~~ just `derive(SerdeAny)`, missing doc. -+ Good documentation +## Contributing -For further TODOs, see [TODO.md](./TODO.md) +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 diff --git a/TODO.md b/TODO.md index c5a59e1a49..8a12d2cbea 100644 --- a/TODO.md +++ b/TODO.md @@ -2,21 +2,20 @@ - [x] ~~Minset corpus scheduler~~ still doc missing - [ ] Win32 shared mem and crash handler to have Windows in-process executor -- [ ] Other feedbacks examples (e.g. maximize allocations to spot OOMs) +- [x] Other feedbacks examples (e.g. maximize allocations to spot OOMs) - [ ] Other objectives examples (e.g. execution of a given program point) - [ ] Objective-Specific Corpuses (named per objective) - [x] A macro crate with derive directives (e.g. for SerdeAny impl). - [ ] Good documentation - [ ] LLMP brotli compression -- [ ] Timeouts (timeout observer, objective) - [ ] AFL-Style Forkserver Executor -- [ ] Restarting EventMgr could use forks on unix +- [x] Restarting EventMgr could use forks on unix - [ ] Android Ashmem support - [ ] Restart Count in Fuzzing Loop - [ ] LAIN / structured fuzzing example - [ ] Errors in the Fuzzer should exit the fuzz run - [ ] More informative outpus, deeper introspection (stats, what mutation did x, etc.) -- [ ] Timeouts for executors +- [x] Timeouts for executors - [ ] Timeout handling for llmp clients (no ping for n seconds -> treat as disconnected) - [ ] LLMP Cross Machine Link (2 brokers connected via TCP) - [ ] "Launcher" example that spawns broker + n clients diff --git a/fuzzers/libfuzzer_libpng/src/fuzzer.rs b/fuzzers/libfuzzer_libpng/src/fuzzer.rs index 4640433c40..9bd2febdc3 100644 --- a/fuzzers/libfuzzer_libpng/src/fuzzer.rs +++ b/fuzzers/libfuzzer_libpng/src/fuzzer.rs @@ -1,6 +1,7 @@ //! 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}; #[cfg(unix)] @@ -11,8 +12,8 @@ use libafl::{ QueueCorpusScheduler, }, events::setup_restarting_mgr, - executors::{inprocess::InProcessExecutor, Executor, ExitKind}, - feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, + executors::{inprocess::InProcessExecutor, Executor, ExitKind, TimeoutExecutor}, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, inputs::Input, mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens}, @@ -117,7 +118,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()), + tuple_list!(CrashFeedback::new(), TimeoutFeedback::new()), ) }); @@ -143,13 +144,17 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage)); // Create the executor for an in-process function with just one observer for edge coverage - let mut executor = InProcessExecutor::new( - "in-process(edges)", - harness, - tuple_list!(edges_observer, TimeObserver::new("time")), - &mut state, - &mut restarting_mgr, - )?; + let mut executor = TimeoutExecutor::new( + InProcessExecutor::new( + "in-process(edges)", + harness, + tuple_list!(edges_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. diff --git a/fuzzers/libfuzzer_libpng_cmpalloc/.gitignore b/fuzzers/libfuzzer_libpng_cmpalloc/.gitignore new file mode 100644 index 0000000000..a977a2ca5b --- /dev/null +++ b/fuzzers/libfuzzer_libpng_cmpalloc/.gitignore @@ -0,0 +1 @@ +libpng-* \ No newline at end of file diff --git a/fuzzers/libfuzzer_libpng_cmpalloc/Cargo.toml b/fuzzers/libfuzzer_libpng_cmpalloc/Cargo.toml new file mode 100644 index 0000000000..16e498bb93 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_cmpalloc/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "libfuzzer_libpng_cmpalloc" +version = "0.1.0" +authors = ["Andrea Fioraldi ", "Dominik Maier "] +edition = "2018" +build = "build.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["std"] +std = [] + +#[profile.release] +#lto = true +#codegen-units = 1 +#opt-level = 3 +#debug = true + +[build-dependencies] +cc = { version = "1.0", features = ["parallel"] } +num_cpus = "1.0" + +[dependencies] +libafl = { path = "../../libafl/" } + +[[example]] +name = "libfuzzer_libpng_cmpalloc" +path = "./src/fuzzer.rs" +test = false +bench = false diff --git a/fuzzers/libfuzzer_libpng_cmpalloc/README.md b/fuzzers/libfuzzer_libpng_cmpalloc/README.md new file mode 100644 index 0000000000..bfd858fbcb --- /dev/null +++ b/fuzzers/libfuzzer_libpng_cmpalloc/README.md @@ -0,0 +1,28 @@ +# Libfuzzer for libpng (cmp+alloc) + +This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection. +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. + +The difference between the normal Libfuzzer for libpng example here is that this fuzzer is not just using edge coverage as feedback but also comparisons values (-value-profile like) and allocations sizes. +This is an example how multiple feedbacks can be combined in a fuzzer. + +## Build + +To build this example, run `cargo build --example libfuzzer_libpng_cmpalloc --release`. +This will call (the build.rs)[./builld.rs], which in turn downloads a libpng archive from the web. +Then, it will link (the fuzzer)[./src/fuzzer.rs] against (the C++ harness)[./harness.cc] and the instrumented `libpng`. +Afterwards, the fuzzer will be ready to run, from `../../target/examples/libfuzzer_libpng_cmpalloc`. + +## 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. + +Each following execution will run a fuzzer client. +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). + +For convenience, you may just run `./test.sh` in this folder to test it. diff --git a/fuzzers/libfuzzer_libpng_cmpalloc/build.rs b/fuzzers/libfuzzer_libpng_cmpalloc/build.rs new file mode 100644 index 0000000000..49f3cfba94 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_cmpalloc/build.rs @@ -0,0 +1,109 @@ +// build.rs + +use std::{ + env, + path::Path, + process::{exit, Command}, +}; + +const LIBPNG_URL: &str = + "https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz"; + +fn main() { + if cfg!(windows) { + println!("cargo:warning=Skipping libpng example on Windows"); + exit(0); + } + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let cwd = env::current_dir().unwrap().to_string_lossy().to_string(); + let out_dir = out_dir.to_string_lossy().to_string(); + let out_dir_path = Path::new(&out_dir); + + println!("cargo:rerun-if-changed=../libfuzzer_runtime/rt.c",); + println!("cargo:rerun-if-changed=harness.cc"); + + let libpng = format!("{}/libpng-1.6.37", &out_dir); + let libpng_path = Path::new(&libpng); + let libpng_tar = format!("{}/libpng-1.6.37.tar.xz", &cwd); + + // Enforce clang for its -fsanitize-coverage support. + std::env::set_var("CC", "clang"); + std::env::set_var("CXX", "clang++"); + let ldflags = match env::var("LDFLAGS") { + Ok(val) => val, + Err(_) => "".to_string(), + }; + + if !libpng_path.is_dir() { + if !Path::new(&libpng_tar).is_file() { + println!("cargo:warning=Libpng not found, downloading..."); + // Download libpng + Command::new("wget") + .arg("-c") + .arg(LIBPNG_URL) + .arg("-O") + .arg(&libpng_tar) + .status() + .unwrap(); + } + Command::new("tar") + .current_dir(&out_dir_path) + .arg("-xvf") + .arg(&libpng_tar) + .status() + .unwrap(); + Command::new(format!("{}/configure", &libpng)) + .current_dir(&libpng_path) + .args(&[ + "--disable-shared", + &format!("--host={}", env::var("TARGET").unwrap())[..], + ]) + .env("CC", "clang") + .env("CXX", "clang++") + .env( + "CFLAGS", + "-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard", + ) + .env( + "CXXFLAGS", + "-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard", + ) + .env( + "LDFLAGS", + format!("-g -fPIE -fsanitize-coverage=trace-pc-guard {}", ldflags), + ) + .status() + .unwrap(); + Command::new("make") + .current_dir(&libpng_path) + .status() + .unwrap(); + } + + cc::Build::new() + .file("../libfuzzer_runtime/rt.c") + .compile("libfuzzer-sys"); + + cc::Build::new() + .include(&libpng_path) + .cpp(true) + .flag("-fsanitize-coverage=trace-pc-guard") + // .define("HAS_DUMMY_CRASH", "1") + .file("./harness.cc") + .compile("libfuzzer-harness"); + + println!("cargo:rustc-link-search=native={}", &out_dir); + println!("cargo:rustc-link-search=native={}/.libs", &libpng); + println!("cargo:rustc-link-lib=static=png16"); + + //Deps for libpng: -pthread -lz -lm + println!("cargo:rustc-link-lib=dylib=m"); + println!("cargo:rustc-link-lib=dylib=z"); + + //For the C++ harness + //must by dylib for android + println!("cargo:rustc-link-lib=dylib=stdc++"); + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/fuzzers/libfuzzer_libpng_cmpalloc/corpus/not_kitty.png b/fuzzers/libfuzzer_libpng_cmpalloc/corpus/not_kitty.png new file mode 100644 index 0000000000..eff7c1707b Binary files /dev/null and b/fuzzers/libfuzzer_libpng_cmpalloc/corpus/not_kitty.png differ diff --git a/fuzzers/libfuzzer_libpng_cmpalloc/corpus/not_kitty_alpha.png b/fuzzers/libfuzzer_libpng_cmpalloc/corpus/not_kitty_alpha.png new file mode 100644 index 0000000000..2fb8da2c8f Binary files /dev/null and b/fuzzers/libfuzzer_libpng_cmpalloc/corpus/not_kitty_alpha.png differ diff --git a/fuzzers/libfuzzer_libpng_cmpalloc/corpus/not_kitty_gamma.png b/fuzzers/libfuzzer_libpng_cmpalloc/corpus/not_kitty_gamma.png new file mode 100644 index 0000000000..939d9d29a9 Binary files /dev/null and b/fuzzers/libfuzzer_libpng_cmpalloc/corpus/not_kitty_gamma.png differ diff --git a/fuzzers/libfuzzer_libpng_cmpalloc/corpus/not_kitty_icc.png b/fuzzers/libfuzzer_libpng_cmpalloc/corpus/not_kitty_icc.png new file mode 100644 index 0000000000..f0c7804d99 Binary files /dev/null and b/fuzzers/libfuzzer_libpng_cmpalloc/corpus/not_kitty_icc.png differ diff --git a/fuzzers/libfuzzer_libpng_cmpalloc/harness.cc b/fuzzers/libfuzzer_libpng_cmpalloc/harness.cc new file mode 100644 index 0000000000..65faff685d --- /dev/null +++ b/fuzzers/libfuzzer_libpng_cmpalloc/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_libpng_cmpalloc/src/fuzzer.rs b/fuzzers/libfuzzer_libpng_cmpalloc/src/fuzzer.rs new file mode 100644 index 0000000000..58215afc63 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_cmpalloc/src/fuzzer.rs @@ -0,0 +1,199 @@ +//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts +//! The example harness is built for libpng. + +use std::{env, path::PathBuf}; + +#[cfg(unix)] +use libafl::{ + bolts::{shmem::UnixShMem, tuples::tuple_list}, + corpus::{ + Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, + QueueCorpusScheduler, + }, + events::setup_restarting_mgr, + executors::{inprocess::InProcessExecutor, Executor, ExitKind}, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, + fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, + inputs::Input, + mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens}, + observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, + stages::mutational::StdMutationalStage, + state::{HasCorpus, HasMetadata, State}, + stats::SimpleStats, + utils::{current_nanos, StdRand}, + Error, +}; + +const MAP_SIZE: usize = 16 * 1024; + +/// We will interact with a C++ target, so use external c functionality +#[cfg(unix)] +extern "C" { + /// int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) + fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32; + + // afl_libfuzzer_init calls LLVMFUzzerInitialize() + fn afl_libfuzzer_init() -> i32; + + static __lafl_edges_map: *mut u8; + static __lafl_cmp_map: *mut u8; + static __lafl_alloc_map: *mut usize; + static __lafl_max_edges_size: u32; +} + +/// The wrapped harness function, calling out to the LLVM-style harness +#[cfg(unix)] +fn harness(_executor: &E, buf: &[u8]) -> ExitKind +where + E: Executor, + I: Input, +{ + // println!("{:?}", buf); + unsafe { + LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len()); + } + ExitKind::Ok +} + +/// The main fn, usually parsing parameters, and starting the fuzzer +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"); +} + +/// Not supported on windows right now +#[cfg(windows)] +fn fuzz(_corpus_dirs: Vec, _objective_dir: PathBuf, _broker_port: u16) -> Result<(), ()> { + todo!("Example not supported on Windows"); +} + +/// The actual fuzzer +#[cfg(unix)] +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::<_, _, UnixShMem, _>(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_from_ptr("edges", __lafl_edges_map, __lafl_max_edges_size as usize) + }); + + // Create an observation channel using the cmp map + let cmps_observer = unsafe { StdMapObserver::new_from_ptr("cmps", __lafl_cmp_map, MAP_SIZE) }; + + // Create an observation channel using the allocations map + let allocs_observer = + unsafe { StdMapObserver::new_from_ptr("allocs", __lafl_alloc_map, MAP_SIZE) }; + + // 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), + MaxMapFeedback::new_with_observer(&cmps_observer), + MaxMapFeedback::new_with_observer(&allocs_observer), + 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()), + ) + }); + + 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 = HavocBytesMutator::default(); + let stage = StdMutationalStage::new(mutator); + + // A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); + let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage)); + + // Create the executor for an in-process function with just one observer for edge coverage + let mut executor = InProcessExecutor::new( + "in-process(edges,cmps,allocs)", + harness, + tuple_list!( + edges_observer, + cmps_observer, + allocs_observer, + TimeObserver::new("time") + ), + &mut state, + &mut restarting_mgr, + )?; + + // The actual target run starts here. + // Call LLVMFUzzerInitialize() if present. + unsafe { + if afl_libfuzzer_init() == -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, + fuzzer.scheduler(), + &corpus_dirs, + ) + .expect(&format!( + "Failed to load initial corpus at {:?}", + &corpus_dirs + )); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr)?; + + // Never reached + Ok(()) +} diff --git a/fuzzers/libfuzzer_libpng_cmpalloc/test.sh b/fuzzers/libfuzzer_libpng_cmpalloc/test.sh new file mode 100755 index 0000000000..f707f77271 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_cmpalloc/test.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +mkdir -p ./crashes + +cargo build --example libfuzzer_libpng --release || exit 1 +cp ../../target/release/examples/libfuzzer_libpng ./.libfuzzer_test.elf + +# The broker +RUST_BACKTRACE=full taskset 0 ./.libfuzzer_test.elf & +# Give the broker time to spawn +sleep 2 +echo "Spawning client" +# The 1st fuzzer client, pin to cpu 0x1 +RUST_BACKTRACE=full taskset 1 ./.libfuzzer_test.elf 2>/dev/null + +killall .libfuzzer_test.elf +rm -rf ./.libfuzzer_test.elf diff --git a/fuzzers/libfuzzer_runtime/rt.c b/fuzzers/libfuzzer_runtime/rt.c index 21c308a8d4..8379132732 100644 --- a/fuzzers/libfuzzer_runtime/rt.c +++ b/fuzzers/libfuzzer_runtime/rt.c @@ -3,7 +3,7 @@ #include #include -#define MAP_SIZE 65536 +#define MAP_SIZE (16*1024) int orig_argc; char **orig_argv; @@ -156,9 +156,10 @@ void *calloc(size_t nmemb, size_t size) { k &= MAP_SIZE - 1; __lafl_alloc_map[k] = MAX(__lafl_alloc_map[k], size); - void *result = realloc(NULL, size); - memset(result, 0, size); - return result; + void *ret = NULL; + posix_memalign(&ret, 1<<6, size); + memset(ret, 0, size); + return ret; } diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 01a78fc319..fbaefb34af 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -35,6 +35,7 @@ std = [] # print, sharedmap, ... support runtime = [] # a runtime for clang inmem-executor anymapdbg = ["serde_json"] # uses serde_json to Debug the anymap trait. Disable for smaller footprint. derive = ["libafl_derive"] # provide derive(SerdeAny) macro. +llmp_small_maps = [] # reduces initial map size for llmp [[example]] name = "llmp_test" @@ -59,9 +60,11 @@ num_enum = "0.5.1" [target.'cfg(unix)'.dependencies] libc = "0.2" # For (*nix) libc nix = "0.20.0" +uds = "0.2.3" [target.'cfg(windows)'.dependencies] -windows = "0.3.1" +windows = "0.4.0" +uuid = { version = "0.8", features = ["v4"] } [target.'cfg(windows)'.build-dependencies] -windows = "0.3.1" +windows = "0.4.0" diff --git a/libafl/build.rs b/libafl/build.rs index 1c6e87c8ce..ff4a614f3d 100644 --- a/libafl/build.rs +++ b/libafl/build.rs @@ -1,9 +1,10 @@ fn main() { #[cfg(target_os = "windows")] windows::build!( - windows::win32::system_services::HANDLE, + windows::win32::system_services::{HANDLE, BOOL, PAGE_TYPE, PSTR, ExitProcess}, windows::win32::windows_programming::CloseHandle, // API needed for the shared memory windows::win32::system_services::{CreateFileMappingA, OpenFileMappingA, MapViewOfFile, UnmapViewOfFile}, + windows::win32::debug::{SetUnhandledExceptionFilter, EXCEPTION_POINTERS, EXCEPTION_RECORD, LPTOP_LEVEL_EXCEPTION_FILTER} ); } diff --git a/libafl/examples/llmp_test/main.rs b/libafl/examples/llmp_test/main.rs index 4dadc2ebf4..fcb03d3872 100644 --- a/libafl/examples/llmp_test/main.rs +++ b/libafl/examples/llmp_test/main.rs @@ -8,14 +8,16 @@ use core::{convert::TryInto, time::Duration}; #[cfg(all(unix, feature = "std"))] use std::{thread, time}; +use libafl::bolts::llmp::Tag; #[cfg(all(unix, feature = "std"))] use libafl::{ bolts::{llmp, shmem::UnixShMem}, Error, }; -const _TAG_SIMPLE_U32_V1: u32 = 0x51300321; -const _TAG_MATH_RESULT_V1: u32 = 0x77474331; +const _TAG_SIMPLE_U32_V1: Tag = 0x51300321; +const _TAG_MATH_RESULT_V1: Tag = 0x77474331; +const _TAG_1MEG_V1: Tag = 0xB1111161; #[cfg(all(unix, feature = "std"))] fn adder_loop(port: u16) -> ! { @@ -35,7 +37,11 @@ fn adder_loop(port: u16) -> ! { current_result = current_result.wrapping_add(u32::from_le_bytes(buf.try_into().unwrap())); } - _ => println!("Adder Client ignored unknown message {}", tag), + _ => println!( + "Adder Client ignored unknown message {} with {} bytes", + tag, + buf.len() + ), }; } @@ -55,6 +61,19 @@ fn adder_loop(port: u16) -> ! { } } +#[cfg(all(unix, feature = "std"))] +fn large_msg_loop(port: u16) -> ! { + let mut client = llmp::LlmpClient::::create_attach_to_tcp(port).unwrap(); + + let meg_buf = [1u8; 1 << 20]; + + loop { + client.send_buf(_TAG_1MEG_V1, &meg_buf).unwrap(); + println!("Sending the next megabyte"); + thread::sleep(time::Duration::from_millis(100)) + } +} + #[cfg(all(unix, feature = "std"))] fn broker_message_hook( client_id: u32, @@ -95,7 +114,7 @@ fn main() { let mode = std::env::args() .nth(1) - .expect("no mode specified, chose 'broker', 'ctr', or 'adder'"); + .expect("no mode specified, chose 'broker', 'ctr', 'adder', or 'large'"); let port: u16 = std::env::args() .nth(2) .unwrap_or("1337".into()) @@ -128,6 +147,9 @@ fn main() { "adder" => { adder_loop(port); } + "large" => { + large_msg_loop(port); + } _ => { println!("No valid mode supplied"); } diff --git a/libafl/src/bolts/llmp.rs b/libafl/src/bolts/llmp.rs index 96cee0bbc6..285907f1b1 100644 --- a/libafl/src/bolts/llmp.rs +++ b/libafl/src/bolts/llmp.rs @@ -92,6 +92,9 @@ use std::{ #[cfg(all(feature = "std", unix))] use libc::c_char; +#[cfg(unix)] +use uds::{UnixListenerExt, UnixSocketAddr, UnixStreamExt}; + #[cfg(unix)] use crate::bolts::os::unix_signals::{c_void, setup_signal_handler, siginfo_t, Handler, Signal}; use crate::{ @@ -102,20 +105,26 @@ use crate::{ use super::shmem::HasFd; /// We'll start off with 256 megabyte maps per fuzzer client -const LLMP_PREF_INITIAL_MAP_SIZE: usize = 1 << 28; +#[cfg(not(feature = "llmp_small_maps"))] +const LLMP_CFG_INITIAL_MAP_SIZE: usize = 1 << 28; +/// If llmp_small_maps is set, we start off with 1 meg. +#[cfg(feature = "llmp_small_maps")] +const LLMP_CFG_INITIAL_MAP_SIZE: usize = 1 << 20; /// What byte count to align messages to /// LlmpMsg sizes (including header) will always be rounded up to be a multiple of this value -const LLMP_PREF_ALIGNNMENT: usize = 64; +const LLMP_CFG_ALIGNNMENT: usize = 64; /// A msg fresh from the press: No tag got sent by the user yet -const LLMP_TAG_UNSET: u32 = 0xDEADAF; +const LLMP_TAG_UNSET: Tag = 0xDEADAF; /// This message should not exist yet. Some bug in unsafe code! -const LLMP_TAG_UNINITIALIZED: u32 = 0xA143AF11; -/// The end of page mesasge +const LLMP_TAG_UNINITIALIZED: Tag = 0xA143AF11; +/// The end of page message /// When receiving this, a new sharedmap needs to be allocated. -const LLMP_TAG_END_OF_PAGE: u32 = 0xAF1E0F1; -/// A new client for this broekr got added. -const LLMP_TAG_NEW_SHM_CLIENT: u32 = 0xC11E471; +const LLMP_TAG_END_OF_PAGE: Tag = 0xAF1E0F1; +/// A new client for this broker got added. +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; /// An env var of this value indicates that the set value was a NULL PTR const _NULL_ENV_STR: &str = "_NULL"; @@ -197,19 +206,19 @@ unsafe fn llmp_msg_in_page(page: *const LlmpPage, msg: *const LlmpMsg) -> bool { && (page as *const u8).add((*page).size_total) > msg as *const u8 } -/// allign to LLMP_PREF_ALIGNNMENT=64 bytes +/// allign to LLMP_CFG_ALIGNNMENT=64 bytes #[inline] const fn llmp_align(to_align: usize) -> usize { // check if we need to align first - if LLMP_PREF_ALIGNNMENT == 0 { + if LLMP_CFG_ALIGNNMENT == 0 { return to_align; } // Then do the alignment - let modulo = to_align % LLMP_PREF_ALIGNNMENT; + let modulo = to_align % LLMP_CFG_ALIGNNMENT; if modulo == 0 { to_align } else { - to_align + LLMP_PREF_ALIGNNMENT - modulo + to_align + LLMP_CFG_ALIGNNMENT - modulo } } @@ -233,7 +242,7 @@ fn msg_offset_from_env(env_name: &str) -> Result, Error> { fn new_map_size(max_alloc: usize) -> usize { max( max_alloc * 2 + EOP_MSG_SIZE + LLMP_PAGE_HEADER_LEN, - LLMP_PREF_INITIAL_MAP_SIZE, + LLMP_CFG_INITIAL_MAP_SIZE, ) .next_power_of_two() } @@ -447,7 +456,7 @@ where { #[cfg(all(feature = "std", unix))] pub fn on_domain_socket(filename: &str) -> Result { - match UnixListener::bind(filename) { + match UnixListener::bind_unix_addr(&UnixSocketAddr::new(filename).unwrap()) { Ok(listener) => { dbg!("We're the broker"); let mut broker = LlmpBroker::new()?; @@ -541,7 +550,7 @@ where last_msg_sent: ptr::null_mut(), out_maps: vec![LlmpSharedMap::new( 0, - SH::new_map(new_map_size(LLMP_PREF_INITIAL_MAP_SIZE))?, + SH::new_map(new_map_size(LLMP_CFG_INITIAL_MAP_SIZE))?, )], // drop pages to the broker if it already read them keep_pages_forever, @@ -571,7 +580,7 @@ where pub fn to_env(&self, env_name: &str) -> Result<(), Error> { let current_out_map = self.out_maps.last().unwrap(); current_out_map.shmem.write_to_env(env_name)?; - current_out_map.msg_to_env(self.last_msg_sent, env_name) + unsafe { current_out_map.msg_to_env(self.last_msg_sent, env_name) } } /// Waits for this sender to be save to unmap. @@ -862,6 +871,7 @@ where tag ))); } + unsafe { let msg = self.alloc_next(buf.len())?; (*msg).tag = tag; @@ -927,7 +937,7 @@ where pub fn to_env(&self, env_name: &str) -> Result<(), Error> { let current_out_map = &self.current_recv_map; current_out_map.shmem.write_to_env(env_name)?; - current_out_map.msg_to_env(self.last_msg_recvd, env_name) + unsafe { current_out_map.msg_to_env(self.last_msg_recvd, env_name) } } /// Create a Receiver, reattaching to an existing sender map. @@ -987,6 +997,11 @@ where // Handle special, LLMP internal, messages. match (*msg).tag { LLMP_TAG_UNSET => panic!("BUG: Read unallocated msg"), + LLMP_TAG_EXITING => { + // The other side is done. + assert_eq!((*msg).buf_len, 0); + return Err(Error::ShuttingDown); + } LLMP_TAG_END_OF_PAGE => { #[cfg(feature = "std")] dbg!("Got end of page, allocing next"); @@ -1195,14 +1210,16 @@ where /// Store this msg offset to env_name + _OFFSET env variable. /// It can be restored using msg_from_env with the same env_name later. + /// # Safety + /// This function will dereference the msg ptr, make sure it's valid. #[cfg(feature = "std")] - pub fn msg_to_env(&self, msg: *const LlmpMsg, map_env_name: &str) -> Result<(), Error> { + pub unsafe fn msg_to_env(&self, msg: *const LlmpMsg, map_env_name: &str) -> Result<(), Error> { if msg.is_null() { env::set_var(&format!("{}_OFFSET", map_env_name), _NULL_ENV_STR) } else { env::set_var( &format!("{}_OFFSET", map_env_name), - format!("{}", unsafe { self.msg_to_offset(msg) }?), + format!("{}", self.msg_to_offset(msg)?), ) }; Ok(()) @@ -1319,9 +1336,8 @@ where msg.copy_to_nonoverlapping(out, size_of::() + (*msg).buf_len_padded as usize); (*out).buf_len_padded = actual_size; /* We need to replace the message ID with our own */ - match self.llmp_out.send(out) { - Err(e) => panic!("Error sending msg: {:?}", e), - _ => (), + if let Err(e) = self.llmp_out.send(out) { + panic!("Error sending msg: {:?}", e) }; self.llmp_out.last_msg_sent = out; Ok(()) @@ -1389,6 +1405,9 @@ where None => (), } } + self.llmp_out + .send_buf(LLMP_TAG_EXITING, &[]) + .expect("Error when shutting down broker: Could not send LLMP_TAG_EXITING msg."); } /// Broadcasts the given buf to all lients @@ -1422,7 +1441,7 @@ where // Tcp out map sends messages from background thread tcp server to foreground client let tcp_out_map = LlmpSharedMap::new( llmp_tcp_id, - SH::new_map(new_map_size(LLMP_PREF_INITIAL_MAP_SIZE))?, + SH::new_map(new_map_size(LLMP_CFG_INITIAL_MAP_SIZE))?, ); let tcp_out_map_str = tcp_out_map.shmem.shm_str(); let tcp_out_map_size = tcp_out_map.shmem.map().len(); @@ -1465,7 +1484,7 @@ where (*msg).tag = LLMP_TAG_NEW_SHM_CLIENT; let pageinfo = (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo; (*pageinfo).shm_str = new_client_map_str; - (*pageinfo).map_size = LLMP_PREF_INITIAL_MAP_SIZE; + (*pageinfo).map_size = LLMP_CFG_INITIAL_MAP_SIZE; match new_client_sender.send(msg) { Ok(()) => (), Err(e) => println!("Error forwarding client on map: {:?}", e), @@ -1481,10 +1500,12 @@ where .to_string_lossy() .into_owned() .parse() - .expect(&format!( - "ShmId is not a valid int file descriptor: {:?}", - broadcast_str_initial - )); + .unwrap_or_else(|_| { + panic!( + "ShmId is not a valid int file descriptor: {:?}", + broadcast_str_initial + ) + }); match sendmsg( stream.as_raw_fd(), @@ -1528,7 +1549,7 @@ where let pageinfo = (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo; (*pageinfo).shm_str = fdstr; - (*pageinfo).map_size = LLMP_PREF_INITIAL_MAP_SIZE; + (*pageinfo).map_size = LLMP_CFG_INITIAL_MAP_SIZE; match new_client_sender.send(msg) { Ok(()) => (), Err(e) => { @@ -1739,7 +1760,7 @@ where last_msg_sent: ptr::null_mut(), out_maps: vec![LlmpSharedMap::new( 0, - SH::new_map(new_map_size(LLMP_PREF_INITIAL_MAP_SIZE))?, + SH::new_map(new_map_size(LLMP_CFG_INITIAL_MAP_SIZE))?, )], // drop pages to the broker if it already read them keep_pages_forever: false, @@ -1840,7 +1861,7 @@ where let ret = Self::new(LlmpSharedMap::existing(SH::existing_from_shm_slice( &new_broker_map_str, - LLMP_PREF_INITIAL_MAP_SIZE, + LLMP_CFG_INITIAL_MAP_SIZE, )?))?; stream.write_all(ret.sender.out_maps.first().unwrap().shmem.shm_slice())?; @@ -1858,7 +1879,7 @@ where #[cfg(all(unix, feature = "std"))] /// Create a LlmpClient, getting the ID from a given filename pub fn create_attach_to_unix(filename: &str) -> Result { - let stream = UnixStream::connect(filename)?; + let stream = UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(filename).unwrap())?; println!("Connected to socket {}", filename); let mut buf = [0u8; 5]; @@ -1884,7 +1905,7 @@ where let ret = Self::new(LlmpSharedMap::existing(SH::existing_from_shm_slice( &fdstr, - LLMP_PREF_INITIAL_MAP_SIZE, + LLMP_CFG_INITIAL_MAP_SIZE, )?))?; match sendmsg( diff --git a/libafl/src/bolts/os/mod.rs b/libafl/src/bolts/os/mod.rs index 0a1b95601f..7a45acb215 100644 --- a/libafl/src/bolts/os/mod.rs +++ b/libafl/src/bolts/os/mod.rs @@ -1,2 +1,4 @@ #[cfg(unix)] pub mod unix_signals; +#[cfg(windows)] +pub mod windows_exceptions; diff --git a/libafl/src/bolts/os/unix_signals.rs b/libafl/src/bolts/os/unix_signals.rs index 737cf533fa..8248306a45 100644 --- a/libafl/src/bolts/os/unix_signals.rs +++ b/libafl/src/bolts/os/unix_signals.rs @@ -22,7 +22,7 @@ use crate::Error; pub use libc::{c_void, siginfo_t}; -#[derive(IntoPrimitive, TryFromPrimitive, Hash, Clone, Copy)] +#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)] #[repr(i32)] pub enum Signal { SigAbort = SIGABRT, diff --git a/libafl/src/bolts/os/windows_exceptions.rs b/libafl/src/bolts/os/windows_exceptions.rs new file mode 100644 index 0000000000..ed43a04c00 --- /dev/null +++ b/libafl/src/bolts/os/windows_exceptions.rs @@ -0,0 +1,332 @@ +pub use crate::bolts::bindings::windows::win32::debug::EXCEPTION_POINTERS; + +use crate::{bolts::bindings::windows::win32::debug::SetUnhandledExceptionFilter, Error}; + +use alloc::vec::Vec; +use core::{ + cell::UnsafeCell, + convert::TryFrom, + fmt::{self, Display, Formatter}, + ptr::write_volatile, + sync::atomic::{compiler_fence, Ordering}, +}; +use std::os::raw::{c_long, c_void}; + +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +const EXCEPTION_CONTINUE_EXECUTION: c_long = -1; +//const EXCEPTION_CONTINUE_SEARCH: c_long = 0; +const EXCEPTION_EXECUTE_HANDLER: c_long = 1; + +// From https://github.com/wine-mirror/wine/blob/master/include/winnt.h#L611 +pub const STATUS_WAIT_0: u32 = 0x00000000; +pub const STATUS_ABANDONED_WAIT_0: u32 = 0x00000080; +pub const STATUS_USER_APC: u32 = 0x000000C0; +pub const STATUS_TIMEOUT: u32 = 0x00000102; +pub const STATUS_PENDING: u32 = 0x00000103; +pub const STATUS_SEGMENT_NOTIFICATION: u32 = 0x40000005; +pub const STATUS_FATAL_APP_EXIT: u32 = 0x40000015; +pub const STATUS_GUARD_PAGE_VIOLATION: u32 = 0x80000001; +pub const STATUS_DATATYPE_MISALIGNMENT: u32 = 0x80000002; +pub const STATUS_BREAKPOINT: u32 = 0x80000003; +pub const STATUS_SINGLE_STEP: u32 = 0x80000004; +pub const STATUS_LONGJUMP: u32 = 0x80000026; +pub const STATUS_UNWIND_CONSOLIDATE: u32 = 0x80000029; +pub const STATUS_ACCESS_VIOLATION: u32 = 0xC0000005; +pub const STATUS_IN_PAGE_ERROR: u32 = 0xC0000006; +pub const STATUS_INVALID_HANDLE: u32 = 0xC0000008; +pub const STATUS_NO_MEMORY: u32 = 0xC0000017; +pub const STATUS_ILLEGAL_INSTRUCTION: u32 = 0xC000001D; +pub const STATUS_NONCONTINUABLE_EXCEPTION: u32 = 0xC0000025; +pub const STATUS_INVALID_DISPOSITION: u32 = 0xC0000026; +pub const STATUS_ARRAY_BOUNDS_EXCEEDED: u32 = 0xC000008C; +pub const STATUS_FLOAT_DENORMAL_OPERAND: u32 = 0xC000008D; +pub const STATUS_FLOAT_DIVIDE_BY_ZERO: u32 = 0xC000008E; +pub const STATUS_FLOAT_INEXACT_RESULT: u32 = 0xC000008F; +pub const STATUS_FLOAT_INVALID_OPERATION: u32 = 0xC0000090; +pub const STATUS_FLOAT_OVERFLOW: u32 = 0xC0000091; +pub const STATUS_FLOAT_STACK_CHECK: u32 = 0xC0000092; +pub const STATUS_FLOAT_UNDERFLOW: u32 = 0xC0000093; +pub const STATUS_INTEGER_DIVIDE_BY_ZERO: u32 = 0xC0000094; +pub const STATUS_INTEGER_OVERFLOW: u32 = 0xC0000095; +pub const STATUS_PRIVILEGED_INSTRUCTION: u32 = 0xC0000096; +pub const STATUS_STACK_OVERFLOW: u32 = 0xC00000FD; +pub const STATUS_DLL_NOT_FOUND: u32 = 0xC0000135; +pub const STATUS_ORDINAL_NOT_FOUND: u32 = 0xC0000138; +pub const STATUS_ENTRYPOINT_NOT_FOUND: u32 = 0xC0000139; +pub const STATUS_CONTROL_C_EXIT: u32 = 0xC000013A; +pub const STATUS_DLL_INIT_FAILED: u32 = 0xC0000142; +pub const STATUS_FLOAT_MULTIPLE_FAULTS: u32 = 0xC00002B4; +pub const STATUS_FLOAT_MULTIPLE_TRAPS: u32 = 0xC00002B5; +pub const STATUS_REG_NAT_CONSUMPTION: u32 = 0xC00002C9; +pub const STATUS_HEAP_CORRUPTION: u32 = 0xC0000374; +pub const STATUS_STACK_BUFFER_OVERRUN: u32 = 0xC0000409; +pub const STATUS_INVALID_CRUNTIME_PARAMETER: u32 = 0xC0000417; +pub const STATUS_ASSERTION_FAILURE: u32 = 0xC0000420; +pub const STATUS_SXS_EARLY_DEACTIVATION: u32 = 0xC015000F; +pub const STATUS_SXS_INVALID_DEACTIVATION: u32 = 0xC0150010; + +#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)] +#[repr(u32)] +pub enum ExceptionCode { + // From https://docs.microsoft.com/en-us/windows/win32/debug/getexceptioncode + AccessViolation = STATUS_ACCESS_VIOLATION, + ArrayBoundsExceeded = STATUS_ARRAY_BOUNDS_EXCEEDED, + Breakpoint = STATUS_BREAKPOINT, + DatatypeMisalignment = STATUS_DATATYPE_MISALIGNMENT, + FltDenormalOperand = STATUS_FLOAT_DENORMAL_OPERAND, + FltDivideByZero = STATUS_FLOAT_DIVIDE_BY_ZERO, + FltInexactResult = STATUS_FLOAT_INEXACT_RESULT, + FltInvalidOperation = STATUS_FLOAT_INVALID_OPERATION, + FltOverflow = STATUS_FLOAT_OVERFLOW, + FltStackCheck = STATUS_FLOAT_STACK_CHECK, + FltUnderflow = STATUS_FLOAT_UNDERFLOW, + GuardPageViolation = STATUS_GUARD_PAGE_VIOLATION, + IllegalInstruction = STATUS_ILLEGAL_INSTRUCTION, + InPageError = STATUS_IN_PAGE_ERROR, + IntegerDivideByZero = STATUS_INTEGER_DIVIDE_BY_ZERO, + IntegerOverflow = STATUS_INTEGER_OVERFLOW, + InvalidDisposition = STATUS_INVALID_DISPOSITION, + InvalidHandle = STATUS_INVALID_HANDLE, + NoncontinuableException = STATUS_NONCONTINUABLE_EXCEPTION, + PrivilegedInstruction = STATUS_PRIVILEGED_INSTRUCTION, + SingleStep = STATUS_SINGLE_STEP, + StackOverflow = STATUS_STACK_OVERFLOW, + UnwindConsolidate = STATUS_UNWIND_CONSOLIDATE, + // Addition exceptions + Wait0 = STATUS_WAIT_0, + AbandonedWait0 = STATUS_ABANDONED_WAIT_0, + UserAPC = STATUS_USER_APC, + Timeout = STATUS_TIMEOUT, + Pending = STATUS_PENDING, + SegmentNotification = STATUS_SEGMENT_NOTIFICATION, + FatalAppExit = STATUS_FATAL_APP_EXIT, + Longjump = STATUS_LONGJUMP, + DLLNotFound = STATUS_DLL_NOT_FOUND, + OrdinalNotFound = STATUS_ORDINAL_NOT_FOUND, + EntryPointNotFound = STATUS_ENTRYPOINT_NOT_FOUND, + ControlCExit = STATUS_CONTROL_C_EXIT, + DllInitFailed = STATUS_DLL_INIT_FAILED, + FltMultipleFaults = STATUS_FLOAT_MULTIPLE_FAULTS, + FltMultipleTraps = STATUS_FLOAT_MULTIPLE_TRAPS, + RegNatConsumption = STATUS_REG_NAT_CONSUMPTION, + HeapCorruption = STATUS_HEAP_CORRUPTION, + StackBufferOverrun = STATUS_STACK_BUFFER_OVERRUN, + InvalidCRuntimeParameter = STATUS_INVALID_CRUNTIME_PARAMETER, + AssertionFailure = STATUS_ASSERTION_FAILURE, + SXSEarlyDeactivation = STATUS_SXS_EARLY_DEACTIVATION, + SXSInvalidDeactivation = STATUS_SXS_INVALID_DEACTIVATION, +} + +pub static CRASH_EXCEPTIONS: &[ExceptionCode] = &[ + ExceptionCode::AccessViolation, + ExceptionCode::ArrayBoundsExceeded, + ExceptionCode::FltDivideByZero, + ExceptionCode::GuardPageViolation, + ExceptionCode::IllegalInstruction, + ExceptionCode::InPageError, + ExceptionCode::IntegerDivideByZero, + ExceptionCode::InvalidHandle, + ExceptionCode::NoncontinuableException, + ExceptionCode::PrivilegedInstruction, + ExceptionCode::StackOverflow, + ExceptionCode::HeapCorruption, + ExceptionCode::StackBufferOverrun, + ExceptionCode::AssertionFailure, +]; + +impl PartialEq for ExceptionCode { + fn eq(&self, other: &Self) -> bool { + *self as u32 == *other as u32 + } +} + +impl Eq for ExceptionCode {} + +unsafe impl Sync for ExceptionCode {} + +impl Display for ExceptionCode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + match self { + ExceptionCode::AccessViolation => write!(f, "STATUS_ACCESS_VIOLATION")?, + ExceptionCode::ArrayBoundsExceeded => write!(f, "STATUS_ARRAY_BOUNDS_EXCEEDED")?, + ExceptionCode::Breakpoint => write!(f, "STATUS_BREAKPOINT")?, + ExceptionCode::DatatypeMisalignment => write!(f, "STATUS_DATATYPE_MISALIGNMENT")?, + ExceptionCode::FltDenormalOperand => write!(f, "STATUS_FLOAT_DENORMAL_OPERAND")?, + ExceptionCode::FltDivideByZero => write!(f, "STATUS_FLOAT_DIVIDE_BY_ZERO")?, + ExceptionCode::FltInexactResult => write!(f, "STATUS_FLOAT_INEXACT_RESULT")?, + ExceptionCode::FltInvalidOperation => write!(f, "STATUS_FLOAT_INVALID_OPERATION")?, + ExceptionCode::FltOverflow => write!(f, "STATUS_FLOAT_OVERFLOW")?, + ExceptionCode::FltStackCheck => write!(f, "STATUS_FLOAT_STACK_CHECK")?, + ExceptionCode::FltUnderflow => write!(f, "STATUS_FLOAT_UNDERFLOW")?, + ExceptionCode::GuardPageViolation => write!(f, "STATUS_GUARD_PAGE_VIOLATION")?, + ExceptionCode::IllegalInstruction => write!(f, "STATUS_ILLEGAL_INSTRUCTION")?, + ExceptionCode::InPageError => write!(f, "STATUS_IN_PAGE_ERROR")?, + ExceptionCode::IntegerDivideByZero => write!(f, "STATUS_INTEGER_DIVIDE_BY_ZERO")?, + ExceptionCode::IntegerOverflow => write!(f, "STATUS_INTEGER_OVERFLOW")?, + ExceptionCode::InvalidDisposition => write!(f, "STATUS_INVALID_DISPOSITION")?, + ExceptionCode::InvalidHandle => write!(f, "STATUS_INVALID_HANDLE")?, + ExceptionCode::NoncontinuableException => write!(f, "STATUS_NONCONTINUABLE_EXCEPTION")?, + ExceptionCode::PrivilegedInstruction => write!(f, "STATUS_PRIVILEGED_INSTRUCTION")?, + ExceptionCode::SingleStep => write!(f, "STATUS_SINGLE_STEP")?, + ExceptionCode::StackOverflow => write!(f, "STATUS_STACK_OVERFLOW")?, + ExceptionCode::UnwindConsolidate => write!(f, "STATUS_UNWIND_CONSOLIDATE")?, + ExceptionCode::Wait0 => write!(f, "STATUS_WAIT_0")?, + ExceptionCode::AbandonedWait0 => write!(f, "STATUS_ABANDONED_WAIT_0")?, + ExceptionCode::UserAPC => write!(f, "STATUS_USER_APC")?, + ExceptionCode::Timeout => write!(f, "STATUS_TIMEOUT")?, + ExceptionCode::Pending => write!(f, "STATUS_PENDING")?, + ExceptionCode::SegmentNotification => write!(f, "STATUS_SEGMENT_NOTIFICATION")?, + ExceptionCode::FatalAppExit => write!(f, "STATUS_FATAL_APP_EXIT")?, + ExceptionCode::Longjump => write!(f, "STATUS_LONGJUMP")?, + ExceptionCode::DLLNotFound => write!(f, "STATUS_DLL_NOT_FOUND")?, + ExceptionCode::OrdinalNotFound => write!(f, "STATUS_ORDINAL_NOT_FOUND")?, + ExceptionCode::EntryPointNotFound => write!(f, "STATUS_ENTRYPOINT_NOT_FOUND")?, + ExceptionCode::ControlCExit => write!(f, "STATUS_CONTROL_C_EXIT")?, + ExceptionCode::DllInitFailed => write!(f, "STATUS_DLL_INIT_FAILED")?, + ExceptionCode::FltMultipleFaults => write!(f, "STATUS_FLOAT_MULTIPLE_FAULTS")?, + ExceptionCode::FltMultipleTraps => write!(f, "STATUS_FLOAT_MULTIPLE_TRAPS")?, + ExceptionCode::RegNatConsumption => write!(f, "STATUS_REG_NAT_CONSUMPTION")?, + ExceptionCode::HeapCorruption => write!(f, "STATUS_HEAP_CORRUPTION")?, + ExceptionCode::StackBufferOverrun => write!(f, "STATUS_STACK_BUFFER_OVERRUN")?, + ExceptionCode::InvalidCRuntimeParameter => { + write!(f, "STATUS_INVALID_CRUNTIME_PARAMETER")? + } + ExceptionCode::AssertionFailure => write!(f, "STATUS_ASSERTION_FAILURE")?, + ExceptionCode::SXSEarlyDeactivation => write!(f, "STATUS_SXS_EARLY_DEACTIVATION")?, + ExceptionCode::SXSInvalidDeactivation => write!(f, "STATUS_SXS_INVALID_DEACTIVATION")?, + }; + + Ok(()) + } +} + +pub static EXCEPTION_CODES_MAPPING: [ExceptionCode; 45] = [ + ExceptionCode::AccessViolation, + ExceptionCode::ArrayBoundsExceeded, + ExceptionCode::Breakpoint, + ExceptionCode::DatatypeMisalignment, + ExceptionCode::FltDenormalOperand, + ExceptionCode::FltDivideByZero, + ExceptionCode::FltInexactResult, + ExceptionCode::FltInvalidOperation, + ExceptionCode::FltOverflow, + ExceptionCode::FltStackCheck, + ExceptionCode::FltUnderflow, + ExceptionCode::GuardPageViolation, + ExceptionCode::IllegalInstruction, + ExceptionCode::InPageError, + ExceptionCode::IntegerDivideByZero, + ExceptionCode::IntegerOverflow, + ExceptionCode::InvalidDisposition, + ExceptionCode::InvalidHandle, + ExceptionCode::NoncontinuableException, + ExceptionCode::PrivilegedInstruction, + ExceptionCode::SingleStep, + ExceptionCode::StackOverflow, + ExceptionCode::UnwindConsolidate, + ExceptionCode::Wait0, + ExceptionCode::AbandonedWait0, + ExceptionCode::UserAPC, + ExceptionCode::Timeout, + ExceptionCode::Pending, + ExceptionCode::SegmentNotification, + ExceptionCode::FatalAppExit, + ExceptionCode::Longjump, + ExceptionCode::DLLNotFound, + ExceptionCode::OrdinalNotFound, + ExceptionCode::EntryPointNotFound, + ExceptionCode::ControlCExit, + ExceptionCode::DllInitFailed, + ExceptionCode::FltMultipleFaults, + ExceptionCode::FltMultipleTraps, + ExceptionCode::RegNatConsumption, + ExceptionCode::HeapCorruption, + ExceptionCode::StackBufferOverrun, + ExceptionCode::InvalidCRuntimeParameter, + ExceptionCode::AssertionFailure, + ExceptionCode::SXSEarlyDeactivation, + ExceptionCode::SXSInvalidDeactivation, +]; + +pub trait Handler { + /// Handle an exception + fn handle( + &mut self, + exception_code: ExceptionCode, + exception_pointers: *mut EXCEPTION_POINTERS, + ); + /// Return a list of exceptions to handle + fn exceptions(&self) -> Vec; +} + +struct HandlerHolder { + handler: UnsafeCell<*mut dyn Handler>, +} + +unsafe impl Send for HandlerHolder {} + +/// Keep track of which handler is registered for which exception +static mut EXCEPTION_HANDLERS: [Option; 64] = [ + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, +]; + +type NativeHandlerType = extern "system" fn(*mut EXCEPTION_POINTERS) -> c_long; +static mut PREVIOUS_HANDLER: Option = None; + +/// Internal function that is being called whenever an exception arrives (stdcall). +unsafe extern "system" fn handle_exception(exception_pointers: *mut EXCEPTION_POINTERS) -> c_long { + let code = exception_pointers + .as_mut() + .unwrap() + .exception_record + .as_mut() + .unwrap() + .exception_code; + let exception_code = ExceptionCode::try_from(code).unwrap(); + let index = EXCEPTION_CODES_MAPPING + .iter() + .position(|x| *x == exception_code) + .unwrap(); + let ret = match &EXCEPTION_HANDLERS[index] { + Some(handler_holder) => { + let handler = &mut **handler_holder.handler.get(); + handler.handle(exception_code, exception_pointers); + EXCEPTION_EXECUTE_HANDLER + } + None => EXCEPTION_EXECUTE_HANDLER, + }; + if let Some(prev_handler) = PREVIOUS_HANDLER { + prev_handler(exception_pointers) + } else { + ret + } +} + +/// Setup Win32 exception handlers in a somewhat rusty way. +pub unsafe fn setup_exception_handler(handler: &mut T) -> Result<(), Error> { + let exceptions = handler.exceptions(); + for exception_code in exceptions { + let index = EXCEPTION_CODES_MAPPING + .iter() + .position(|x| *x == exception_code) + .unwrap(); + write_volatile( + &mut EXCEPTION_HANDLERS[index], + Some(HandlerHolder { + handler: UnsafeCell::new(handler as *mut dyn Handler), + }), + ); + } + compiler_fence(Ordering::SeqCst); + + if let Some(prev) = SetUnhandledExceptionFilter(Some(core::mem::transmute( + handle_exception as *const c_void, + ))) { + PREVIOUS_HANDLER = Some(core::mem::transmute(prev as *const c_void)); + } + Ok(()) +} diff --git a/libafl/src/bolts/ownedref.rs b/libafl/src/bolts/ownedref.rs index 968b7bc4ce..979f3fb00d 100644 --- a/libafl/src/bolts/ownedref.rs +++ b/libafl/src/bolts/ownedref.rs @@ -1,6 +1,11 @@ +//! Wrappers that abstracts references (or pointers) and owned data accesses. +// The serialization is towards owned, allowing to serialize pointers without troubles. + use alloc::{boxed::Box, vec::Vec}; +use core::{clone::Clone, fmt::Debug}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +/// Wrap a reference and convert to a Box on serialize #[derive(Clone, Debug)] pub enum Ptr<'a, T: 'a + ?Sized> { Ref(&'a T), @@ -40,6 +45,8 @@ impl<'a, T: Sized> AsRef for Ptr<'a, T> { } } +/// Wrap a mutable reference and convert to a Box on serialize +#[derive(Debug)] pub enum PtrMut<'a, T: 'a + ?Sized> { Ref(&'a mut T), Owned(Box), @@ -87,6 +94,8 @@ impl<'a, T: Sized> AsMut for PtrMut<'a, T> { } } +/// Wrap a slice and convert to a Vec on serialize +#[derive(Clone, Debug)] pub enum Slice<'a, T: 'a + Sized> { Ref(&'a [T]), Owned(Vec), @@ -125,6 +134,8 @@ impl<'a, T: Sized> Slice<'a, T> { } } +/// Wrap a mutable slice and convert to a Vec on serialize +#[derive(Debug)] pub enum SliceMut<'a, T: 'a + Sized> { Ref(&'a mut [T]), Owned(Vec), @@ -170,6 +181,7 @@ impl<'a, T: Sized> SliceMut<'a, T> { } } +/// Wrap a C-style pointer and convert to a Box on serialize #[derive(Clone, Debug)] pub enum Cptr { Cptr(*const T), @@ -206,6 +218,8 @@ impl AsRef for Cptr { } } +/// Wrap a C-style mutable pointer and convert to a Box on serialize +#[derive(Clone, Debug)] pub enum CptrMut { Cptr(*mut T), Owned(Box), @@ -250,6 +264,8 @@ impl AsMut for CptrMut { } } +/// Wrap a C-style pointer to an array (with size= and convert to a Vec on serialize +#[derive(Clone, Debug)] pub enum Array { Cptr((*const T, usize)), Owned(Vec), @@ -285,6 +301,7 @@ impl Array { } } +/// Wrap a C-style mutable pointer to an array (with size= and convert to a Vec on serialize #[derive(Clone, Debug)] pub enum ArrayMut { Cptr((*mut T, usize)), diff --git a/libafl/src/bolts/serdeany.rs b/libafl/src/bolts/serdeany.rs index 01a2a2e85f..50e3d6034f 100644 --- a/libafl/src/bolts/serdeany.rs +++ b/libafl/src/bolts/serdeany.rs @@ -1,4 +1,5 @@ //! Poor-rust-man's downcasts for stuff we send over the wire (or shared maps) + use serde::{Deserialize, Deserializer, Serialize, Serializer}; use alloc::boxed::Box; diff --git a/libafl/src/bolts/shmem.rs b/libafl/src/bolts/shmem.rs index 4803654d43..a4d55d0100 100644 --- a/libafl/src/bolts/shmem.rs +++ b/libafl/src/bolts/shmem.rs @@ -169,7 +169,6 @@ pub mod unix_shmem { #[cfg(target_os = "android")] unsafe fn shmctl(__shmid: c_int, __cmd: c_int, _buf: *mut shmid_ds) -> c_int { - println!("shmctl(__shmid: {})", __shmid); if __cmd == 0 { let length = ioctl(__shmid, ASHMEM_GET_SIZE); @@ -188,7 +187,10 @@ pub mod unix_shmem { #[cfg(target_os = "android")] unsafe fn shmget(__key: c_int, __size: c_ulong, __shmflg: c_int) -> c_int { - let path = CString::new(ASHMEM_DEVICE).expect("CString::new failed!"); + let boot_id = std::fs::read_to_string("/proc/sys/kernel/random/boot_id").unwrap(); + + let path = CString::new(format!("{}{}", ASHMEM_DEVICE, boot_id).trim()) + .expect("CString::new failed!"); let fd = open(path.as_ptr(), O_RDWR); let mut ourkey: [c_char; 20] = [0; 20]; @@ -196,10 +198,9 @@ pub mod unix_shmem { ourkey.as_mut_ptr() as *mut c_char, size_of::<[c_char; 20]>() as c_ulong, b"%d\x00" as *const u8 as *const c_char, - __key, + if __key == 0 { fd } else { __key }, ); - println!("ourkey: {:?}", ourkey); if ioctl(fd, ASHMEM_SET_NAME, &ourkey) != 0 { close(fd); return 0; @@ -210,14 +211,11 @@ pub mod unix_shmem { return 0; }; - println!("shmget returns {}", fd); fd } #[cfg(target_os = "android")] unsafe fn shmat(__shmid: c_int, __shmaddr: *const c_void, __shmflg: c_int) -> *mut c_void { - println!("shmat(__shmid: {})", __shmid); - let size = ioctl(__shmid, ASHMEM_GET_SIZE); if size < 0 { return 0 as *mut c_void; @@ -235,7 +233,6 @@ pub mod unix_shmem { return 0 as *mut c_void; } - println!("shmat() = {:?}", ptr); ptr } @@ -321,14 +318,14 @@ pub mod unix_shmem { impl Drop for UnixShMem { fn drop(&mut self) { unsafe { - afl_shmem_deinit(self); + unix_shmem_deinit(self); } } } /// Create an uninitialized shmap #[cfg(unix)] - const fn afl_shmem_unitialized() -> UnixShMem { + const fn unix_shmem_unitialized() -> UnixShMem { UnixShMem { shm_str: [0; 20], shm_id: -1, @@ -340,8 +337,8 @@ pub mod unix_shmem { #[cfg(unix)] impl UnixShMem { pub fn from_str(shm_str: &CStr, map_size: usize) -> Result { - let mut ret = afl_shmem_unitialized(); - let map = unsafe { afl_shmem_by_str(&mut ret, shm_str, map_size) }; + let mut ret = unix_shmem_unitialized(); + let map = unsafe { unix_shmem_by_str(&mut ret, shm_str, map_size) }; if !map.is_null() { Ok(ret) } else { @@ -353,8 +350,8 @@ pub mod unix_shmem { } pub fn new(map_size: usize) -> Result { - let mut ret = afl_shmem_unitialized(); - let map = unsafe { afl_shmem_init(&mut ret, map_size) }; + let mut ret = unix_shmem_unitialized(); + let map = unsafe { unix_shmem_init(&mut ret, map_size) }; if !map.is_null() { Ok(ret) } else { @@ -367,7 +364,7 @@ pub mod unix_shmem { } /// Deinitialize this shmem instance - unsafe fn afl_shmem_deinit(shm: *mut UnixShMem) { + unsafe fn unix_shmem_deinit(shm: *mut UnixShMem) { if shm.is_null() || (*shm).map.is_null() { /* Serialized map id */ // Not set or not initialized; @@ -380,7 +377,7 @@ pub mod unix_shmem { /// Functions to create Shared memory region, for observation channels and /// opening inputs and stuff. - unsafe fn afl_shmem_init(shm: *mut UnixShMem, map_size: usize) -> *mut c_uchar { + unsafe fn unix_shmem_init(shm: *mut UnixShMem, map_size: usize) -> *mut c_uchar { (*shm).map_size = map_size; (*shm).map = ptr::null_mut(); (*shm).shm_id = shmget( @@ -412,7 +409,7 @@ pub mod unix_shmem { } /// Uses a shmap id string to open a shared map - unsafe fn afl_shmem_by_str( + unsafe fn unix_shmem_by_str( shm: *mut UnixShMem, shm_str: &CStr, map_size: usize, @@ -446,18 +443,140 @@ pub mod unix_shmem { #[cfg(all(feature = "std", windows))] pub mod shmem { - //TODO use super::ShMem; + use super::ShMem; + use crate::{ + bolts::bindings::{ + windows::win32::system_services::{ + CreateFileMappingA, MapViewOfFile, OpenFileMappingA, UnmapViewOfFile, + }, + windows::win32::system_services::{BOOL, HANDLE, PAGE_TYPE, PSTR}, + windows::win32::windows_programming::CloseHandle, + }, + Error, + }; + + use core::{ffi::c_void, ptr, slice}; + use uuid::Uuid; + + const INVALID_HANDLE_VALUE: isize = -1; + const FILE_MAP_ALL_ACCESS: u32 = 0xf001f; /// The default Sharedmap impl for windows using shmctl & shmget #[derive(Clone, Debug)] pub struct Win32ShMem { - pub filename: [u8; 64], - //TODO pub handle: windows::win32::system_services::HANDLE, + pub shm_str: [u8; 20], + pub handle: HANDLE, pub map: *mut u8, pub map_size: usize, } - // TODO complete + impl ShMem for Win32ShMem { + fn existing_from_shm_slice( + map_str_bytes: &[u8; 20], + map_size: usize, + ) -> Result { + Self::from_str(map_str_bytes, map_size) + } + + fn new_map(map_size: usize) -> Result { + Self::new(map_size) + } + + fn shm_slice(&self) -> &[u8; 20] { + &self.shm_str + } + + fn map(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.map, self.map_size) } + } + + fn map_mut(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.map, self.map_size) } + } + } + + /// Deinit sharedmaps on drop + impl Drop for Win32ShMem { + fn drop(&mut self) { + unsafe { + UnmapViewOfFile(self.map as *mut c_void); + CloseHandle(self.handle); + } + } + } + + impl Win32ShMem { + pub fn from_str(map_str_bytes: &[u8; 20], map_size: usize) -> Result { + unsafe { + let handle = OpenFileMappingA( + FILE_MAP_ALL_ACCESS, + BOOL(0), + PSTR(map_str_bytes as *const u8 as *mut u8), + ); + if handle == HANDLE(0) { + return Err(Error::Unknown(format!( + "Cannot open shared memory {}", + String::from_utf8_lossy(map_str_bytes) + ))); + } + let map = + MapViewOfFile(handle.clone(), FILE_MAP_ALL_ACCESS, 0, 0, map_size) as *mut u8; + if map == ptr::null_mut() { + return Err(Error::Unknown(format!( + "Cannot map shared memory {}", + String::from_utf8_lossy(map_str_bytes) + ))); + } + let mut ret = Self { + shm_str: [0; 20], + handle: handle, + map: map, + map_size: map_size, + }; + ret.shm_str.clone_from_slice(map_str_bytes); + Ok(ret) + } + } + + pub fn new(map_size: usize) -> Result { + unsafe { + let uuid = Uuid::new_v4(); + let mut map_str = format!("libafl_{}", uuid.to_simple()); + let map_str_bytes = map_str.as_mut_vec(); + map_str_bytes[19] = 0; // Trucate to size 20 + let handle = CreateFileMappingA( + HANDLE(INVALID_HANDLE_VALUE), + ptr::null_mut(), + PAGE_TYPE::PAGE_READWRITE, + 0, + map_size as u32, + PSTR(map_str_bytes.as_mut_ptr()), + ); + if handle == HANDLE(0) { + return Err(Error::Unknown(format!( + "Cannot create shared memory {}", + String::from_utf8_lossy(map_str_bytes) + ))); + } + let map = + MapViewOfFile(handle.clone(), FILE_MAP_ALL_ACCESS, 0, 0, map_size) as *mut u8; + if map == ptr::null_mut() { + return Err(Error::Unknown(format!( + "Cannot map shared memory {}", + String::from_utf8_lossy(map_str_bytes) + ))); + } + let mut ret = Self { + shm_str: [0; 20], + handle: handle, + map: map, + map_size: map_size, + }; + ret.shm_str.clone_from_slice(&map_str_bytes[0..20]); + Ok(ret) + } + } + } } #[cfg(test)] diff --git a/libafl/src/corpus/minimizer.rs b/libafl/src/corpus/minimizer.rs index 0520d98734..475688909b 100644 --- a/libafl/src/corpus/minimizer.rs +++ b/libafl/src/corpus/minimizer.rs @@ -1,3 +1,6 @@ +//! The Minimizer schedulers are a family of corpus schedulers that feed the fuzzer +// with testcases only from a subset of the total corpus. + use crate::{ bolts::serdeany::SerdeAny, corpus::{Corpus, CorpusScheduler, Testcase}, @@ -43,6 +46,7 @@ impl Default for TopRatedsMetadata { } } +/// Compute the favor factor of a testcase. Lower is better. pub trait FavFactor where I: Input, @@ -50,6 +54,8 @@ where fn compute(testcase: &mut Testcase) -> Result; } +/// Multiply the testcase size with the execution time. +/// This favors small and quick testcases. pub struct LenTimeMulFavFactor where I: Input + HasLen, @@ -67,6 +73,9 @@ where } } +/// The Minimizer scheduler employs a genetic algorithm to compute a subset of the +/// corpus that exercise all the requested features (e.g. all the coverage seen so far) +/// prioritizing testcases using FavFactor pub struct MinimizerCorpusScheduler where CS: CorpusScheduler, @@ -230,8 +239,11 @@ where } } +/// A MinimizerCorpusScheduler with LenTimeMulFavFactor to prioritize quick and small testcases pub type LenTimeMinimizerCorpusScheduler = MinimizerCorpusScheduler, I, M, R, S>; +/// A MinimizerCorpusScheduler with LenTimeMulFavFactor to prioritize quick and small testcases +/// that exercise all the entries registered in the MapIndexesMetadata pub type IndexesLenTimeMinimizerCorpusScheduler = MinimizerCorpusScheduler, I, MapIndexesMetadata, R, S>; diff --git a/libafl/src/corpus/mod.rs b/libafl/src/corpus/mod.rs index 3f48ffa0c7..73849fb871 100644 --- a/libafl/src/corpus/mod.rs +++ b/libafl/src/corpus/mod.rs @@ -63,6 +63,8 @@ where fn current_mut(&mut self) -> &mut Option; } +/// The scheduler define how the fuzzer requests a testcase from the corpus. +/// It has hooks to corpus add/replace/remove to allow complex scheduling algorithms to collect data. pub trait CorpusScheduler where I: Input, @@ -96,6 +98,7 @@ where fn next(&self, state: &mut S) -> Result; } +/// Feed the fuzzer simpply with a random testcase on request pub struct RandCorpusScheduler where S: HasCorpus + HasRand, diff --git a/libafl/src/corpus/queue.rs b/libafl/src/corpus/queue.rs index 3edb0f0833..89b10b945f 100644 --- a/libafl/src/corpus/queue.rs +++ b/libafl/src/corpus/queue.rs @@ -1,4 +1,4 @@ -//! The queue corpus implements an afl-like queue mechanism +//! The queue corpus scheduler implements an AFL-like queue mechanism use alloc::borrow::ToOwned; use core::marker::PhantomData; @@ -10,6 +10,7 @@ use crate::{ Error, }; +/// Walk the corpus in a queue-like fashion pub struct QueueCorpusScheduler where S: HasCorpus, @@ -25,7 +26,7 @@ where C: Corpus, I: Input, { - /// Gets the next entry at random + /// Gets the next entry in the queue fn next(&self, state: &mut S) -> Result { if state.corpus().count() == 0 { Err(Error::Empty("No entries in corpus".to_owned())) diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index 7219338e32..c8984d0dad 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -1,8 +1,10 @@ -use crate::bolts::{llmp::LlmpSender, shmem::HasFd}; use alloc::{string::ToString, vec::Vec}; use core::{marker::PhantomData, time::Duration}; use serde::{de::DeserializeOwned, Serialize}; +#[cfg(feature = "std")] +use core::ptr::read_volatile; + #[cfg(feature = "std")] use crate::bolts::llmp::LlmpReceiver; @@ -16,8 +18,8 @@ use crate::utils::{fork, ForkResult}; use crate::bolts::shmem::UnixShMem; use crate::{ bolts::{ - llmp::{self, LlmpClient, LlmpClientDescription, Tag}, - shmem::ShMem, + llmp::{self, LlmpClient, LlmpClientDescription, LlmpSender, Tag}, + shmem::{HasFd, ShMem}, }, corpus::CorpusScheduler, events::{BrokerEventResult, Event, EventManager}, @@ -352,7 +354,7 @@ where llmp::LlmpConnection::IsClient { client } => { while let Some((sender_id, tag, msg)) = client.recv_buf()? { if tag == _LLMP_TAG_EVENT_TO_BROKER { - continue; + panic!("EVENT_TO_BROKER parcel should not have arrived in the client!"); } let event: Event = postcard::from_bytes(msg)?; events.push((sender_id, event)); @@ -519,10 +521,7 @@ where #[cfg(target_os = "android")] { let path = std::env::current_dir()?; - mgr = LlmpEventManager::::new_on_domain_socket( - stats, - &format!("{}/.llmp_socket", path.display()).to_string(), - )?; + mgr = LlmpEventManager::::new_on_domain_socket(stats, "\x00llmp_socket")?; }; #[cfg(not(target_os = "android"))] { @@ -535,6 +534,7 @@ where mgr.broker_loop()?; return Err(Error::ShuttingDown); } else { + // We are the fuzzer respawner in a llmp client mgr.to_env(_ENV_FUZZER_BROKER_CLIENT_INITIAL); // First, create a channel from the fuzzer (sender) to us (receiver) to report its state for restarts. @@ -547,7 +547,7 @@ where sender.to_env(_ENV_FUZZER_SENDER)?; receiver.to_env(_ENV_FUZZER_RECEIVER)?; - let mut ctr = 0; + let mut ctr: u64 = 0; // Client->parent loop loop { dbg!("Spawning next client (id {})", ctr); @@ -563,7 +563,12 @@ where #[cfg(windows)] startable_self()?.status()?; - ctr += 1; + if unsafe { read_volatile(&(*receiver.current_recv_map.page()).size_used) } == 0 { + // Storing state in the last round did not work + panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client!"); + } + + ctr = ctr.wrapping_add(1); } } } else { diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index a890be9518..7606284edf 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -1,15 +1,18 @@ //! The InProcess Executor is a libfuzzer-like executor, that will simply call a function. //! It should usually be paired with extra error-handling, such as a restarting event manager, to be effective. -use core::marker::PhantomData; -#[cfg(unix)] use core::{ + ffi::c_void, + marker::PhantomData, ptr::{self, write_volatile}, sync::atomic::{compiler_fence, Ordering}, }; #[cfg(unix)] -use crate::bolts::os::unix_signals::{c_void, setup_signal_handler}; +use crate::bolts::os::unix_signals::setup_signal_handler; +#[cfg(windows)] +use crate::bolts::os::windows_exceptions::setup_exception_handler; + use crate::{ bolts::tuples::Named, corpus::Corpus, @@ -59,6 +62,27 @@ where &mut data.current_input_ptr, _input as *const _ as *const c_void, ); + write_volatile( + &mut data.observers_ptr, + &self.observers as *const _ as *const c_void, + ); + // Direct raw pointers access /aliasing is pretty undefined behavior. + // Since the state and event may have moved in memory, refresh them right before the signal may happen + write_volatile(&mut data.state_ptr, _state as *mut _ as *mut c_void); + write_volatile(&mut data.event_mgr_ptr, _event_mgr as *mut _ as *mut c_void); + compiler_fence(Ordering::SeqCst); + } + #[cfg(windows)] + unsafe { + let data = &mut windows_exception_handler::GLOBAL_STATE; + write_volatile( + &mut data.current_input_ptr, + _input as *const _ as *const c_void, + ); + write_volatile( + &mut data.observers_ptr, + &self.observers as *const _ as *const c_void, + ); // Direct raw pointers access /aliasing is pretty undefined behavior. // Since the state and event may have moved in memory, refresh them right before the signal may happen write_volatile(&mut data.state_ptr, _state as *mut _ as *mut c_void); @@ -72,6 +96,16 @@ where fn run_target(&mut self, input: &I) -> Result { let bytes = input.target_bytes(); let ret = (self.harness_fn)(self, bytes.as_slice()); + Ok(ret) + } + + #[inline] + fn post_exec( + &mut self, + _state: &mut S, + _event_mgr: &mut EM, + _input: &I, + ) -> Result<(), Error> { #[cfg(unix)] unsafe { write_volatile( @@ -80,7 +114,15 @@ where ); compiler_fence(Ordering::SeqCst); } - Ok(ret) + #[cfg(windows)] + unsafe { + write_volatile( + &mut windows_exception_handler::GLOBAL_STATE.current_input_ptr, + ptr::null(), + ); + compiler_fence(Ordering::SeqCst); + } + Ok(()) } } @@ -138,10 +180,6 @@ where #[cfg(unix)] unsafe { let data = &mut unix_signal_handler::GLOBAL_STATE; - write_volatile( - &mut data.observers_ptr, - &observers as *const _ as *const c_void, - ); write_volatile( &mut data.crash_handler, unix_signal_handler::inproc_crash_handler::, @@ -154,6 +192,21 @@ where setup_signal_handler(data)?; compiler_fence(Ordering::SeqCst); } + #[cfg(windows)] + unsafe { + let data = &mut windows_exception_handler::GLOBAL_STATE; + write_volatile( + &mut data.crash_handler, + windows_exception_handler::inproc_crash_handler::, + ); + //write_volatile( + // &mut data.timeout_handler, + // windows_exception_handler::inproc_timeout_handler::, + //); + + setup_exception_handler(data)?; + compiler_fence(Ordering::SeqCst); + } Ok(Self { harness_fn, @@ -186,6 +239,8 @@ mod unix_signal_handler { state::{HasObjectives, HasSolutions}, }; + // TODO merge GLOBAL_STATE with the Windows one + /// Signal handling on unix systems needs some nasty unsafe. pub static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHandlerData { /// The state ptr for signal handling @@ -228,7 +283,9 @@ mod unix_signal_handler { unsafe { let data = &mut GLOBAL_STATE; match signal { - Signal::SigUser2 => (data.timeout_handler)(signal, info, void, data), + Signal::SigUser2 | Signal::SigAlarm => { + (data.timeout_handler)(signal, info, void, data) + } _ => (data.crash_handler)(signal, info, void, data), } } @@ -236,6 +293,7 @@ mod unix_signal_handler { fn signals(&self) -> Vec { vec![ + Signal::SigAlarm, Signal::SigUser2, Signal::SigAbort, Signal::SigBus, @@ -279,7 +337,7 @@ mod unix_signal_handler { let obj_fitness = state .objectives_mut() - .is_interesting_all(&input, observers, ExitKind::Crash) + .is_interesting_all(&input, observers, ExitKind::Timeout) .expect("In timeout handler objectives failure."); if obj_fitness > 0 { state @@ -404,6 +462,166 @@ mod unix_signal_handler { } } +#[cfg(windows)] +mod windows_exception_handler { + use alloc::vec::Vec; + use core::{ffi::c_void, ptr}; + #[cfg(feature = "std")] + use std::io::{stdout, Write}; + + use crate::{ + bolts::{ + bindings::windows::win32::system_services::ExitProcess, + os::windows_exceptions::{ + ExceptionCode, Handler, CRASH_EXCEPTIONS, EXCEPTION_POINTERS, + }, + }, + corpus::{Corpus, Testcase}, + events::{Event, EventManager}, + executors::ExitKind, + feedbacks::FeedbacksTuple, + inputs::{HasTargetBytes, Input}, + observers::ObserversTuple, + state::{HasObjectives, HasSolutions}, + }; + + /// Signal handling on unix systems needs some nasty unsafe. + pub static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHandlerData { + /// The state ptr for signal handling + state_ptr: ptr::null_mut(), + /// The event manager ptr for signal handling + event_mgr_ptr: ptr::null_mut(), + /// The observers ptr for signal handling + observers_ptr: ptr::null(), + /// The current input for signal handling + current_input_ptr: ptr::null(), + /// The crash handler fn + crash_handler: nop_handler, + // The timeout handler fn + //timeout_handler: nop_handler, + }; + + pub struct InProcessExecutorHandlerData { + pub state_ptr: *mut c_void, + pub event_mgr_ptr: *mut c_void, + pub observers_ptr: *const c_void, + pub current_input_ptr: *const c_void, + pub crash_handler: unsafe fn(ExceptionCode, *mut EXCEPTION_POINTERS, &mut Self), + //pub timeout_handler: unsafe fn(ExceptionCode, *mut EXCEPTION_POINTERS, &mut Self), + } + + unsafe impl Send for InProcessExecutorHandlerData {} + unsafe impl Sync for InProcessExecutorHandlerData {} + + unsafe fn nop_handler( + _code: ExceptionCode, + _exception_pointers: *mut EXCEPTION_POINTERS, + _data: &mut InProcessExecutorHandlerData, + ) { + } + + impl Handler for InProcessExecutorHandlerData { + fn handle(&mut self, code: ExceptionCode, exception_pointers: *mut EXCEPTION_POINTERS) { + unsafe { + let data = &mut GLOBAL_STATE; + (data.crash_handler)(code, exception_pointers, data) + } + } + + fn exceptions(&self) -> Vec { + CRASH_EXCEPTIONS.to_vec() + } + } + + pub unsafe fn inproc_crash_handler( + code: ExceptionCode, + exception_pointers: *mut EXCEPTION_POINTERS, + data: &mut InProcessExecutorHandlerData, + ) where + EM: EventManager, + OT: ObserversTuple, + OC: Corpus, + OFT: FeedbacksTuple, + S: HasObjectives + HasSolutions, + I: Input + HasTargetBytes, + { + #[cfg(feature = "std")] + println!("Crashed with {}", code); + if !data.current_input_ptr.is_null() { + let state = (data.state_ptr as *mut S).as_mut().unwrap(); + let event_mgr = (data.event_mgr_ptr as *mut EM).as_mut().unwrap(); + let observers = (data.observers_ptr as *const OT).as_ref().unwrap(); + + #[cfg(feature = "std")] + println!("Child crashed!"); + #[cfg(feature = "std")] + let _ = stdout().flush(); + + let input = (data.current_input_ptr as *const I).as_ref().unwrap(); + // 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 new_input = input.clone(); + state + .solutions_mut() + .add(Testcase::new(new_input)) + .expect("In crash handler solutions failure."); + event_mgr + .fire( + state, + Event::Objective { + objective_size: state.solutions().count(), + }, + ) + .expect("Could not send crashing input"); + } + + event_mgr.on_restart(state).unwrap(); + + #[cfg(feature = "std")] + println!("Waiting for broker..."); + event_mgr.await_restart_safe(); + #[cfg(feature = "std")] + println!("Bye!"); + + ExitProcess(1); + } else { + #[cfg(feature = "std")] + { + println!("Double crash\n"); + let crash_addr = exception_pointers + .as_mut() + .unwrap() + .exception_record + .as_mut() + .unwrap() + .exception_address as usize; + + println!( + "We crashed at addr 0x{:x}, but are not in the target... Bug in the fuzzer? Exiting.", + crash_addr + ); + } + #[cfg(feature = "std")] + { + println!("Type QUIT to restart the child"); + let mut line = String::new(); + while line.trim() != "QUIT" { + std::io::stdin().read_line(&mut line).unwrap(); + } + } + + // TODO tell the parent to not restart + ExitProcess(1); + } + } +} + #[cfg(test)] mod tests { @@ -425,7 +643,6 @@ mod tests { let mut in_process_executor = InProcessExecutor:: { harness_fn: test_harness_fn_nop, - // TODO: on_crash_fn: Box::new(|_, _, _, _, _| ()), observers: tuple_list!(), name: "main", phantom: PhantomData, diff --git a/libafl/src/executors/mod.rs b/libafl/src/executors/mod.rs index b3d25bc9ff..a5ee692469 100644 --- a/libafl/src/executors/mod.rs +++ b/libafl/src/executors/mod.rs @@ -2,6 +2,8 @@ pub mod inprocess; pub use inprocess::InProcessExecutor; +pub mod timeout; +pub use timeout::TimeoutExecutor; #[cfg(feature = "runtime")] pub mod runtime; @@ -94,7 +96,12 @@ where /// Called right after execution finished. #[inline] - fn post_exec(&mut self, _state: &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> where EM: EventManager, { diff --git a/libafl/src/executors/timeout.rs b/libafl/src/executors/timeout.rs new file mode 100644 index 0000000000..6acb753032 --- /dev/null +++ b/libafl/src/executors/timeout.rs @@ -0,0 +1,164 @@ +//! A TimeoutExecutor set a timeout before each target run + +use core::{marker::PhantomData, time::Duration}; + +use crate::{ + bolts::tuples::Named, + events::EventManager, + executors::{Executor, ExitKind, HasObservers}, + inputs::{HasTargetBytes, Input}, + observers::ObserversTuple, + Error, +}; + +#[cfg(unix)] +use core::ptr::null_mut; +#[cfg(unix)] +use libc::c_int; + +#[repr(C)] +#[cfg(unix)] +struct Timeval { + pub tv_sec: i64, + pub tv_usec: i64, +} + +#[repr(C)] +#[cfg(unix)] +struct Itimerval { + pub it_interval: Timeval, + pub it_value: Timeval, +} + +#[cfg(unix)] +extern "C" { + fn setitimer(which: c_int, new_value: *mut Itimerval, old_value: *mut Itimerval) -> c_int; +} + +#[cfg(unix)] +const ITIMER_REAL: c_int = 0; + +/// The timeout excutor is a wrapper that set a timeout before each run +pub struct TimeoutExecutor +where + E: Executor + HasObservers, + I: Input + HasTargetBytes, + OT: ObserversTuple, +{ + executor: E, + exec_tmout: Duration, + phantom: PhantomData<(I, OT)>, +} + +impl Named for TimeoutExecutor +where + E: Executor + HasObservers, + I: Input + HasTargetBytes, + OT: ObserversTuple, +{ + fn name(&self) -> &str { + self.executor.name() + } +} + +impl HasObservers for TimeoutExecutor +where + E: Executor + HasObservers, + I: Input + HasTargetBytes, + OT: ObserversTuple, +{ + #[inline] + fn observers(&self) -> &OT { + self.executor.observers() + } + + #[inline] + fn observers_mut(&mut self) -> &mut OT { + self.executor.observers_mut() + } +} + +impl TimeoutExecutor +where + E: Executor + HasObservers, + I: Input + HasTargetBytes, + OT: ObserversTuple, +{ + pub fn new(executor: E, exec_tmout: Duration) -> Self { + Self { + executor, + exec_tmout, + phantom: PhantomData, + } + } +} + +impl Executor for TimeoutExecutor +where + E: Executor + HasObservers, + I: Input + HasTargetBytes, + OT: ObserversTuple, +{ + #[inline] + fn pre_exec, S>( + &mut self, + _state: &mut S, + _event_mgr: &mut EM, + _input: &I, + ) -> Result<(), Error> { + #[cfg(unix)] + unsafe { + let milli_sec = self.exec_tmout.as_millis(); + let it_value = Timeval { + tv_sec: (milli_sec / 1000) as i64, + tv_usec: (milli_sec % 1000) as i64, + }; + let it_interval = Timeval { + tv_sec: 0, + tv_usec: 0, + }; + setitimer( + ITIMER_REAL, + &mut Itimerval { + it_interval, + it_value, + }, + null_mut(), + ); + } + self.executor.pre_exec(_state, _event_mgr, _input) + } + + #[inline] + fn post_exec, S>( + &mut self, + _state: &mut S, + _event_mgr: &mut EM, + _input: &I, + ) -> Result<(), Error> { + #[cfg(unix)] + unsafe { + let it_value = Timeval { + tv_sec: 0, + tv_usec: 0, + }; + let it_interval = Timeval { + tv_sec: 0, + tv_usec: 0, + }; + setitimer( + ITIMER_REAL, + &mut Itimerval { + it_interval, + it_value, + }, + null_mut(), + ); + } + self.executor.post_exec(_state, _event_mgr, _input) + } + + fn run_target(&mut self, input: &I) -> Result { + self.executor.run_target(input) + } +} diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 79dddaaf5d..5a156539eb 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -157,6 +157,46 @@ impl Default for CrashFeedback { } } +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TimeoutFeedback {} + +impl Feedback for TimeoutFeedback +where + I: Input, +{ + fn is_interesting( + &mut self, + _input: &I, + _observers: &OT, + exit_kind: ExitKind, + ) -> Result { + if exit_kind == ExitKind::Timeout { + Ok(1) + } else { + Ok(0) + } + } +} + +impl Named for TimeoutFeedback { + #[inline] + fn name(&self) -> &str { + "TimeoutFeedback" + } +} + +impl TimeoutFeedback { + pub fn new() -> Self { + Self {} + } +} + +impl Default for TimeoutFeedback { + fn default() -> Self { + Self::new() + } +} + /// Nop feedback that annotates execution time in the new testcase, if any #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TimeFeedback {