diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index ca2f255430..cce77bff2d 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -60,6 +60,8 @@ jobs: run: cargo test --all-features --doc - name: Run clippy run: ./clippy.sh + - name: Build fuzzers + run: ./build_all_fuzzers.sh windows: runs-on: windows-latest steps: diff --git a/build_all_fuzzers.sh b/build_all_fuzzers.sh new file mode 100755 index 0000000000..d596d7aac6 --- /dev/null +++ b/build_all_fuzzers.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# TODO: This should be rewritten in rust, a Makefile, or some platform-independent language + +cd fuzzers + +for fuzzer in *; +do + echo "[+] Checking fmt, clippy, and building $fuzzer" + cd $fuzzer \ + && cargo fmt --all -- --check \ + && ../../clippy.sh --no-clean \ + && cargo build \ + && cd .. \ + || exit 1 +done \ No newline at end of file diff --git a/clippy.sh b/clippy.sh index 67a8c4709d..cb70773ae9 100755 --- a/clippy.sh +++ b/clippy.sh @@ -1,14 +1,18 @@ #!/bin/sh # Clippy checks -cargo clean -p libafl +if [ "$1" != "--no-clean" ]; then + # Usually, we want to clean, since clippy won't work otherwise. + echo "[+] Cleaning up previous builds..." + cargo clean -p libafl +fi RUST_BACKTRACE=full cargo clippy --all --all-features --tests -- \ -D clippy::pedantic \ - -W clippy::similar-names \ -W clippy::unused_self \ -W clippy::too_many_lines \ -W clippy::option_if_let_else \ -W clippy::must-use-candidate \ -W clippy::if-not-else \ + -W clippy::similar-names \ -A clippy::type_repetition_in_bounds \ -A clippy::missing-errors-doc \ -A clippy::cast-possible-truncation \ diff --git a/docs/src/baby_fuzzer.md b/docs/src/baby_fuzzer.md index 851f00cdb7..fe557688ce 100644 --- a/docs/src/baby_fuzzer.md +++ b/docs/src/baby_fuzzer.md @@ -156,13 +156,13 @@ Now you can prepend the following `use` directives to your main.rs and compile i ```rust use std::path::PathBuf; use libafl::{ + bolts::{current_nanos, rands::StdRand}, corpus::{InMemoryCorpus, OnDiskCorpus, QueueCorpusScheduler}, events::SimpleEventManager, executors::{inprocess::InProcessExecutor, ExitKind}, generators::RandPrintablesGenerator, state::State, stats::SimpleStats, - utils::{current_nanos, StdRand}, }; ``` diff --git a/fuzzers/baby_fuzzer/Cargo.toml b/fuzzers/baby_fuzzer/Cargo.toml index cddd5b7b6e..f02b1ebbe9 100644 --- a/fuzzers/baby_fuzzer/Cargo.toml +++ b/fuzzers/baby_fuzzer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "baby_fuzzer" -version = "0.2.0" +version = "0.3.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 132b3b3974..a40c9d9f7c 100644 --- a/fuzzers/baby_fuzzer/src/main.rs +++ b/fuzzers/baby_fuzzer/src/main.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use libafl::{ - bolts::tuples::tuple_list, + bolts::{current_nanos, rands::StdRand, tuples::tuple_list}, corpus::{InMemoryCorpus, OnDiskCorpus, QueueCorpusScheduler}, events::SimpleEventManager, executors::{inprocess::InProcessExecutor, ExitKind}, @@ -13,25 +13,26 @@ use libafl::{ stages::mutational::StdMutationalStage, state::StdState, stats::SimpleStats, - utils::{current_nanos, StdRand}, }; -// Coverage map with explicit assignments due to the lack of instrumentation +/// Coverage map with explicit assignments due to the lack of instrumentation static mut SIGNALS: [u8; 16] = [0; 16]; +/// Assign a signal to the signals map fn signals_set(idx: usize) { unsafe { SIGNALS[idx] = 1 }; } +#[allow(clippy::similar_names)] pub fn main() { // The closure that we want to fuzz let mut harness = |buf: &[u8]| { signals_set(0); - if buf.len() > 0 && buf[0] == 'a' as u8 { + if !buf.is_empty() && buf[0] == b'a' { signals_set(1); - if buf.len() > 1 && buf[1] == 'b' as u8 { + if buf.len() > 1 && buf[1] == b'b' { signals_set(2); - if buf.len() > 2 && buf[2] == 'c' as u8 { + if buf.len() > 2 && buf[2] == b'c' { panic!("=)"); } } @@ -86,7 +87,7 @@ pub fn main() { &mut state, &mut mgr, ) - .expect("Failed to create the Executor".into()); + .expect("Failed to create the Executor"); // Generator of printable bytearrays of max size 32 let mut generator = RandPrintablesGenerator::new(32); @@ -94,7 +95,7 @@ pub fn main() { // Generate 8 initial inputs state .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8) - .expect("Failed to generate the initial corpus".into()); + .expect("Failed to generate the initial corpus"); // Setup a mutational stage with a basic bytes mutator let mutator = StdScheduledMutator::new(havoc_mutations()); @@ -102,5 +103,5 @@ pub fn main() { fuzzer .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) - .expect("Error in the fuzzing loop".into()); + .expect("Error in the fuzzing loop"); } diff --git a/fuzzers/frida_libpng/Cargo.toml b/fuzzers/frida_libpng/Cargo.toml index 56a6b3f603..5ae8c24746 100644 --- a/fuzzers/frida_libpng/Cargo.toml +++ b/fuzzers/frida_libpng/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frida_libpng" -version = "0.2.0" +version = "0.3.0" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" build = "build.rs" @@ -21,17 +21,18 @@ num_cpus = "1.0" which = "4.1" [target.'cfg(unix)'.dependencies] -libafl = { path = "../../libafl/", features = [ "std", "llmp_compression" ] } #, "llmp_small_maps", "llmp_debug"]} +libafl = { path = "../../libafl/", features = [ "std", "llmp_compression", "llmp_bind_public" ] } #, "llmp_small_maps", "llmp_debug"]} +libafl_frida = { path = "../../libafl_frida" } capstone = "0.8.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.1", git = "https://github.com/frida/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" num-traits = "0.2.14" rangemap = "0.1.10" seahash = "4.1.0" +clap = "2.33" serde = "1.0" backtrace = "0.3" diff --git a/fuzzers/frida_libpng/README.md b/fuzzers/frida_libpng/README.md index dd032eb121..5e49056444 100644 --- a/fuzzers/frida_libpng/README.md +++ b/fuzzers/frida_libpng/README.md @@ -11,6 +11,15 @@ This will call (the build.rs)[./build.rs], which in turn downloads a libpng arch 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`. +### Build For Android +When building for android using a cross-compiler, make sure you have a _standalone toolchain_, and then add the following: +1. In the ~/.cargo/config file add a target with the correct cross-compiler toolchain name (in this case aarch64-linux-android, but names may vary) +`[target.aarch64-linux-android]` +`linker="aarch64-linux-android-clang"` +2. add path to installed toolchain to PATH env variable. +3. define CLANG_PATH and add target to the build command line: +`CLANG_PATH=/bin/aarch64-linux-android-clang cargo -v build --release --target=aarch64-linux-android` + ## 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. diff --git a/fuzzers/frida_libpng/build.rs b/fuzzers/frida_libpng/build.rs index 0fa8a7e5b3..8683b8b447 100644 --- a/fuzzers/frida_libpng/build.rs +++ b/fuzzers/frida_libpng/build.rs @@ -12,15 +12,14 @@ const LIBPNG_URL: &str = "https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz"; fn build_dep_check(tools: &[&str]) { - for tool in tools.into_iter() { + for tool in tools { println!("Checking for build tool {}...", tool); - match which(tool) { - Ok(path) => println!("Found build tool {}", path.to_str().unwrap()), - Err(_) => { - println!("ERROR: missing build tool {}", tool); - exit(1); - } + if let Ok(path) = which(tool) { + println!("Found build tool {}", path.to_str().unwrap()) + } else { + println!("ERROR: missing build tool {}", tool); + exit(1); }; } } @@ -35,7 +34,7 @@ fn main() { 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); - std::fs::create_dir_all(&out_dir).expect(&format!("Failed to create {}", &out_dir)); + std::fs::create_dir_all(&out_dir).unwrap_or_else(|_| panic!("Failed to create {}", &out_dir)); println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=../libfuzzer_runtime/rt.c",); diff --git a/fuzzers/frida_libpng/src/fuzzer.rs b/fuzzers/frida_libpng/src/fuzzer.rs index 99bf877210..7d1caaeaae 100644 --- a/fuzzers/frida_libpng/src/fuzzer.rs +++ b/fuzzers/frida_libpng/src/fuzzer.rs @@ -1,13 +1,24 @@ //! A libfuzzer-like fuzzer with llmp-multithreading support and restarts //! The example harness is built for libpng. +use clap::{App, Arg}; + +#[cfg(target_os = "android")] +use libafl::bolts::os::ashmem_server::AshmemService; + use libafl::{ - bolts::tuples::tuple_list, + bolts::{ + current_nanos, + launcher::Launcher, + os::parse_core_bind_arg, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::tuple_list, + }, corpus::{ ondisk::OnDiskMetadataFormat, Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler, }, - events::setup_restarting_mgr_std, executors::{ inprocess::InProcessExecutor, timeout::TimeoutExecutor, Executor, ExitKind, HasExecHooks, HasExecHooksTuple, HasObservers, HasObserversHooks, @@ -16,13 +27,14 @@ use libafl::{ feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, StdFuzzer}, inputs::{HasTargetBytes, Input}, - mutators::scheduled::{havoc_mutations, StdScheduledMutator}, - mutators::token_mutations::Tokens, + mutators::{ + scheduled::{havoc_mutations, StdScheduledMutator}, + token_mutations::Tokens, + }, observers::{HitcountsMapObserver, ObserversTuple, StdMapObserver, TimeObserver}, stages::mutational::StdMutationalStage, state::{HasCorpus, HasMetadata, StdState}, stats::SimpleStats, - utils::{current_nanos, StdRand}, Error, }; @@ -31,7 +43,14 @@ use frida_gum::{ Gum, NativePointer, }; -use std::{env, ffi::c_void, marker::PhantomData, path::PathBuf, time::Duration}; +use std::{ + env, + ffi::c_void, + marker::PhantomData, + net::SocketAddr, + path::{Path, PathBuf}, + time::Duration, +}; use libafl_frida::{ asan_rt::{AsanErrorsFeedback, AsanErrorsObserver, ASAN_ERRORS}, @@ -67,17 +86,20 @@ where #[inline] fn run_target(&mut self, input: &I) -> Result { if self.helper.stalker_enabled() { - if !self.followed { - self.followed = true; - self.stalker - .follow_me::(self.helper.transformer(), None); - } else { + if self.followed { self.stalker.activate(NativePointer( self.base.inner().harness_mut() as *mut _ as *mut c_void )) + } else { + self.followed = true; + self.stalker + .follow_me::(self.helper.transformer(), None); } } let res = self.base.run_target(input); + if self.helper.stalker_enabled() { + self.stalker.deactivate(); + } if unsafe { ASAN_ERRORS.is_some() && !ASAN_ERRORS.as_ref().unwrap().is_empty() } { println!("Crashing target as it had ASAN errors"); unsafe { @@ -196,25 +218,71 @@ pub fn main() { // Needed only on no_std //RegistryBuilder::register::(); + let matches = App::new("libafl_frida") + .version("0.1.0") + .arg( + Arg::with_name("cores") + .short("c") + .long("cores") + .value_name("CORES") + .required(true) + .takes_value(true), + ) + .arg(Arg::with_name("harness").required(true).index(1)) + .arg(Arg::with_name("symbol").required(true).index(2)) + .arg( + Arg::with_name("modules_to_instrument") + .required(true) + .index(3), + ) + .arg( + Arg::with_name("output") + .short("o") + .long("output") + .value_name("OUTPUT") + .required(false) + .takes_value(true), + ) + .arg( + Arg::with_name("b2baddr") + .short("B") + .long("b2baddr") + .value_name("B2BADDR") + .required(false) + .takes_value(true), + ) + .get_matches(); + + let cores = parse_core_bind_arg(&matches.value_of("cores").unwrap().to_string()).unwrap(); + color_backtrace::install(); println!( "Workdir: {:?}", env::current_dir().unwrap().to_string_lossy().to_string() ); + + let broker_addr = matches + .value_of("b2baddr") + .map(|addrstr| addrstr.parse().unwrap()); + unsafe { fuzz( - &env::args().nth(1).expect("no module specified"), - &env::args().nth(2).expect("no symbol specified"), - env::args() - .nth(3) - .expect("no modules to instrument specified") + matches.value_of("harness").unwrap(), + matches.value_of("symbol").unwrap(), + &matches + .value_of("modules_to_instrument") + .unwrap() .split(':') .map(|module_name| std::fs::canonicalize(module_name).unwrap()) - .collect(), - &vec![PathBuf::from("./corpus")], - PathBuf::from("./crashes"), + .collect::>(), + //modules_to_instrument, + &[PathBuf::from("./corpus")], + &PathBuf::from("./crashes"), 1337, + &cores, + matches.value_of("output"), + broker_addr, ) .expect("An error occurred while fuzzing"); } @@ -222,172 +290,185 @@ pub fn main() { /// Not supported on windows right now #[cfg(windows)] +#[allow(clippy::too_many_arguments)] fn fuzz( _module_name: &str, _symbol_name: &str, - _corpus_dirs: Vec, - _objective_dir: PathBuf, + _corpus_dirs: &[PathBuf], + _objective_dir: &Path, _broker_port: u16, + _cores: &[usize], + _stdout_file: Option<&str>, + _broker_addr: Option, ) -> Result<(), ()> { todo!("Example not supported on Windows"); } /// The actual fuzzer #[cfg(unix)] +#[allow(clippy::too_many_lines, clippy::clippy::too_many_arguments)] unsafe fn fuzz( module_name: &str, symbol_name: &str, - modules_to_instrument: Vec, - corpus_dirs: &Vec, - objective_dir: PathBuf, + modules_to_instrument: &[PathBuf], + corpus_dirs: &[PathBuf], + objective_dir: &Path, broker_port: u16, + cores: &[usize], + stdout_file: Option<&str>, + broker_addr: Option, ) -> Result<(), Error> { + let stats_closure = |s| println!("{}", s); // 'While the stats are state, they are usually used in the broker - which is likely never restarted - let stats = SimpleStats::new(|s| println!("{}", s)); + let stats = SimpleStats::new(stats_closure); - // 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); - } - }, - }; + #[cfg(target_os = "android")] + AshmemService::start().expect("Failed to start Ashmem service"); + let shmem_provider = StdShMemProvider::new()?; - let gum = Gum::obtain(); + let mut client_init_stats = || Ok(SimpleStats::new(stats_closure)); - let lib = libloading::Library::new(module_name).unwrap(); - let target_func: libloading::Symbol i32> = - lib.get(symbol_name.as_bytes()).unwrap(); + let mut run_client = |state: Option>, mut mgr| { + // The restarting state will spawn the same process again as child, then restarted it each time it crashes. - let mut frida_harness = move |buf: &[u8]| { - (target_func)(buf.as_ptr(), buf.len()); - ExitKind::Ok - }; + let lib = libloading::Library::new(module_name).unwrap(); + let target_func: libloading::Symbol< + unsafe extern "C" fn(data: *const u8, size: usize) -> i32, + > = lib.get(symbol_name.as_bytes()).unwrap(); - let frida_options = FridaOptions::parse_env_options(); - let mut frida_helper = - FridaInstrumentationHelper::new(&gum, &frida_options, module_name, &modules_to_instrument); + let mut frida_harness = move |buf: &[u8]| { + (target_func)(buf.as_ptr(), buf.len()); + ExitKind::Ok + }; - // Create an observation channel using the coverage map - let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr( - "edges", - frida_helper.map_ptr(), - MAP_SIZE, - )); + let gum = Gum::obtain(); + let frida_options = FridaOptions::parse_env_options(); + let mut frida_helper = FridaInstrumentationHelper::new( + &gum, + &frida_options, + module_name, + &modules_to_instrument, + ); - // Create an observation channel to keep track of the execution time - let time_observer = TimeObserver::new("time"); + // Create an observation channel using the coverage map + let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr( + "edges", + frida_helper.map_ptr(), + MAP_SIZE, + )); - // Create an observation channel for ASan violations - let asan_observer = AsanErrorsObserver::new(&ASAN_ERRORS); + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); - // The state of the edges feedback. - let feedback_state = MapFeedbackState::with_observer(&edges_observer); + let feedback_state = MapFeedbackState::with_observer(&edges_observer); + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false), + // Time feedback, this one does not need a feedback state + TimeFeedback::new_with_observer(&time_observer) + ); - // Feedback to rate the interestingness of an input - // This one is composed by two Feedbacks in OR - let feedback = feedback_or!( - // New maximization map feedback linked to the edges observer and the feedback state - MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false), - // Time feedback, this one does not need a feedback state - TimeFeedback::new_with_observer(&time_observer) - ); + // Feedbacks to recognize an input as solution + let objective = feedback_or!( + CrashFeedback::new(), + TimeoutFeedback::new(), + AsanErrorsFeedback::new() + ); - // Feedbacks to recognize an input as solution - let objective = feedback_or!( - CrashFeedback::new(), - TimeoutFeedback::new(), - AsanErrorsFeedback::new() - ); - - // If not restarting, create a State from scratch - let mut state = state.unwrap_or_else(|| { - StdState::new( - // RNG - StdRand::with_seed(current_nanos()), - // Corpus that will be evolved, we keep it in memory for performance - InMemoryCorpus::new(), - // Corpus in which we store solutions (crashes in this example), - // on disk so the user can get them after stopping the fuzzer - OnDiskCorpus::new_save_meta(objective_dir, Some(OnDiskMetadataFormat::JsonPretty)) + // If not restarting, create a State from scratch + let mut state = state.unwrap_or_else(|| { + StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new_save_meta( + objective_dir.to_path_buf(), + Some(OnDiskMetadataFormat::JsonPretty), + ) .unwrap(), - // States of the feedbacks. - // They are the data related to the feedbacks that you want to persist in the State. - tuple_list!(feedback_state), - ) - }); - - 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 - b"IHDR".to_vec(), - b"IDAT".to_vec(), - b"PLTE".to_vec(), - b"IEND".to_vec(), - ])); - } - - // Setup a basic mutator with a mutational stage - let mutator = StdScheduledMutator::new(havoc_mutations()); - let mut stages = tuple_list!(StdMutationalStage::new(mutator)); - - // A minimization+queue policy to get testcasess from the corpus - let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); - - // A fuzzer with feedbacks and a corpus scheduler - let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - - frida_helper.register_thread(); - - // Create the executor for an in-process function with just one observer for edge coverage - let mut executor = FridaInProcessExecutor::new( - &gum, - InProcessExecutor::new( - &mut frida_harness, - tuple_list!(edges_observer, time_observer, asan_observer), - &mut fuzzer, - &mut state, - &mut restarting_mgr, - )?, - &mut frida_helper, - Duration::new(10, 0), - ); - // Let's exclude the main module and libc.so at least: - //executor.stalker.exclude(&MemoryRange::new( - //Module::find_base_address(&env::args().next().unwrap()), - //get_module_size(&env::args().next().unwrap()), - //)); - //executor.stalker.exclude(&MemoryRange::new( - //Module::find_base_address("libc.so"), - //get_module_size("libc.so"), - //)); - - // In case the corpus is empty (on first run), reset - if state.corpus().count() < 1 { - state - .load_initial_inputs( - &mut fuzzer, - &mut executor, - &mut restarting_mgr, - &corpus_dirs, + // States of the feedbacks. + // They are the data related to the feedbacks that you want to persist in the State. + tuple_list!(feedback_state), ) - .expect(&format!( - "Failed to load initial corpus at {:?}", - &corpus_dirs - )); - println!("We imported {} inputs from disk.", state.corpus().count()); - } + }); - fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?; + println!("We're a client, let's fuzz :)"); - // Never reached - Ok(()) + // 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 + b"IHDR".to_vec(), + b"IDAT".to_vec(), + b"PLTE".to_vec(), + b"IEND".to_vec(), + ])); + } + + // Setup a basic mutator with a mutational stage + let mutator = StdScheduledMutator::new(havoc_mutations()); + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + frida_helper.register_thread(); + // Create the executor for an in-process function with just one observer for edge coverage + let mut executor = FridaInProcessExecutor::new( + &gum, + InProcessExecutor::new( + &mut frida_harness, + tuple_list!( + edges_observer, + time_observer, + AsanErrorsObserver::new(&ASAN_ERRORS) + ), + &mut fuzzer, + &mut state, + &mut mgr, + )?, + &mut frida_helper, + Duration::new(10, 0), + ); + // Let's exclude the main module and libc.so at least: + //executor.stalker.exclude(&MemoryRange::new( + //Module::find_base_address(&env::args().next().unwrap()), + //get_module_size(&env::args().next().unwrap()), + //)); + //executor.stalker.exclude(&MemoryRange::new( + //Module::find_base_address("libc.so"), + //get_module_size("libc.so"), + //)); + + // In case the corpus is empty (on first run), reset + if state.corpus().count() < 1 { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &corpus_dirs) + .unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", &corpus_dirs)); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + Ok(()) + }; + + Launcher::builder() + .shmem_provider(shmem_provider) + .stats(stats) + .client_init_stats(&mut client_init_stats) + .run_client(&mut run_client) + .cores(cores) + .broker_port(broker_port) + .stdout_file(stdout_file) + .remote_broker_addr(broker_addr) + .build() + .launch() } diff --git a/fuzzers/libfuzzer_libmozjpeg/Cargo.toml b/fuzzers/libfuzzer_libmozjpeg/Cargo.toml index 93d64aa431..0be5b90c1d 100644 --- a/fuzzers/libfuzzer_libmozjpeg/Cargo.toml +++ b/fuzzers/libfuzzer_libmozjpeg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfuzzer_libmozjpeg" -version = "0.2.0" +version = "0.3.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 4801eb9d22..edf4f29717 100644 --- a/fuzzers/libfuzzer_libmozjpeg/src/lib.rs +++ b/fuzzers/libfuzzer_libmozjpeg/src/lib.rs @@ -4,7 +4,7 @@ use std::{env, path::PathBuf}; use libafl::{ - bolts::tuples::tuple_list, + bolts::{current_nanos, rands::StdRand, tuples::tuple_list}, corpus::{Corpus, InMemoryCorpus, OnDiskCorpus, RandCorpusScheduler}, events::setup_restarting_mgr_std, executors::{inprocess::InProcessExecutor, ExitKind}, @@ -17,7 +17,6 @@ use libafl::{ stages::mutational::StdMutationalStage, state::{HasCorpus, HasMetadata, StdState}, stats::SimpleStats, - utils::{current_nanos, StdRand}, Error, }; @@ -42,7 +41,7 @@ pub fn main() { env::current_dir().unwrap().to_string_lossy().to_string() ); fuzz( - vec![PathBuf::from("./corpus")], + &[PathBuf::from("./corpus")], PathBuf::from("./crashes"), 1337, ) @@ -50,13 +49,13 @@ pub fn main() { } /// The actual fuzzer -fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { +fn fuzz(corpus_dirs: &[PathBuf], 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) = - setup_restarting_mgr_std(stats, broker_port).expect("Failed to setup the restarter".into()); + setup_restarting_mgr_std(stats, broker_port).expect("Failed to setup the restarter"); // Create an observation channel using the coverage map let edges = unsafe { &mut EDGES_MAP[0..MAX_EDGES_NUM] }; @@ -155,10 +154,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> &mut restarting_mgr, &corpus_dirs, ) - .expect(&format!( - "Failed to load initial corpus at {:?}", - &corpus_dirs - )); + .unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", &corpus_dirs)); println!("We imported {} inputs from disk.", state.corpus().count()); } diff --git a/fuzzers/libfuzzer_libpng/Cargo.toml b/fuzzers/libfuzzer_libpng/Cargo.toml index 8c96bc5a4d..37362ea4e3 100644 --- a/fuzzers/libfuzzer_libpng/Cargo.toml +++ b/fuzzers/libfuzzer_libpng/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfuzzer_libpng" -version = "0.2.0" +version = "0.3.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 41c4884e8e..5c12a430cc 100644 --- a/fuzzers/libfuzzer_libpng/src/lib.rs +++ b/fuzzers/libfuzzer_libpng/src/lib.rs @@ -6,6 +6,7 @@ use std::{env, path::PathBuf}; use libafl::{ bolts::tuples::tuple_list, + bolts::{current_nanos, rands::StdRand}, corpus::{ Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler, @@ -21,13 +22,12 @@ use libafl::{ stages::mutational::StdMutationalStage, state::{HasCorpus, HasMetadata, StdState}, stats::SimpleStats, - utils::{current_nanos, StdRand}, Error, }; use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM}; -/// The main fn, no_mangle as it is a C main +/// The main fn, `no_mangle` as it is a C main #[no_mangle] pub fn main() { // Registry the metadata types used in this fuzzer @@ -39,7 +39,7 @@ pub fn main() { env::current_dir().unwrap().to_string_lossy().to_string() ); fuzz( - vec![PathBuf::from("./corpus")], + &[PathBuf::from("./corpus")], PathBuf::from("./crashes"), 1337, ) @@ -47,7 +47,7 @@ pub fn main() { } /// The actual fuzzer -fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { +fn fuzz(corpus_dirs: &[PathBuf], 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)); @@ -160,10 +160,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> &mut restarting_mgr, &corpus_dirs, ) - .expect(&format!( - "Failed to load initial corpus at {:?}", - &corpus_dirs - )); + .unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", &corpus_dirs)); println!("We imported {} inputs from disk.", state.corpus().count()); } diff --git a/fuzzers/libfuzzer_libpng_launcher/.gitignore b/fuzzers/libfuzzer_libpng_launcher/.gitignore new file mode 100644 index 0000000000..a977a2ca5b --- /dev/null +++ b/fuzzers/libfuzzer_libpng_launcher/.gitignore @@ -0,0 +1 @@ +libpng-* \ No newline at end of file diff --git a/fuzzers/libfuzzer_libpng_launcher/Cargo.toml b/fuzzers/libfuzzer_libpng_launcher/Cargo.toml new file mode 100644 index 0000000000..29807dc614 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_launcher/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "libfuzzer_libpng_launcher" +version = "0.3.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/" } +clap = { version = "3.0.0-beta.2", features = ["yaml"] } + +[lib] +name = "libfuzzer_libpng" +crate-type = ["staticlib"] diff --git a/fuzzers/libfuzzer_libpng_launcher/README.md b/fuzzers/libfuzzer_libpng_launcher/README.md new file mode 100644 index 0000000000..b96b0e851c --- /dev/null +++ b/fuzzers/libfuzzer_libpng_launcher/README.md @@ -0,0 +1,47 @@ +# Libfuzzer for libpng, with launcher + +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. + +In contrast to the normal libfuzzer libpng example, this uses the `launcher` feature, that automatically spawns `n` child processes, and binds them to a free core. + +## 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. + +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=../target/release/libafl_cc CXX=../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 +``` + +Afterwards, the fuzzer will be ready to run. + +## Run + +Just run once, the launcher feature should do the rest. \ No newline at end of file diff --git a/fuzzers/libfuzzer_libpng_launcher/corpus/not_kitty.png b/fuzzers/libfuzzer_libpng_launcher/corpus/not_kitty.png new file mode 100644 index 0000000000..eff7c1707b Binary files /dev/null and b/fuzzers/libfuzzer_libpng_launcher/corpus/not_kitty.png differ diff --git a/fuzzers/libfuzzer_libpng_launcher/corpus/not_kitty_alpha.png b/fuzzers/libfuzzer_libpng_launcher/corpus/not_kitty_alpha.png new file mode 100644 index 0000000000..2fb8da2c8f Binary files /dev/null and b/fuzzers/libfuzzer_libpng_launcher/corpus/not_kitty_alpha.png differ diff --git a/fuzzers/libfuzzer_libpng_launcher/corpus/not_kitty_gamma.png b/fuzzers/libfuzzer_libpng_launcher/corpus/not_kitty_gamma.png new file mode 100644 index 0000000000..939d9d29a9 Binary files /dev/null and b/fuzzers/libfuzzer_libpng_launcher/corpus/not_kitty_gamma.png differ diff --git a/fuzzers/libfuzzer_libpng_launcher/corpus/not_kitty_icc.png b/fuzzers/libfuzzer_libpng_launcher/corpus/not_kitty_icc.png new file mode 100644 index 0000000000..f0c7804d99 Binary files /dev/null and b/fuzzers/libfuzzer_libpng_launcher/corpus/not_kitty_icc.png differ diff --git a/fuzzers/libfuzzer_libpng_launcher/harness.cc b/fuzzers/libfuzzer_libpng_launcher/harness.cc new file mode 100644 index 0000000000..65faff685d --- /dev/null +++ b/fuzzers/libfuzzer_libpng_launcher/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_launcher/src/bin/libafl_cc.rs b/fuzzers/libfuzzer_libpng_launcher/src/bin/libafl_cc.rs new file mode 100644 index 0000000000..dc65a9a57b --- /dev/null +++ b/fuzzers/libfuzzer_libpng_launcher/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_libpng_launcher/src/bin/libafl_cxx.rs b/fuzzers/libfuzzer_libpng_launcher/src/bin/libafl_cxx.rs new file mode 100644 index 0000000000..2183682d96 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_launcher/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_libpng_launcher/src/clap-config.yaml b/fuzzers/libfuzzer_libpng_launcher/src/clap-config.yaml new file mode 100644 index 0000000000..387fe0fb54 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_launcher/src/clap-config.yaml @@ -0,0 +1,12 @@ +name: libfuzzer libpng +version: "0.1.0" +author: "Andrea Fioraldi , Dominik Maier " +about: A clone of libfuzzer using libafl for a libpng harness. +args: + - cores: + short: c + long: cores + about: "spawn a client in each of the provided cores. Broker runs in the 0th core. 'all' to select all available cores. 'none' to run a client without binding to any core. eg: '1,2-4,6' selects the cores 1,2,3,4,6." + value_name: CORES + required: true + takes_value: true diff --git a/fuzzers/libfuzzer_libpng_launcher/src/lib.rs b/fuzzers/libfuzzer_libpng_launcher/src/lib.rs new file mode 100644 index 0000000000..7d63b52440 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_launcher/src/lib.rs @@ -0,0 +1,178 @@ +//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts +//! The example harness is built for libpng. +//! In this example, you will see the use of the `launcher` feature. +//! The `launcher` will spawn new processes for each cpu core. + +use clap::{load_yaml, App}; +use core::time::Duration; +use std::{env, path::PathBuf}; + +use libafl::{ + bolts::{ + current_nanos, + launcher::Launcher, + os::parse_core_bind_arg, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::tuple_list, + }, + corpus::{ + Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, + QueueCorpusScheduler, + }, + executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, + feedback_or, + feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + mutators::scheduled::{havoc_mutations, StdScheduledMutator}, + mutators::token_mutations::Tokens, + observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, + stages::mutational::StdMutationalStage, + state::{HasCorpus, HasMetadata, StdState}, + stats::SimpleStats, +}; + +use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM}; + +/// 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::(); + let yaml = load_yaml!("clap-config.yaml"); + let matches = App::from(yaml).get_matches(); + + let broker_port = 1337; + + let cores = parse_core_bind_arg(&matches.value_of("cores").unwrap()) + .expect("No valid core count given!"); + + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + + #[cfg(target_os = "android")] + AshmemService::start().expect("Failed to start Ashmem service"); + let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); + + let stats_closure = |s| println!("{}", s); + let stats = SimpleStats::new(stats_closure); + let mut client_init_stats = || Ok(SimpleStats::new(stats_closure)); + + let mut run_client = |state: Option>, mut restarting_mgr| { + let corpus_dirs = &[PathBuf::from("./corpus")]; + let objective_dir = PathBuf::from("./crashes"); + + // Create an observation channel using the coverage map + let edges = unsafe { &mut EDGES_MAP[0..MAX_EDGES_NUM] }; + let edges_observer = HitcountsMapObserver::new(StdMapObserver::new("edges", edges)); + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + // The state of the edges feedback. + let feedback_state = MapFeedbackState::with_observer(&edges_observer); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false), + // Time feedback, this one does not need a feedback state + TimeFeedback::new_with_observer(&time_observer) + ); + + // A feedback to choose if an input is a solution or not + let objective = feedback_or!(CrashFeedback::new(), TimeoutFeedback::new()); + + // If not restarting, create a State from scratch + let mut state = state.unwrap_or_else(|| { + StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(objective_dir).unwrap(), + // States of the feedbacks. + // They are the data related to the feedbacks that you want to persist in the State. + tuple_list!(feedback_state), + ) + }); + + 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 mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + // The wrapped harness function, calling out to the LLVM-style harness + 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( + &mut harness, + tuple_list!(edges_observer, time_observer), + &mut fuzzer, + &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 fuzzer, &mut executor, &mut restarting_mgr, corpus_dirs) + .unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", corpus_dirs)); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?; + Ok(()) + }; + + Launcher::builder() + .shmem_provider(shmem_provider) + .stats(stats) + .client_init_stats(&mut client_init_stats) + .run_client(&mut run_client) + .cores(&cores) + .broker_port(broker_port) + .stdout_file(Some("/dev/null")) + .build() + .launch() + .expect("Launcher failed"); +} diff --git a/fuzzers/libfuzzer_reachability/Cargo.toml b/fuzzers/libfuzzer_reachability/Cargo.toml index 86a79eb22f..b18ae74a9f 100644 --- a/fuzzers/libfuzzer_reachability/Cargo.toml +++ b/fuzzers/libfuzzer_reachability/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfuzzer_reachability" -version = "0.2.0" +version = "0.3.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 23c2fc51ab..f51b22cb86 100644 --- a/fuzzers/libfuzzer_reachability/src/lib.rs +++ b/fuzzers/libfuzzer_reachability/src/lib.rs @@ -4,7 +4,7 @@ use std::{env, path::PathBuf}; use libafl::{ - bolts::tuples::tuple_list, + bolts::{current_nanos, rands::StdRand, tuples::tuple_list}, corpus::{Corpus, InMemoryCorpus, OnDiskCorpus, RandCorpusScheduler}, events::{setup_restarting_mgr_std, EventRestarter}, executors::{inprocess::InProcessExecutor, ExitKind}, @@ -16,7 +16,6 @@ use libafl::{ stages::mutational::StdMutationalStage, state::{HasCorpus, HasMetadata, StdState}, stats::SimpleStats, - utils::{current_nanos, StdRand}, Error, }; use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM}; @@ -27,7 +26,7 @@ extern "C" { static __libafl_target_list: *mut usize; } -/// The main fn, no_mangle as it is a C main +/// The main fn, `no_mangle` as it is a C main #[no_mangle] pub fn main() { // Registry the metadata types used in this fuzzer @@ -39,7 +38,7 @@ pub fn main() { env::current_dir().unwrap().to_string_lossy().to_string() ); fuzz( - vec![PathBuf::from("./corpus")], + &[PathBuf::from("./corpus")], PathBuf::from("./crashes"), 1337, ) @@ -47,7 +46,7 @@ pub fn main() { } /// The actual fuzzer -fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { +fn fuzz(corpus_dirs: &[PathBuf], 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)); @@ -150,10 +149,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> &mut restarting_mgr, &corpus_dirs, ) - .expect(&format!( - "Failed to load initial corpus at {:?}", - &corpus_dirs - )); + .unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", &corpus_dirs)); println!("We imported {} inputs from disk.", state.corpus().count()); } diff --git a/fuzzers/libfuzzer_stb_image/Cargo.toml b/fuzzers/libfuzzer_stb_image/Cargo.toml index db0a4b5e25..a49a54a81c 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.2.0" +version = "0.3.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 ba093c35e4..090ba79492 100644 --- a/fuzzers/libfuzzer_stb_image/src/main.rs +++ b/fuzzers/libfuzzer_stb_image/src/main.rs @@ -1,10 +1,10 @@ //! A libfuzzer-like fuzzer with llmp-multithreading support and restarts -//! The example harness is built for stb_image. +//! The example harness is built for `stb_image`. use std::{env, path::PathBuf}; use libafl::{ - bolts::tuples::tuple_list, + bolts::{current_nanos, rands::StdRand, tuples::tuple_list}, corpus::{ Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler, @@ -20,7 +20,6 @@ use libafl::{ stages::mutational::StdMutationalStage, state::{HasCorpus, HasMetadata, StdState}, stats::SimpleStats, - utils::{current_nanos, StdRand}, Error, }; @@ -36,7 +35,7 @@ pub fn main() { env::current_dir().unwrap().to_string_lossy().to_string() ); fuzz( - vec![PathBuf::from("./corpus")], + &[PathBuf::from("./corpus")], PathBuf::from("./crashes"), 1337, ) @@ -44,7 +43,7 @@ pub fn main() { } /// The actual fuzzer -fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { +fn fuzz(corpus_dirs: &[PathBuf], 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)); @@ -154,10 +153,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> &mut restarting_mgr, &corpus_dirs, ) - .expect(&format!( - "Failed to load initial corpus at {:?}", - &corpus_dirs - )); + .unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", &corpus_dirs)); println!("We imported {} inputs from disk.", state.corpus().count()); } diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 6c6a6bf901..432becf93a 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl" -version = "0.2.1" +version = "0.3.0" authors = ["Andrea Fioraldi ", "Dominik Maier "] description = "Slot your own fuzzers together and extend their features using Rust" documentation = "https://docs.rs/libafl" @@ -37,7 +37,7 @@ harness = false [features] default = ["std", "anymap_debug", "derive", "llmp_compression"] -std = [] # print, sharedmap, ... support +std = [] # print, env, launcher ... support anymap_debug = ["serde_json"] # uses serde_json to Debug the anymap trait. Disable for smaller footprint. derive = ["libafl_derive"] # provide derive(SerdeAny) macro. llmp_bind_public = [] # If set, llmp will bind to 0.0.0.0, allowing cross-device communication. Binds to localhost by default. @@ -52,6 +52,7 @@ path = "./examples/llmp_test/main.rs" required-features = ["std"] [dependencies] +libafl_derive = { optional = true, path = "../libafl_derive", version = "0.3.0" } tuple_list = "0.1.2" hashbrown = { version = "0.9", features = ["serde", "ahash-compile-time-rng"] } # A faster hashmap, nostd compatible num = "0.4.0" @@ -61,11 +62,12 @@ erased-serde = "0.3.12" postcard = { version = "0.5.1", features = ["alloc"] } # no_std compatible serde serialization fromat static_assertions = "1.1.0" ctor = "0.1.20" -libafl_derive = { optional = true, path = "../libafl_derive", version = "0.2.1" } serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } # an easy way to debug print SerdeAnyMap compression = { version = "0.1.5" } +core_affinity = { version = "0.5", git = "https://github.com/s1341/core_affinity_rs" } num_enum = "0.5.1" hostname = "^0.3" # Is there really no gethostname in the stdlib? +typed-builder = "0.9.0" [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/benches/hash_speeds.rs b/libafl/benches/hash_speeds.rs index a443dfc7e6..fae7f5884b 100644 --- a/libafl/benches/hash_speeds.rs +++ b/libafl/benches/hash_speeds.rs @@ -7,7 +7,7 @@ use xxhash_rust::const_xxh3; use xxhash_rust::xxh3; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use libafl::utils::{Rand, StdRand}; +use libafl::bolts::rands::{Rand, StdRand}; fn criterion_benchmark(c: &mut Criterion) { let mut rand = StdRand::with_seed(0); diff --git a/libafl/benches/rand_speeds.rs b/libafl/benches/rand_speeds.rs index 4ae294298c..099420cfbb 100644 --- a/libafl/benches/rand_speeds.rs +++ b/libafl/benches/rand_speeds.rs @@ -1,7 +1,7 @@ //! Compare the speed of rand implementations use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use libafl::utils::{ +use libafl::bolts::rands::{ Lehmer64Rand, Rand, RomuDuoJrRand, RomuTrioRand, XorShift64Rand, Xoshiro256StarRand, }; diff --git a/libafl/build.rs b/libafl/build.rs index 15a69aaa4e..8baee9213b 100644 --- a/libafl/build.rs +++ b/libafl/build.rs @@ -2,8 +2,10 @@ use rustc_version::{version_meta, Channel}; +#[allow(clippy::ptr_arg, clippy::upper_case_acronyms)] fn main() { #[cfg(target_os = "windows")] + #[allow(clippy::ptr_arg, clippy::upper_case_acronyms)] windows::build!( windows::win32::system_services::{HANDLE, BOOL, PAGE_TYPE, PSTR, ExitProcess}, windows::win32::windows_programming::CloseHandle, diff --git a/libafl/src/bolts/compress.rs b/libafl/src/bolts/compress.rs index 1f2fe96631..a5a7b313f6 100644 --- a/libafl/src/bolts/compress.rs +++ b/libafl/src/bolts/compress.rs @@ -19,7 +19,7 @@ impl GzipCompressor { /// When given a `threshold` of `0`, the `GzipCompressor` will always compress. #[must_use] pub fn new(threshold: usize) -> Self { - GzipCompressor { threshold } + Self { threshold } } } diff --git a/libafl/src/bolts/launcher.rs b/libafl/src/bolts/launcher.rs new file mode 100644 index 0000000000..5479f5b8ed --- /dev/null +++ b/libafl/src/bolts/launcher.rs @@ -0,0 +1,244 @@ +#[cfg(feature = "std")] +use serde::de::DeserializeOwned; + +#[cfg(feature = "std")] +use crate::{ + bolts::shmem::ShMemProvider, + events::{LlmpRestartingEventManager, ManagerKind, RestartingMgr}, + inputs::Input, + observers::ObserversTuple, + stats::Stats, + Error, +}; + +#[cfg(all(windows, feature = "std"))] +use crate::bolts::os::startable_self; + +#[cfg(all(unix, feature = "std"))] +use crate::bolts::os::{dup2, fork, ForkResult}; + +#[cfg(all(unix, feature = "std"))] +use std::{fs::File, os::unix::io::AsRawFd}; + +#[cfg(feature = "std")] +use std::net::SocketAddr; +#[cfg(all(windows, feature = "std"))] +use std::process::Stdio; + +#[cfg(all(windows, feature = "std"))] +use core_affinity::CoreId; + +#[cfg(feature = "std")] +use typed_builder::TypedBuilder; + +/// The Launcher client callback type reference +#[cfg(feature = "std")] +pub type LauncherClientFnRef<'a, I, OT, S, SP, ST> = + &'a mut dyn FnMut(Option, LlmpRestartingEventManager) -> Result<(), Error>; + +/// Provides a Launcher, which can be used to launch a fuzzing run on a specified list of cores +#[cfg(feature = "std")] +#[derive(TypedBuilder)] +#[allow(clippy::type_complexity)] +pub struct Launcher<'a, I, OT, S, SP, ST> +where + I: Input, + ST: Stats, + SP: ShMemProvider + 'static, + OT: ObserversTuple, + S: DeserializeOwned, +{ + /// The ShmemProvider to use + shmem_provider: SP, + /// The stats instance to use + stats: ST, + /// A closure or function which generates stats instances for newly spawned clients + client_init_stats: &'a mut dyn FnMut() -> Result, + /// The 'main' function to run for each client forked. This probably shouldn't return + run_client: LauncherClientFnRef<'a, I, OT, S, SP, ST>, + /// The broker port to use + #[builder(default = 1337_u16)] + broker_port: u16, + /// The list of cores to run on + cores: &'a [usize], + /// A file name to write all client output to + #[builder(default = None)] + stdout_file: Option<&'a str>, + /// The `ip:port` address of another broker to connect our new broker to for multi-machine + /// clusters. + #[builder(default = None)] + remote_broker_addr: Option, +} + +#[cfg(feature = "std")] +impl<'a, I, OT, S, SP, ST> Launcher<'a, I, OT, S, SP, ST> +where + I: Input, + OT: ObserversTuple, + ST: Stats + Clone, + SP: ShMemProvider + 'static, + S: DeserializeOwned, +{ + /// Launch the broker and the clients and fuzz + #[cfg(all(unix, feature = "std"))] + #[allow(clippy::similar_names)] + pub fn launch(&mut self) -> Result<(), Error> { + let core_ids = core_affinity::get_core_ids().unwrap(); + let num_cores = core_ids.len(); + let mut handles = vec![]; + + println!("spawning on cores: {:?}", self.cores); + let file = self + .stdout_file + .map(|filename| File::create(filename).unwrap()); + + //spawn clients + for (id, bind_to) in core_ids.iter().enumerate().take(num_cores) { + if self.cores.iter().any(|&x| x == id) { + self.shmem_provider.pre_fork()?; + match unsafe { fork() }? { + ForkResult::Parent(child) => { + self.shmem_provider.post_fork(false)?; + handles.push(child.pid); + #[cfg(feature = "std")] + println!("child spawned and bound to core {}", id); + } + ForkResult::Child => { + self.shmem_provider.post_fork(true)?; + + #[cfg(feature = "std")] + std::thread::sleep(std::time::Duration::from_secs((id + 1) as u64)); + + #[cfg(feature = "std")] + if file.is_some() { + dup2(file.as_ref().unwrap().as_raw_fd(), libc::STDOUT_FILENO)?; + dup2(file.as_ref().unwrap().as_raw_fd(), libc::STDERR_FILENO)?; + } + //fuzzer client. keeps retrying the connection to broker till the broker starts + let stats = (self.client_init_stats)()?; + let (state, mgr) = RestartingMgr::builder() + .shmem_provider(self.shmem_provider.clone()) + .stats(stats) + .broker_port(self.broker_port) + .kind(ManagerKind::Client { + cpu_core: Some(*bind_to), + }) + .build() + .launch()?; + + (self.run_client)(state, mgr)?; + break; + } + }; + } + } + #[cfg(feature = "std")] + println!("I am broker!!."); + + RestartingMgr::::builder() + .shmem_provider(self.shmem_provider.clone()) + .stats(self.stats.clone()) + .broker_port(self.broker_port) + .kind(ManagerKind::Broker) + .remote_broker_addr(self.remote_broker_addr) + .build() + .launch()?; + + //broker exited. kill all clients. + for handle in &handles { + unsafe { + libc::kill(*handle, libc::SIGINT); + } + } + + Ok(()) + } + + /// Launch the broker and the clients and fuzz + #[cfg(all(windows, feature = "std"))] + #[allow(unused_mut)] + pub fn launch(&mut self) -> Result<(), Error> { + let is_client = std::env::var(_AFL_LAUNCHER_CLIENT); + + let mut handles = match is_client { + Ok(core_conf) => { + //todo: silence stdout and stderr for clients + + // the actual client. do the fuzzing + let stats = (self.client_init_stats)()?; + let (state, mgr) = RestartingMgr::::builder() + .shmem_provider(self.shmem_provider.clone()) + .stats(stats) + .broker_port(self.broker_port) + .kind(ManagerKind::Client { + cpu_core: Some(CoreId { + id: core_conf.parse()?, + }), + }) + .build() + .launch()?; + + (self.run_client)(state, mgr)?; + + unreachable!("Fuzzer client code should never get here!"); + } + Err(std::env::VarError::NotPresent) => { + // I am a broker + // before going to the broker loop, spawn n clients + + if self.stdout_file.is_some() { + println!("Child process file stdio is not supported on Windows yet. Dumping to stdout instead..."); + } + + let core_ids = core_affinity::get_core_ids().unwrap(); + let num_cores = core_ids.len(); + let mut handles = vec![]; + + println!("spawning on cores: {:?}", self.cores); + + //spawn clients + for (id, _) in core_ids.iter().enumerate().take(num_cores) { + if self.cores.iter().any(|&x| x == id) { + for id in 0..num_cores { + let stdio = if self.stdout_file.is_some() { + Stdio::inherit() + } else { + Stdio::null() + }; + + if self.cores.iter().any(|&x| x == id) { + std::env::set_var(_AFL_LAUNCHER_CLIENT, id.to_string()); + let child = startable_self()?.stdout(stdio).spawn()?; + handles.push(child); + } + } + } + } + + handles + } + Err(_) => panic!("Env variables are broken, received non-unicode!"), + }; + + #[cfg(feature = "std")] + println!("I am broker!!."); + + RestartingMgr::::builder() + .shmem_provider(self.shmem_provider.clone()) + .stats(self.stats.clone()) + .broker_port(self.broker_port) + .kind(ManagerKind::Broker) + .remote_broker_addr(self.remote_broker_addr) + .build() + .launch()?; + + //broker exited. kill all clients. + for handle in &mut handles { + handle.kill()?; + } + + Ok(()) + } +} + +const _AFL_LAUNCHER_CLIENT: &str = &"AFL_LAUNCHER_CLIENT"; diff --git a/libafl/src/bolts/llmp.rs b/libafl/src/bolts/llmp.rs index b7b7585aa8..404dff511c 100644 --- a/libafl/src/bolts/llmp.rs +++ b/libafl/src/bolts/llmp.rs @@ -70,11 +70,12 @@ use core::{ time::Duration, }; use serde::{Deserialize, Serialize}; + #[cfg(feature = "std")] use std::{ convert::TryInto, env, - io::{Read, Write}, + io::{ErrorKind, Read, Write}, net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs}, sync::mpsc::channel, thread, @@ -91,6 +92,10 @@ use crate::{ }; #[cfg(unix)] use libc::ucontext_t; +#[cfg(all(unix, feature = "std"))] +use nix::sys::socket::{self, sockopt::ReusePort}; +#[cfg(all(unix, feature = "std"))] +use std::os::unix::io::AsRawFd; /// We'll start off with 256 megabyte maps per fuzzer client #[cfg(not(feature = "llmp_small_maps"))] @@ -323,6 +328,19 @@ fn msg_offset_from_env(env_name: &str) -> Result, Error> { }) } +/// Bind to a tcp port on the [`_LLMP_BIND_ADDR`] (local, or global) +/// on a given `port`. +/// Will set `SO_REUSEPORT` on unix. +#[cfg(feature = "std")] +fn tcp_bind(port: u16) -> Result { + let listener = TcpListener::bind((_LLMP_BIND_ADDR, port))?; + + #[cfg(unix)] + socket::setsockopt(listener.as_raw_fd(), ReusePort, &true)?; + + Ok(listener) +} + /// Send one message as `u32` len and `[u8;len]` bytes #[cfg(feature = "std")] fn send_tcp_msg(stream: &mut TcpStream, msg: &T) -> Result<(), Error> @@ -560,30 +578,50 @@ 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!("{}:{}", _LLMP_BIND_ADDR, port)) { + match tcp_bind(port) { Ok(listener) => { // We got the port. We are the broker! :) dbg!("We're the broker"); + let mut broker = LlmpBroker::new(shmem_provider)?; let _listener_thread = broker.launch_listener(Listener::Tcp(listener))?; Ok(LlmpConnection::IsBroker { broker }) } - Err(e) => { - println!("error: {:?}", e); - match e.kind() { - std::io::ErrorKind::AddrInUse => { - // We are the client :) - dbg!("We're the client", e); - Ok(LlmpConnection::IsClient { - client: LlmpClient::create_attach_to_tcp(shmem_provider, port)?, - }) - } - _ => Err(Error::File(e)), - } + Err(Error::File(e)) if e.kind() == ErrorKind::AddrInUse => { + // We are the client :) + println!( + "We're the client (internal port already bound by broker, {:#?})", + e + ); + Ok(LlmpConnection::IsClient { + client: LlmpClient::create_attach_to_tcp(shmem_provider, port)?, + }) } + Err(e) => Err(dbg!(e)), } } + /// Creates a new broker on the given port + #[cfg(feature = "std")] + pub fn broker_on_port(shmem_provider: SP, port: u16) -> Result { + match tcp_bind(port) { + Ok(listener) => { + let mut broker = LlmpBroker::new(shmem_provider)?; + let _listener_thread = broker.launch_listener(Listener::Tcp(listener))?; + Ok(LlmpConnection::IsBroker { broker }) + } + Err(e) => Err(e), + } + } + + /// Creates a new client on the given port + #[cfg(feature = "std")] + pub fn client_on_port(shmem_provider: SP, port: u16) -> Result { + Ok(LlmpConnection::IsClient { + client: LlmpClient::create_attach_to_tcp(shmem_provider, port)?, + }) + } + /// Describe this in a reproducable fashion, if it's a client pub fn describe(&self) -> Result { Ok(match self { @@ -1671,7 +1709,7 @@ where // TODO: handle broker_ids properly/at all. let map_description = Self::b2b_thread_on( stream, - &self.shmem_provider, + &mut self.shmem_provider, self.llmp_clients.len() as ClientId, &self.llmp_out.out_maps.first().unwrap().shmem.description(), )?; @@ -1787,7 +1825,7 @@ where /// 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!("{}:{}", _LLMP_BIND_ADDR, port))?; + let listener = tcp_bind(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)) @@ -1822,11 +1860,13 @@ where #[allow(clippy::let_and_return)] fn b2b_thread_on( mut stream: TcpStream, - shmem_provider: &SP, + shmem_provider: &mut SP, b2b_client_id: ClientId, broker_map_description: &ShMemDescription, ) -> Result { let broker_map_description = *broker_map_description; + + shmem_provider.pre_fork()?; let mut shmem_provider_clone = shmem_provider.clone(); // A channel to get the new "client's" sharedmap id from @@ -1835,7 +1875,7 @@ where // (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(); + shmem_provider_clone.post_fork(true).unwrap(); #[cfg(fature = "llmp_debug")] println!("B2b: Spawned proxy thread"); @@ -1927,6 +1967,8 @@ where } }); + shmem_provider.post_fork(false)?; + let ret = recv.recv().map_err(|_| { Error::Unknown("Error launching background thread for b2b communcation".to_string()) }); @@ -1944,7 +1986,7 @@ where request: &TcpRequest, current_client_id: &mut u32, sender: &mut LlmpSender, - shmem_provider: &SP, + shmem_provider: &mut SP, broker_map_description: &ShMemDescription, ) { match request { @@ -2024,11 +2066,12 @@ where let tcp_out_map_description = tcp_out_map.shmem.description(); self.register_client(tcp_out_map); + self.shmem_provider.pre_fork()?; let mut shmem_provider_clone = self.shmem_provider.clone(); - Ok(thread::spawn(move || { + let ret = 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(); + shmem_provider_clone.post_fork(true).unwrap(); let mut current_client_id = llmp_tcp_id + 1; @@ -2080,7 +2123,7 @@ where &req, &mut current_client_id, &mut tcp_incoming_sender, - &shmem_provider_clone, + &mut shmem_provider_clone, &broker_map_description, ); } @@ -2089,7 +2132,10 @@ where } }; } - })) + }); + + self.shmem_provider.post_fork(false)?; + Ok(ret) } /// broker broadcast to its own page for all others to read */ @@ -2411,7 +2457,25 @@ 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!("{}:{}", _LLMP_BIND_ADDR, port))?; + let mut stream = match TcpStream::connect(format!("{}:{}", _LLMP_BIND_ADDR, port)) { + Ok(stream) => stream, + Err(e) => { + match e.kind() { + std::io::ErrorKind::ConnectionRefused => { + //connection refused. loop till the broker is up + loop { + match TcpStream::connect(format!("{}:{}", _LLMP_BIND_ADDR, port)) { + Ok(stream) => break stream, + Err(_) => { + dbg!("Connection Refused.. Retrying"); + } + } + } + } + _ => return Err(Error::IllegalState(e.to_string())), + } + } + }; println!("Connected to port {}", port); let broker_map_description = if let TcpResponse::BrokerConnectHello { diff --git a/libafl/src/bolts/mod.rs b/libafl/src/bolts/mod.rs index 62c8830b95..6fb28759a3 100644 --- a/libafl/src/bolts/mod.rs +++ b/libafl/src/bolts/mod.rs @@ -1,13 +1,55 @@ //! Bolts are no conceptual fuzzing elements, but they keep libafl-based fuzzers together. pub mod bindings; +pub mod launcher; +pub mod llmp; +pub mod os; +pub mod ownedref; +pub mod rands; +pub mod serdeany; +pub mod shmem; +pub mod tuples; #[cfg(feature = "llmp_compression")] pub mod compress; -pub mod llmp; -pub mod os; -pub mod ownedref; -pub mod serdeany; -pub mod shmem; -pub mod tuples; +use core::time; +#[cfg(feature = "std")] +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Can be converted to a slice +pub trait AsSlice { + /// Convert to a slice + fn as_slice(&self) -> &[T]; +} + +/// Current time +#[cfg(feature = "std")] +#[must_use] +#[inline] +pub fn current_time() -> time::Duration { + SystemTime::now().duration_since(UNIX_EPOCH).unwrap() +} + +/// Current time (fixed fallback for no_std) +#[cfg(not(feature = "std"))] +#[inline] +pub fn current_time() -> time::Duration { + // We may not have a rt clock available. + // TODO: Make it somehow plugin-able + time::Duration::from_millis(1) +} + +/// Gets current nanoseconds since [`UNIX_EPOCH`] +#[must_use] +#[inline] +pub fn current_nanos() -> u64 { + current_time().as_nanos() as u64 +} + +/// Gets current milliseconds since [`UNIX_EPOCH`] +#[must_use] +#[inline] +pub fn current_milliseconds() -> u64 { + current_time().as_millis() as u64 +} diff --git a/libafl/src/bolts/os/ashmem_server.rs b/libafl/src/bolts/os/ashmem_server.rs index 9177225b5e..40d526f779 100644 --- a/libafl/src/bolts/os/ashmem_server.rs +++ b/libafl/src/bolts/os/ashmem_server.rs @@ -76,8 +76,8 @@ impl ShMem for ServedShMem { impl ServedShMemProvider { /// Send a request to the server, and wait for a response #[allow(clippy::similar_names)] // id and fd - fn send_receive(&mut self, request: AshmemRequest) -> (i32, i32) { - let body = postcard::to_allocvec(&request).unwrap(); + fn send_receive(&mut self, request: AshmemRequest) -> Result<(i32, i32), Error> { + let body = postcard::to_allocvec(&request)?; let header = (body.len() as u32).to_be_bytes(); let mut message = header.to_vec(); @@ -95,8 +95,8 @@ impl ServedShMemProvider { let server_id = ShMemId::from_slice(&shm_slice); let server_id_str = server_id.to_string(); - let server_fd: i32 = server_id_str.parse().unwrap(); - (server_fd, fd_buf[0]) + let server_fd: i32 = server_id_str.parse()?; + Ok((server_fd, fd_buf[0])) } } @@ -118,18 +118,16 @@ impl ShMemProvider for ServedShMemProvider { /// Connect to the server and return a new [`ServedShMemProvider`] fn new() -> Result { let mut res = Self { - stream: UnixStream::connect_to_unix_addr( - &UnixSocketAddr::new(ASHMEM_SERVER_NAME).unwrap(), - )?, + stream: UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(ASHMEM_SERVER_NAME)?)?, inner: AshmemShMemProvider::new()?, id: -1, }; - let (id, _) = res.send_receive(AshmemRequest::Hello(None)); + let (id, _) = res.send_receive(AshmemRequest::Hello(None))?; res.id = id; Ok(res) } fn new_map(&mut self, map_size: usize) -> Result { - let (server_fd, client_fd) = self.send_receive(AshmemRequest::NewMap(map_size)); + let (server_fd, client_fd) = self.send_receive(AshmemRequest::NewMap(map_size))?; Ok(ServedShMem { inner: ManuallyDrop::new( @@ -145,7 +143,7 @@ impl ShMemProvider for ServedShMemProvider { let server_id_str = parts.get(0).unwrap(); let (server_fd, client_fd) = self.send_receive(AshmemRequest::ExistingMap( ShMemDescription::from_string_and_size(server_id_str, size), - )); + ))?; Ok(ServedShMem { inner: ManuallyDrop::new( self.inner @@ -155,16 +153,21 @@ impl ShMemProvider for ServedShMemProvider { }) } - fn post_fork(&mut self) { - self.stream = - UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(ASHMEM_SERVER_NAME).unwrap()) - .expect("Unable to reconnect to the ashmem service"); - let (id, _) = self.send_receive(AshmemRequest::Hello(Some(self.id))); - self.id = id; + fn post_fork(&mut self, is_child: bool) -> Result<(), Error> { + if is_child { + // After fork, the child needs to reconnect as to not share the fds with the parent. + self.stream = + UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(ASHMEM_SERVER_NAME)?)?; + let (id, _) = self.send_receive(AshmemRequest::Hello(Some(self.id)))?; + self.id = id; + } + Ok(()) } fn release_map(&mut self, map: &mut Self::Mem) { - let (refcount, _) = self.send_receive(AshmemRequest::Deregister(map.server_fd)); + let (refcount, _) = self + .send_receive(AshmemRequest::Deregister(map.server_fd)) + .expect("Could not communicate to AshMem server!"); if refcount == 0 { unsafe { ManuallyDrop::drop(&mut map.inner); diff --git a/libafl/src/bolts/os/mod.rs b/libafl/src/bolts/os/mod.rs index 6aacc14fd8..06717ecb5d 100644 --- a/libafl/src/bolts/os/mod.rs +++ b/libafl/src/bolts/os/mod.rs @@ -1,9 +1,200 @@ //! Operating System specific abstractions +use alloc::vec::Vec; + +#[cfg(any(unix, all(windows, feature = "std")))] +use crate::Error; + +#[cfg(feature = "std")] +use std::{env, process::Command}; + #[cfg(all(unix, feature = "std"))] pub mod ashmem_server; #[cfg(unix)] pub mod unix_signals; -#[cfg(windows)] + +#[cfg(unix)] +pub mod pipes; + +#[cfg(all(unix, feature = "std"))] +use std::{ffi::CString, fs::File}; + +#[cfg(all(windows, feature = "std"))] pub mod windows_exceptions; + +#[cfg(unix)] +use libc::pid_t; + +/// Child Process Handle +#[cfg(unix)] +pub struct ChildHandle { + pub pid: pid_t, +} + +#[cfg(unix)] +impl ChildHandle { + /// Block until the child exited and the status code becomes available + #[must_use] + pub fn status(&self) -> i32 { + let mut status = -1; + unsafe { + libc::waitpid(self.pid, &mut status, 0); + } + status + } +} + +/// The `ForkResult` (result of a fork) +#[cfg(unix)] +pub enum ForkResult { + /// The fork finished, we are the parent process. + /// The child has the handle `ChildHandle`. + Parent(ChildHandle), + /// The fork finished, we are the child process. + Child, +} + +/// Unix has forks. +/// # Safety +/// A Normal fork. Runs on in two processes. Should be memory safe in general. +#[cfg(unix)] +pub unsafe fn fork() -> Result { + match libc::fork() { + pid if pid > 0 => Ok(ForkResult::Parent(ChildHandle { pid })), + pid if pid < 0 => { + // Getting errno from rust is hard, we'll just let the libc print to stderr for now. + // In any case, this should usually not happen. + #[cfg(feature = "std")] + { + let err_str = CString::new("Fork failed").unwrap(); + libc::perror(err_str.as_ptr()); + } + Err(Error::Unknown(format!("Fork failed ({})", pid))) + } + _ => Ok(ForkResult::Child), + } +} + +/// Executes the current process from the beginning, as subprocess. +/// use `start_self.status()?` to wait for the child +#[cfg(feature = "std")] +pub fn startable_self() -> Result { + let mut startable = Command::new(env::current_exe()?); + startable + .current_dir(env::current_dir()?) + .args(env::args().skip(1)); + Ok(startable) +} + +/// Allows one to walk the mappings in /proc/self/maps, caling a callback function for each +/// mapping. +/// If the callback returns true, we stop the walk. +#[cfg(all(feature = "std", any(target_os = "linux", target_os = "android")))] +pub fn walk_self_maps(visitor: &mut dyn FnMut(usize, usize, String, String) -> bool) { + use regex::Regex; + use std::io::{BufRead, BufReader}; + let re = Regex::new(r"^(?P[0-9a-f]{8,16})-(?P[0-9a-f]{8,16}) (?P[-rwxp]{4}) (?P[0-9a-f]{8}) [0-9a-f]+:[0-9a-f]+ [0-9]+\s+(?P.*)$") + .unwrap(); + + let mapsfile = File::open("/proc/self/maps").expect("Unable to open /proc/self/maps"); + + for line in BufReader::new(mapsfile).lines() { + let line = line.unwrap(); + if let Some(caps) = re.captures(&line) { + if visitor( + usize::from_str_radix(caps.name("start").unwrap().as_str(), 16).unwrap(), + usize::from_str_radix(caps.name("end").unwrap().as_str(), 16).unwrap(), + caps.name("perm").unwrap().as_str().to_string(), + caps.name("path").unwrap().as_str().to_string(), + ) { + break; + }; + } + } +} + +/// Get the start and end address, permissions and path of the mapping containing a particular address +#[cfg(all(feature = "std", any(target_os = "linux", target_os = "android")))] +pub fn find_mapping_for_address(address: usize) -> Result<(usize, usize, String, String), Error> { + let mut result = (0, 0, "".to_string(), "".to_string()); + walk_self_maps(&mut |start, end, permissions, path| { + if start <= address && address < end { + result = (start, end, permissions, path); + true + } else { + false + } + }); + + if result.0 == 0 { + Err(Error::Unknown( + "Couldn't find a mapping for this address".to_string(), + )) + } else { + Ok(result) + } +} + +/// Get the start and end address of the mapping containing with a particular path +#[cfg(all(feature = "std", any(target_os = "linux", target_os = "android")))] +#[must_use] +pub fn find_mapping_for_path(libpath: &str) -> (usize, usize) { + let mut libstart = 0; + let mut libend = 0; + walk_self_maps(&mut |start, end, _permissions, path| { + if libpath == path { + if libstart == 0 { + libstart = start; + } + + libend = end; + } + false + }); + + (libstart, libend) +} + +/// "Safe" wrapper around dup2 +#[cfg(all(unix, feature = "std"))] +pub fn dup2(fd: i32, device: i32) -> Result<(), Error> { + match unsafe { libc::dup2(fd, device) } { + -1 => Err(Error::File(std::io::Error::last_os_error())), + _ => Ok(()), + } +} + +/// Parses core binding args from user input +/// Returns a Vec of CPU IDs. +/// `./fuzzer --cores 1,2-4,6` -> clients run in cores 1,2,3,4,6 +/// ` ./fuzzer --cores all` -> one client runs on each available core +#[must_use] +pub fn parse_core_bind_arg(args: &str) -> Option> { + let mut cores: Vec = vec![]; + if args == "all" { + let num_cores = core_affinity::get_core_ids().unwrap().len(); + for x in 0..num_cores { + cores.push(x); + } + } else { + let core_args: Vec<&str> = args.split(',').collect(); + + // ./fuzzer --cores 1,2-4,6 -> clients run in cores 1,2,3,4,6 + // ./fuzzer --cores all -> one client runs in each available core + for csv in core_args { + let core_range: Vec<&str> = csv.split('-').collect(); + if core_range.len() == 1 { + cores.push(core_range[0].parse::().unwrap()); + } else if core_range.len() == 2 { + for x in core_range[0].parse::().unwrap() + ..=(core_range[1].parse::().unwrap()) + { + cores.push(x); + } + } + } + } + + Some(cores) +} diff --git a/libafl/src/bolts/os/pipes.rs b/libafl/src/bolts/os/pipes.rs new file mode 100644 index 0000000000..70388101eb --- /dev/null +++ b/libafl/src/bolts/os/pipes.rs @@ -0,0 +1,93 @@ +//! Unix `pipe` wrapper for `LibAFL` +use crate::Error; +use nix::unistd::{close, pipe}; + +#[cfg(feature = "std")] +use nix::unistd::{read, write}; +#[cfg(feature = "std")] +use std::{ + io::{self, ErrorKind, Read, Write}, + os::unix::io::RawFd, +}; + +#[cfg(not(feature = "std"))] +type RawFd = i32; + +#[derive(Debug, Clone)] +pub struct Pipe { + read_end: Option, + write_end: Option, +} + +impl Pipe { + pub fn new() -> Result { + let (read_end, write_end) = pipe()?; + Ok(Self { + read_end: Some(read_end), + write_end: Some(write_end), + }) + } + + pub fn close_read_end(&mut self) { + if let Some(read_end) = self.read_end { + let _ = close(read_end); + self.read_end = None; + } + } + + pub fn close_write_end(&mut self) { + if let Some(write_end) = self.write_end { + let _ = close(write_end); + self.write_end = None; + } + } +} + +#[cfg(feature = "std")] +impl Read for Pipe { + /// Reads a few bytes + fn read(&mut self, buf: &mut [u8]) -> Result { + match self.read_end { + Some(read_end) => match read(read_end, buf) { + Ok(res) => Ok(res), + Err(e) => Err(io::Error::from_raw_os_error(e.as_errno().unwrap() as i32)), + }, + None => Err(io::Error::new( + ErrorKind::BrokenPipe, + "Read pipe end was already closed", + )), + } + } +} + +#[cfg(feature = "std")] +impl Write for Pipe { + /// Writes a few bytes + fn write(&mut self, buf: &[u8]) -> Result { + match self.write_end { + Some(write_end) => match write(write_end, buf) { + Ok(res) => Ok(res), + Err(e) => Err(io::Error::from_raw_os_error(e.as_errno().unwrap() as i32)), + }, + None => Err(io::Error::new( + ErrorKind::BrokenPipe, + "Write pipe end was already closed", + )), + } + } + + fn flush(&mut self) -> Result<(), io::Error> { + Ok(()) + } +} + +impl Drop for Pipe { + fn drop(&mut self) { + if let Some(read_end) = self.read_end { + let _ = close(read_end); + } + if let Some(write_end) = self.write_end { + let _ = close(write_end); + } + } +} diff --git a/libafl/src/bolts/os/windows_exceptions.rs b/libafl/src/bolts/os/windows_exceptions.rs index 98bd392863..f50e8e0564 100644 --- a/libafl/src/bolts/os/windows_exceptions.rs +++ b/libafl/src/bolts/os/windows_exceptions.rs @@ -1,8 +1,10 @@ //! Exception handling for Windows -pub use crate::bolts::bindings::windows::win32::debug::EXCEPTION_POINTERS; - -use crate::{bolts::bindings::windows::win32::debug::SetUnhandledExceptionFilter, Error}; +pub use crate::bolts::bindings::windows::win32::debug::{ + SetUnhandledExceptionFilter, EXCEPTION_POINTERS, +}; +use crate::Error; +use std::os::raw::{c_long, c_void}; use alloc::vec::Vec; use core::{ @@ -13,7 +15,6 @@ use core::{ ptr::write_volatile, sync::atomic::{compiler_fence, Ordering}, }; -use std::os::raw::{c_long, c_void}; use num_enum::{IntoPrimitive, TryFromPrimitive}; diff --git a/libafl/src/utils.rs b/libafl/src/bolts/rands.rs similarity index 67% rename from libafl/src/utils.rs rename to libafl/src/bolts/rands.rs index 3b754aac99..4430b71d47 100644 --- a/libafl/src/utils.rs +++ b/libafl/src/bolts/rands.rs @@ -1,28 +1,11 @@ -//! Utility functions for AFL - -use core::{cell::RefCell, debug_assert, fmt::Debug, time}; +use core::{cell::RefCell, debug_assert, fmt::Debug}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use xxhash_rust::xxh3::xxh3_64_with_seed; -#[cfg(unix)] -use libc::pid_t; -#[cfg(all(unix, feature = "std"))] -use std::ffi::CString; #[cfg(feature = "std")] -use std::{ - env, - process::Command, - time::{SystemTime, UNIX_EPOCH}, -}; +use crate::bolts::current_nanos; -#[cfg(any(unix, feature = "std"))] -use crate::Error; - -/// Can be converted to a slice -pub trait AsSlice { - /// Convert to a slice - fn as_slice(&self) -> &[T]; -} +const HASH_CONST: u64 = 0xa5b35705; /// The standard rand implementation for `LibAFL`. /// It is usually the right choice, with very good speed and a reasonable randomness. @@ -147,39 +130,6 @@ impl_randomseed!(Lehmer64Rand); impl_randomseed!(RomuTrioRand); impl_randomseed!(RomuDuoJrRand); -const HASH_CONST: u64 = 0xa5b35705; - -/// Current time -#[cfg(feature = "std")] -#[must_use] -#[inline] -pub fn current_time() -> time::Duration { - SystemTime::now().duration_since(UNIX_EPOCH).unwrap() -} - -/// Current time (fixed fallback for no_std) -#[cfg(not(feature = "std"))] -#[inline] -pub fn current_time() -> time::Duration { - // We may not have a rt clock available. - // TODO: Make it somehow plugin-able - time::Duration::from_millis(1) -} - -/// Gets current nanoseconds since [`UNIX_EPOCH`] -#[must_use] -#[inline] -pub fn current_nanos() -> u64 { - current_time().as_nanos() as u64 -} - -/// Gets current milliseconds since [`UNIX_EPOCH`] -#[must_use] -#[inline] -pub fn current_milliseconds() -> u64 { - current_time().as_millis() as u64 -} - /// XXH3 Based, hopefully speedy, rnd implementation #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct Xoshiro256StarRand { @@ -404,142 +354,11 @@ impl XkcdRand { } } -/// Child Process Handle -#[cfg(unix)] -pub struct ChildHandle { - pid: pid_t, -} - -#[cfg(unix)] -impl ChildHandle { - /// Block until the child exited and the status code becomes available - #[must_use] - pub fn status(&self) -> i32 { - let mut status = -1; - unsafe { - libc::waitpid(self.pid, &mut status, 0); - } - status - } -} - -/// The `ForkResult` (result of a fork) -#[cfg(unix)] -pub enum ForkResult { - /// The fork finished, we are the parent process. - /// The child has the handle `ChildHandle`. - Parent(ChildHandle), - /// The fork finished, we are the child process. - Child, -} - -/// Unix has forks. -/// # Safety -/// A Normal fork. Runs on in two processes. Should be memory safe in general. -#[cfg(unix)] -pub unsafe fn fork() -> Result { - match libc::fork() { - pid if pid > 0 => Ok(ForkResult::Parent(ChildHandle { pid })), - pid if pid < 0 => { - // Getting errno from rust is hard, we'll just let the libc print to stderr for now. - // In any case, this should usually not happen. - #[cfg(feature = "std")] - { - let err_str = CString::new("Fork failed").unwrap(); - libc::perror(err_str.as_ptr()); - } - Err(Error::Unknown(format!("Fork failed ({})", pid))) - } - _ => Ok(ForkResult::Child), - } -} - -/// Executes the current process from the beginning, as subprocess. -/// use `start_self.status()?` to wait for the child -#[cfg(feature = "std")] -pub fn startable_self() -> Result { - let mut startable = Command::new(env::current_exe()?); - startable.current_dir(env::current_dir()?).args(env::args()); - Ok(startable) -} - -/// Allows one to walk the mappings in /proc/self/maps, caling a callback function for each -/// mapping. -/// If the callback returns true, we stop the walk. -#[cfg(all(feature = "std", any(target_os = "linux", target_os = "android")))] -pub fn walk_self_maps(visitor: &mut dyn FnMut(usize, usize, String, String) -> bool) { - use regex::Regex; - use std::{ - fs::File, - io::{BufRead, BufReader}, - }; - let re = Regex::new(r"^(?P[0-9a-f]{8,16})-(?P[0-9a-f]{8,16}) (?P[-rwxp]{4}) (?P[0-9a-f]{8}) [0-9a-f]+:[0-9a-f]+ [0-9]+\s+(?P.*)$") - .unwrap(); - - let mapsfile = File::open("/proc/self/maps").expect("Unable to open /proc/self/maps"); - - for line in BufReader::new(mapsfile).lines() { - let line = line.unwrap(); - if let Some(caps) = re.captures(&line) { - if visitor( - usize::from_str_radix(caps.name("start").unwrap().as_str(), 16).unwrap(), - usize::from_str_radix(caps.name("end").unwrap().as_str(), 16).unwrap(), - caps.name("perm").unwrap().as_str().to_string(), - caps.name("path").unwrap().as_str().to_string(), - ) { - break; - }; - } - } -} - -/// Get the start and end address, permissions and path of the mapping containing a particular address -#[cfg(all(feature = "std", any(target_os = "linux", target_os = "android")))] -pub fn find_mapping_for_address(address: usize) -> Result<(usize, usize, String, String), Error> { - let mut result = (0, 0, "".to_string(), "".to_string()); - walk_self_maps(&mut |start, end, permissions, path| { - if start <= address && address < end { - result = (start, end, permissions, path); - true - } else { - false - } - }); - - if result.0 == 0 { - Err(Error::Unknown( - "Couldn't find a mapping for this address".to_string(), - )) - } else { - Ok(result) - } -} - -/// Get the start and end address of the mapping containing with a particular path -#[cfg(all(feature = "std", any(target_os = "linux", target_os = "android")))] -#[must_use] -pub fn find_mapping_for_path(libpath: &str) -> (usize, usize) { - let mut libstart = 0; - let mut libend = 0; - walk_self_maps(&mut |start, end, _permissions, path| { - if libpath == path { - if libstart == 0 { - libstart = start; - } - - libend = end; - } - false - }); - - (libstart, libend) -} - #[cfg(test)] mod tests { //use xxhash_rust::xxh3::xxh3_64_with_seed; - use crate::utils::{ + use crate::bolts::rands::{ Rand, RomuDuoJrRand, RomuTrioRand, StdRand, XorShift64Rand, Xoshiro256StarRand, }; @@ -564,7 +383,7 @@ mod tests { #[cfg(feature = "std")] #[test] fn test_random_seed() { - use crate::utils::RandomSeed; + use crate::bolts::rands::RandomSeed; let mut rand_fixed = StdRand::with_seed(0); let mut rand = StdRand::new(); diff --git a/libafl/src/bolts/serdeany.rs b/libafl/src/bolts/serdeany.rs index 7c818d03b7..021ab27bac 100644 --- a/libafl/src/bolts/serdeany.rs +++ b/libafl/src/bolts/serdeany.rs @@ -256,6 +256,12 @@ macro_rules! create_serde_registry_for_trait { self.map.len() } + /// Returns `true` if this map is empty. + #[must_use] + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + /// Returns if the map contains the given type. #[must_use] #[inline] @@ -484,6 +490,12 @@ macro_rules! create_serde_registry_for_trait { self.map.len() } + /// Returns `true` if this map is empty. + #[must_use] + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + /// Returns if the element with a given type is contained in this map. #[must_use] #[inline] diff --git a/libafl/src/bolts/shmem.rs b/libafl/src/bolts/shmem.rs index f031f68ad7..1549acc2a0 100644 --- a/libafl/src/bolts/shmem.rs +++ b/libafl/src/bolts/shmem.rs @@ -17,11 +17,13 @@ pub type OsShMemProvider = Win32ShMemProvider; #[cfg(all(windows, feature = "std"))] pub type OsShMem = Win32ShMem; -#[cfg(target_os = "android")] +use crate::Error; + +#[cfg(all(target_os = "android", feature = "std"))] use crate::bolts::os::ashmem_server::ServedShMemProvider; -#[cfg(target_os = "android")] +#[cfg(all(target_os = "android", feature = "std"))] pub type StdShMemProvider = RcShMemProvider; -#[cfg(target_os = "android")] +#[cfg(all(target_os = "android", feature = "std"))] pub type StdShMem = RcShMem; /// The default [`ShMemProvider`] for this os. @@ -31,16 +33,17 @@ pub type StdShMemProvider = OsShMemProvider; #[cfg(all(feature = "std", not(target_os = "android")))] pub type StdShMem = OsShMem; -use core::fmt::Debug; use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use std::env; use alloc::{rc::Rc, string::ToString}; -use core::cell::RefCell; -use core::mem::ManuallyDrop; +use core::{cell::RefCell, fmt::Debug, mem::ManuallyDrop}; -use crate::Error; +#[cfg(all(unix, feature = "std"))] +use crate::bolts::os::pipes::Pipe; +#[cfg(all(unix, feature = "std"))] +use std::io::{Read, Write}; /// Description of a shared map. /// May be used to restore the map by id. @@ -191,10 +194,20 @@ pub trait ShMemProvider: Send + Clone + Default + Debug { )) } - /// 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) { + /// This method should be called before a fork or a thread creation event, allowing the [`ShMemProvider`] to + /// get ready for a potential reset of thread specific info, and for potential reconnects. + /// Make sure to call [`Self::post_fork()`] after threading! + fn pre_fork(&mut self) -> Result<(), Error> { // do nothing + Ok(()) + } + + /// This method should be called after a fork or after cloning/a thread creation event, allowing the [`ShMemProvider`] to + /// reset thread specific info, and potentially reconnect. + /// Make sure to call [`Self::pre_fork()`] before threading! + fn post_fork(&mut self, _is_child: bool) -> Result<(), Error> { + // do nothing + Ok(()) } /// Release the resources associated with the given [`ShMem`] @@ -243,12 +256,24 @@ impl Drop for RcShMem { /// that can use internal mutability. /// Useful if the `ShMemProvider` needs to keep local state. #[derive(Debug, Clone)] +#[cfg(all(unix, feature = "std"))] pub struct RcShMemProvider { + /// The wrapped [`ShMemProvider`]. internal: Rc>, + /// A pipe the child uses to communicate progress to the parent after fork. + /// This prevents a potential race condition when using the [`AshmemService`]. + #[cfg(unix)] + child_parent_pipe: Option, + #[cfg(unix)] + /// A pipe the parent uses to communicate progress to the child after fork. + /// This prevents a potential race condition when using the [`AshmemService`]. + parent_child_pipe: Option, } +#[cfg(all(unix, feature = "std"))] unsafe impl Send for RcShMemProvider {} +#[cfg(all(unix, feature = "std"))] impl ShMemProvider for RcShMemProvider where T: ShMemProvider + alloc::fmt::Debug, @@ -258,6 +283,8 @@ where fn new() -> Result { Ok(Self { internal: Rc::new(RefCell::new(T::new()?)), + child_parent_pipe: None, + parent_child_pipe: None, }) } @@ -286,11 +313,100 @@ where }) } - fn post_fork(&mut self) { - self.internal.borrow_mut().post_fork() + /// This method should be called before a fork or a thread creation event, allowing the [`ShMemProvider`] to + /// get ready for a potential reset of thread specific info, and for potential reconnects. + fn pre_fork(&mut self) -> Result<(), Error> { + // Set up the pipes to communicate progress over, later. + self.child_parent_pipe = Some(Pipe::new()?); + self.parent_child_pipe = Some(Pipe::new()?); + self.internal.borrow_mut().pre_fork() + } + + /// After fork, make sure everything gets set up correctly internally. + fn post_fork(&mut self, is_child: bool) -> Result<(), Error> { + if is_child { + self.await_parent_done()?; + let child_shmem = self.internal.borrow_mut().clone(); + self.internal = Rc::new(RefCell::new(child_shmem)); + } + self.internal.borrow_mut().post_fork(is_child)?; + if is_child { + self.set_child_done()?; + } else { + self.set_parent_done()?; + self.await_child_done()?; + } + + self.parent_child_pipe = None; + self.child_parent_pipe = None; + Ok(()) } } +#[cfg(all(unix, feature = "std"))] +impl RcShMemProvider +where + T: ShMemProvider, +{ + /// "set" the "latch" + /// (we abuse `pipes` as `semaphores`, as they don't need an additional shared mem region.) + fn pipe_set(pipe: &mut Option) -> Result<(), Error> { + match pipe { + Some(pipe) => { + let ok = [0u8; 4]; + pipe.write_all(&ok)?; + Ok(()) + } + None => Err(Error::IllegalState( + "Unexpected `None` Pipe in RcShMemProvider! Missing post_fork()?".to_string(), + )), + } + } + + /// "await" the "latch" + fn pipe_await(pipe: &mut Option) -> Result<(), Error> { + match pipe { + Some(pipe) => { + let ok = [0u8; 4]; + let mut ret = ok; + pipe.read_exact(&mut ret)?; + if ret == ok { + Ok(()) + } else { + Err(Error::Unknown(format!( + "Wrong result read from pipe! Expected 0, got {:?}", + ret + ))) + } + } + None => Err(Error::IllegalState( + "Unexpected `None` Pipe in RcShMemProvider! Missing post_fork()?".to_string(), + )), + } + } + + /// After fork, wait for the parent to write to our pipe :) + fn await_parent_done(&mut self) -> Result<(), Error> { + Self::pipe_await(&mut self.parent_child_pipe) + } + + /// After fork, inform the new child we're done + fn set_parent_done(&mut self) -> Result<(), Error> { + Self::pipe_set(&mut self.parent_child_pipe) + } + + /// After fork, wait for the child to write to our pipe :) + fn await_child_done(&mut self) -> Result<(), Error> { + Self::pipe_await(&mut self.child_parent_pipe) + } + + /// After fork, inform the new child we're done + fn set_child_done(&mut self) -> Result<(), Error> { + Self::pipe_set(&mut self.child_parent_pipe) + } +} + +#[cfg(all(unix, feature = "std"))] impl Default for RcShMemProvider where T: ShMemProvider + alloc::fmt::Debug, @@ -326,10 +442,10 @@ pub mod unix_shmem { use core::{ptr, slice}; use libc::{c_int, c_long, c_uchar, c_uint, c_ulong, c_ushort, c_void}; - use crate::Error; - - use super::super::{ShMem, ShMemId, ShMemProvider}; - + use crate::{ + bolts::shmem::{ShMem, ShMemId, ShMemProvider}, + Error, + }; #[cfg(unix)] #[derive(Copy, Clone)] #[repr(C)] @@ -491,9 +607,10 @@ pub mod unix_shmem { }; use std::ffi::CString; - use crate::Error; - - use super::super::{ShMem, ShMemId, ShMemProvider}; + use crate::{ + bolts::shmem::{ShMem, ShMemId, ShMemProvider}, + Error, + }; extern "C" { fn ioctl(fd: c_int, request: c_long, ...) -> c_int; @@ -702,14 +819,16 @@ pub mod unix_shmem { #[cfg(all(feature = "std", windows))] pub mod win32_shmem { - use super::{ShMem, ShMemId, ShMemProvider}; use crate::{ - bolts::bindings::{ - windows::win32::system_services::{ - CreateFileMappingA, MapViewOfFile, OpenFileMappingA, UnmapViewOfFile, + bolts::{ + bindings::{ + windows::win32::system_services::{ + CreateFileMappingA, MapViewOfFile, OpenFileMappingA, UnmapViewOfFile, + }, + windows::win32::system_services::{BOOL, HANDLE, PAGE_TYPE, PSTR}, + windows::win32::windows_programming::CloseHandle, }, - windows::win32::system_services::{BOOL, HANDLE, PAGE_TYPE, PSTR}, - windows::win32::windows_programming::CloseHandle, + shmem::{ShMem, ShMemId, ShMemProvider}, }, Error, }; @@ -752,7 +871,7 @@ pub mod win32_shmem { ))); } let map = MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, map_size) as *mut u8; - if map == ptr::null_mut() { + if map.is_null() { return Err(Error::Unknown(format!( "Cannot map shared memory {}", String::from_utf8_lossy(map_str_bytes) diff --git a/libafl/src/corpus/minimizer.rs b/libafl/src/corpus/minimizer.rs index 34322841bb..6cafbedd4b 100644 --- a/libafl/src/corpus/minimizer.rs +++ b/libafl/src/corpus/minimizer.rs @@ -2,12 +2,11 @@ // with testcases only from a subset of the total corpus. use crate::{ - bolts::serdeany::SerdeAny, + bolts::{rands::Rand, serdeany::SerdeAny, AsSlice}, corpus::{Corpus, CorpusScheduler, Testcase}, feedbacks::MapIndexesMetadata, inputs::{HasLen, Input}, state::{HasCorpus, HasMetadata, HasRand}, - utils::{AsSlice, Rand}, Error, }; diff --git a/libafl/src/corpus/mod.rs b/libafl/src/corpus/mod.rs index 1622e4f97b..15e6a8315d 100644 --- a/libafl/src/corpus/mod.rs +++ b/libafl/src/corpus/mod.rs @@ -25,9 +25,9 @@ use alloc::borrow::ToOwned; use core::{cell::RefCell, marker::PhantomData}; use crate::{ + bolts::rands::Rand, inputs::Input, state::{HasCorpus, HasRand}, - utils::Rand, Error, }; diff --git a/libafl/src/corpus/ondisk.rs b/libafl/src/corpus/ondisk.rs index b72c15be92..15d7cbc277 100644 --- a/libafl/src/corpus/ondisk.rs +++ b/libafl/src/corpus/ondisk.rs @@ -55,7 +55,7 @@ where testcase.set_filename(filename_str.into()); }; if self.meta_format.is_some() { - let filename = testcase.filename().as_ref().unwrap().to_owned() + ".metadata"; + let filename = testcase.filename().as_ref().unwrap().clone() + ".metadata"; let mut file = File::create(filename)?; let serialized = match self.meta_format.as_ref().unwrap() { diff --git a/libafl/src/corpus/queue.rs b/libafl/src/corpus/queue.rs index 6f997e1bf1..7c8b5114dc 100644 --- a/libafl/src/corpus/queue.rs +++ b/libafl/src/corpus/queue.rs @@ -80,10 +80,10 @@ mod tests { use std::{fs, path::PathBuf}; use crate::{ + bolts::rands::StdRand, corpus::{Corpus, CorpusScheduler, OnDiskCorpus, QueueCorpusScheduler, Testcase}, inputs::bytes::BytesInput, state::{HasCorpus, StdState}, - utils::StdRand, }; #[test] @@ -114,7 +114,7 @@ mod tests { .filename() .as_ref() .unwrap() - .to_owned(); + .clone(); assert_eq!(filename, "target/.test/fancy/path/fancyfile"); diff --git a/libafl/src/cpu.rs b/libafl/src/cpu.rs index 7575c2585a..a0773c3cf3 100644 --- a/libafl/src/cpu.rs +++ b/libafl/src/cpu.rs @@ -1,7 +1,7 @@ //! Architecture agnostic processor features #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] -use crate::utils::current_nanos; +use crate::bolts::current_nanos; // TODO: Add more architectures, using C code, see // https://github.com/google/benchmark/blob/master/src/cycleclock.h diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index ab1213de90..4523bfcf34 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -2,7 +2,7 @@ use alloc::{string::ToString, vec::Vec}; use core::{marker::PhantomData, time::Duration}; - +use core_affinity::CoreId; use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "std")] @@ -14,6 +14,9 @@ use crate::bolts::{ shmem::StdShMemProvider, }; +#[cfg(feature = "std")] +use std::net::{SocketAddr, ToSocketAddrs}; + use crate::{ bolts::{ llmp::{self, Flags, LlmpClientDescription, LlmpSender, Tag}, @@ -36,14 +39,17 @@ use crate::bolts::{ }; #[cfg(all(feature = "std", windows))] -use crate::utils::startable_self; +use crate::bolts::os::startable_self; #[cfg(all(feature = "std", unix))] -use crate::utils::{fork, ForkResult}; +use crate::bolts::os::{fork, ForkResult}; -#[cfg(all(feature = "std", target_os = "android"))] +#[cfg(all(target_os = "android", feature = "std"))] use crate::bolts::os::ashmem_server::AshmemService; +#[cfg(feature = "std")] +use typed_builder::TypedBuilder; + /// Forward this to the client const _LLMP_TAG_EVENT_TO_CLIENT: llmp::Tag = 0x2C11E471; /// Only handle this in the broker @@ -167,6 +173,19 @@ where matches!(self.llmp, llmp::LlmpConnection::IsBroker { broker: _ }) } + #[cfg(feature = "std")] + pub fn connect_b2b(&mut self, addr: A) -> Result<(), Error> + where + A: ToSocketAddrs, + { + match &mut self.llmp { + llmp::LlmpConnection::IsBroker { broker } => broker.connect_b2b(addr), + llmp::LlmpConnection::IsClient { client: _ } => Err(Error::IllegalState( + "Called broker loop in the client".into(), + )), + } + } + /// Run forever in the broker pub fn broker_loop(&mut self) -> Result<(), Error> { match &mut self.llmp { @@ -604,6 +623,17 @@ where } } +/// The kind of manager we're creating right now +#[derive(Debug, Clone, Copy)] +pub enum ManagerKind { + /// Any kind will do + Any, + /// A client, getting messages from a local broker. + Client { cpu_core: Option }, + /// A [`LlmpBroker`], forwarding the packets of local clients. + Broker, +} + /// Sets up a restarting fuzzer, using the [`StdShMemProvider`], and standard features. /// The restarting mgr is a combination of restarter and runner, that can be used on systems with and without `fork` support. /// The restarter will spawn a new process each time the child crashes or timeouts. @@ -621,148 +651,232 @@ pub fn setup_restarting_mgr_std( > where I: Input, + S: DeserializeOwned, + ST: Stats + Clone, OT: ObserversTuple, S: DeserializeOwned, - ST: Stats, { #[cfg(target_os = "android")] AshmemService::start().expect("Error starting Ashmem Service"); - setup_restarting_mgr::(StdShMemProvider::new()?, stats, broker_port) + RestartingMgr::builder() + .shmem_provider(StdShMemProvider::new()?) + .stats(stats) + .broker_port(broker_port) + .build() + .launch() } -/// A restarting state is a combination of restarter and runner, that can be used on systems with and without `fork` support. -/// The restarter will start a new process each time the child crashes or timeouts. +/// Provides a `builder` which can be used to build a [`RestartingMgr`], which is a combination of a +/// `restarter` and `runner`, that can be used on systems both with and without `fork` support. The +/// `restarter` will start a new process each time the child crashes or times out. #[cfg(feature = "std")] -#[allow( - clippy::unnecessary_operation, - clippy::type_complexity, - clippy::similar_names -)] // for { mgr = LlmpEventManager... } -pub fn setup_restarting_mgr( - mut shmem_provider: SP, - //mgr: &mut LlmpEventManager, - stats: ST, - broker_port: u16, -) -> Result<(Option, LlmpRestartingEventManager), Error> +#[allow(clippy::default_trait_access)] +#[derive(TypedBuilder, Debug)] +pub struct RestartingMgr where I: Input, - S: DeserializeOwned, OT: ObserversTuple, + S: DeserializeOwned, SP: ShMemProvider + 'static, ST: Stats, //CE: CustomEvent, { - let mut mgr = LlmpEventManager::::new_on_port( - shmem_provider.clone(), - stats, - broker_port, - )?; + /// The shared memory provider to use for the broker or client spawned by the restarting + /// manager. + shmem_provider: SP, + /// The stats to use + stats: ST, + /// The broker port to use + #[builder(default = 1337_u16)] + broker_port: u16, + /// The address to connect to + #[builder(default = None)] + remote_broker_addr: Option, + /// The type of manager to build + #[builder(default = ManagerKind::Any)] + kind: ManagerKind, + #[builder(setter(skip), default = PhantomData {})] + _phantom: PhantomData<(I, OT, S)>, +} - // We start ourself as child process to actually fuzz - let (sender, mut receiver, mut new_shmem_provider) = if std::env::var(_ENV_FUZZER_SENDER) +#[cfg(feature = "std")] +#[allow(clippy::type_complexity)] +#[allow(clippy::too_many_lines)] +impl RestartingMgr +where + I: Input, + OT: ObserversTuple, + S: DeserializeOwned, + SP: ShMemProvider, + ST: Stats + Clone, +{ + /// Launch the restarting manager + pub fn launch( + &mut self, + ) -> Result<(Option, LlmpRestartingEventManager), Error> { + let mut mgr = LlmpEventManager::::new_on_port( + self.shmem_provider.clone(), + self.stats.clone(), + self.broker_port, + )?; + + // We start ourself as child process to actually fuzz + let (sender, mut receiver, new_shmem_provider, core_id) = if std::env::var( + _ENV_FUZZER_SENDER, + ) .is_err() - { - if mgr.is_broker() { - // Yep, broker. Just loop here. - println!("Doing broker things. Run this tool again to start fuzzing in a client."); - mgr.broker_loop()?; - return Err(Error::ShuttingDown); - } + { + // We get here if we are on Unix, or we are a broker on Windows. + let core_id = if mgr.is_broker() { + match self.kind { + ManagerKind::Broker | ManagerKind::Any => { + // Yep, broker. Just loop here. + println!( + "Doing broker things. Run this tool again to start fuzzing in a client." + ); - // We are the fuzzer respawner in a llmp client - mgr.to_env(_ENV_FUZZER_BROKER_CLIENT_INITIAL); + if let Some(remote_broker_addr) = self.remote_broker_addr { + println!("B2b: Connecting to {:?}", &remote_broker_addr); + mgr.connect_b2b(remote_broker_addr)?; + }; - // First, create a channel from the fuzzer (sender) to us (receiver) to report its state for restarts. - let sender = { LlmpSender::new(shmem_provider.clone(), 0, false)? }; - - let map = { shmem_provider.clone_ref(&sender.out_maps.last().unwrap().shmem)? }; - let receiver = LlmpReceiver::on_existing_map(shmem_provider.clone(), map, None)?; - // Store the information to a map. - sender.to_env(_ENV_FUZZER_SENDER)?; - receiver.to_env(_ENV_FUZZER_RECEIVER)?; - - let mut ctr: u64 = 0; - // Client->parent loop - loop { - dbg!("Spawning next client (id {})", ctr); - - // On Unix, we fork (todo: measure if that is actually faster.) - #[cfg(unix)] - let child_status = match unsafe { fork() }? { - ForkResult::Parent(handle) => handle.status(), - ForkResult::Child => break (sender, receiver, shmem_provider), + mgr.broker_loop()?; + return Err(Error::ShuttingDown); + } + ManagerKind::Client { cpu_core: _ } => { + return Err(Error::IllegalState( + "Tried to start a client, but got a broker".to_string(), + )); + } + } + } else { + match self.kind { + ManagerKind::Broker => { + return Err(Error::IllegalState( + "Tried to start a broker, but got a client".to_string(), + )); + } + ManagerKind::Client { cpu_core } => cpu_core, + ManagerKind::Any => None, + } }; - // On windows, we spawn ourself again - #[cfg(windows)] - let child_status = startable_self()?.status()?; - - 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 - // and https://github.com/AFLplusplus/LibAFL/issues/32 for discussion. - panic!("Fuzzer-respawner: The fuzzed target crashed with an out of memory error! Fix your harness, or switch to another executor (for example, a forkserver)."); - } - - // 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! (Child exited with: {})", child_status); + if let Some(core_id) = core_id { + println!("Setting core affinity to {:?}", core_id); + core_affinity::set_for_current(core_id); } - ctr = ctr.wrapping_add(1); + // 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. + let sender = { LlmpSender::new(self.shmem_provider.clone(), 0, false)? }; + + let map = { + self.shmem_provider + .clone_ref(&sender.out_maps.last().unwrap().shmem)? + }; + let receiver = LlmpReceiver::on_existing_map(self.shmem_provider.clone(), map, None)?; + // Store the information to a map. + sender.to_env(_ENV_FUZZER_SENDER)?; + receiver.to_env(_ENV_FUZZER_RECEIVER)?; + + let mut ctr: u64 = 0; + // Client->parent loop + loop { + dbg!("Spawning next client (id {})", ctr); + + // On Unix, we fork + #[cfg(unix)] + let child_status = { + self.shmem_provider.pre_fork()?; + match unsafe { fork() }? { + ForkResult::Parent(handle) => { + self.shmem_provider.post_fork(false)?; + handle.status() + } + ForkResult::Child => { + self.shmem_provider.post_fork(true)?; + break (sender, receiver, self.shmem_provider.clone(), core_id); + } + } + }; + + // On windows, we spawn ourself again + #[cfg(windows)] + let child_status = startable_self()?.status()?; + + 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 + // and https://github.com/AFLplusplus/LibAFL/issues/32 for discussion. + panic!("Fuzzer-respawner: The fuzzed target crashed with an out of memory error! Fix your harness, or switch to another executor (for example, a forkserver)."); + } + + // 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! (Child exited with: {})", child_status); + } + + ctr = ctr.wrapping_add(1); + } + } else { + // We are the newly started fuzzing instance (i.e. on Windows), first, connect to our own restore map. + // We get here *only on Windows*, if we were started by a restarting fuzzer. + // A sender and a receiver for single communication + ( + LlmpSender::on_existing_from_env(self.shmem_provider.clone(), _ENV_FUZZER_SENDER)?, + LlmpReceiver::on_existing_from_env( + self.shmem_provider.clone(), + _ENV_FUZZER_RECEIVER, + )?, + self.shmem_provider.clone(), + None, + ) + }; + + if let Some(core_id) = core_id { + core_affinity::set_for_current(core_id); } - } else { - // We are the newly started fuzzing instance, first, connect to our own restore map. - // A sender and a receiver for single communication - // Clone so we get a new connection to the AshmemServer if we are using - // ServedShMemProvider - shmem_provider.post_fork(); - ( - LlmpSender::on_existing_from_env(shmem_provider.clone(), _ENV_FUZZER_SENDER)?, - LlmpReceiver::on_existing_from_env(shmem_provider.clone(), _ENV_FUZZER_RECEIVER)?, - shmem_provider, - ) - }; - new_shmem_provider.post_fork(); + println!("We're a client, let's fuzz :)"); - println!("We're a client, let's fuzz :)"); + for (var, val) in std::env::vars() { + println!("ENV VARS: {:?}: {:?}", var, val); + } - for (var, val) in std::env::vars() { - println!("ENV VARS: {:?}: {:?}", var, val); + // If we're restarting, deserialize the old state. + let (state, mut mgr) = match receiver.recv_buf()? { + None => { + println!("First run. Let's set it all up"); + // Mgr to send and receive msgs from/to all other fuzzer instances + let client_mgr = LlmpEventManager::::existing_client_from_env( + new_shmem_provider, + _ENV_FUZZER_BROKER_CLIENT_INITIAL, + )?; + + (None, LlmpRestartingEventManager::new(client_mgr, sender)) + } + // Restoring from a previous run, deserialize state and corpus. + Some((_sender, _tag, msg)) => { + println!("Subsequent run. Let's load all data from shmem (received {} bytes from previous instance)", msg.len()); + let (state, mgr): (S, LlmpEventManager) = + deserialize_state_mgr(new_shmem_provider, &msg)?; + + (Some(state), LlmpRestartingEventManager::new(mgr, sender)) + } + }; + // We reset the sender, the next sender and receiver (after crash) will reuse the page from the initial message. + unsafe { mgr.sender_mut().reset() }; + /* TODO: Not sure if this is needed + // We commit an empty NO_RESTART message to this buf, against infinite loops, + // in case something crashes in the fuzzer. + sender.send_buf(_LLMP_TAG_NO_RESTART, []); + */ + + Ok((state, mgr)) } - - // If we're restarting, deserialize the old state. - let (state, mut mgr) = match receiver.recv_buf()? { - None => { - println!("First run. Let's set it all up"); - // Mgr to send and receive msgs from/to all other fuzzer instances - let client_mgr = LlmpEventManager::::existing_client_from_env( - new_shmem_provider, - _ENV_FUZZER_BROKER_CLIENT_INITIAL, - )?; - - (None, LlmpRestartingEventManager::new(client_mgr, sender)) - } - // Restoring from a previous run, deserialize state and corpus. - Some((_sender, _tag, msg)) => { - println!("Subsequent run. Let's load all data from shmem (received {} bytes from previous instance)", msg.len()); - let (state, mgr): (S, LlmpEventManager) = - deserialize_state_mgr(new_shmem_provider, &msg)?; - - (Some(state), LlmpRestartingEventManager::new(mgr, sender)) - } - }; - // We reset the sender, the next sender and receiver (after crash) will reuse the page from the initial message. - unsafe { mgr.sender_mut().reset() }; - /* TODO: Not sure if this is needed - // We commit an empty NO_RESTART message to this buf, against infinite loops, - // in case something crashes in the fuzzer. - sender.send_buf(_LLMP_TAG_NO_RESTART, []); - */ - - Ok((state, mgr)) } diff --git a/libafl/src/events/mod.rs b/libafl/src/events/mod.rs index 97c94eddfc..5d54739940 100644 --- a/libafl/src/events/mod.rs +++ b/libafl/src/events/mod.rs @@ -256,11 +256,13 @@ mod tests { use tuple_list::tuple_list_type; use crate::{ - bolts::tuples::{tuple_list, Named}, + bolts::{ + current_time, + tuples::{tuple_list, Named}, + }, events::Event, inputs::bytes::BytesInput, observers::StdMapObserver, - utils::current_time, }; static mut MAP: [u32; 4] = [0; 4]; diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index 23a5de83d0..e138ca8cd8 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -1,16 +1,18 @@ //! The [`InProcessExecutor`] 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(any(unix, all(windows, feature = "std")))] use core::{ ffi::c_void, - marker::PhantomData, ptr::{self, write_volatile}, sync::atomic::{compiler_fence, Ordering}, }; #[cfg(unix)] use crate::bolts::os::unix_signals::setup_signal_handler; -#[cfg(windows)] +#[cfg(all(windows, feature = "std"))] use crate::bolts::os::windows_exceptions::setup_exception_handler; use crate::{ @@ -64,9 +66,9 @@ where #[inline] fn pre_exec( &mut self, - fuzzer: &mut Z, - state: &mut S, - event_mgr: &mut EM, + _fuzzer: &mut Z, + _state: &mut S, + _event_mgr: &mut EM, _input: &I, ) -> Result<(), Error> { #[cfg(unix)] @@ -82,12 +84,12 @@ where ); // 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); - write_volatile(&mut data.fuzzer_ptr, fuzzer as *mut _ as *mut c_void); + 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); + write_volatile(&mut data.fuzzer_ptr, _fuzzer as *mut _ as *mut c_void); compiler_fence(Ordering::SeqCst); } - #[cfg(windows)] + #[cfg(all(windows, feature = "std"))] unsafe { let data = &mut windows_exception_handler::GLOBAL_STATE; write_volatile( @@ -100,9 +102,9 @@ where ); // 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); - write_volatile(&mut data.fuzzer_ptr, fuzzer as *mut _ as *mut c_void); + 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); + write_volatile(&mut data.fuzzer_ptr, _fuzzer as *mut _ as *mut c_void); compiler_fence(Ordering::SeqCst); } Ok(()) @@ -124,7 +126,7 @@ where ); compiler_fence(Ordering::SeqCst); } - #[cfg(windows)] + #[cfg(all(windows, feature = "std"))] unsafe { write_volatile( &mut windows_exception_handler::GLOBAL_STATE.current_input_ptr, @@ -203,7 +205,7 @@ where setup_signal_handler(data)?; compiler_fence(Ordering::SeqCst); } - #[cfg(windows)] + #[cfg(all(windows, feature = "std"))] unsafe { let data = &mut windows_exception_handler::GLOBAL_STATE; write_volatile( @@ -229,7 +231,7 @@ where /// Retrieve the harness function. #[inline] pub fn harness(&self) -> &H { - self.harness_fn + &self.harness_fn } /// Retrieve the harness function for a mutable reference. @@ -356,7 +358,7 @@ mod unix_signal_handler { #[cfg(feature = "std")] println!("Timeout in fuzz run."); #[cfg(feature = "std")] - let _ = stdout().flush(); + let _res = stdout().flush(); let input = (data.current_input_ptr as *const I).as_ref().unwrap(); data.current_input_ptr = ptr::null(); @@ -471,7 +473,7 @@ mod unix_signal_handler { target_arch = "aarch64" ))] { - use crate::utils::find_mapping_for_address; + use crate::bolts::os::find_mapping_for_address; println!("{:━^100}", " CRASH "); println!( "Received signal {} at 0x{:016x}, fault address: 0x{:016x}", @@ -504,7 +506,7 @@ mod unix_signal_handler { } #[cfg(feature = "std")] - let _ = stdout().flush(); + let _res = 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. @@ -549,7 +551,7 @@ mod unix_signal_handler { } } -#[cfg(windows)] +#[cfg(all(windows, feature = "std"))] mod windows_exception_handler { use alloc::vec::Vec; use core::{ffi::c_void, ptr}; diff --git a/libafl/src/feedbacks/map.rs b/libafl/src/feedbacks/map.rs index e641d46119..bd3a0b2511 100644 --- a/libafl/src/feedbacks/map.rs +++ b/libafl/src/feedbacks/map.rs @@ -9,14 +9,13 @@ use num::Integer; use serde::{Deserialize, Serialize}; use crate::{ - bolts::tuples::Named, + bolts::{tuples::Named, AsSlice}, corpus::Testcase, executors::ExitKind, feedbacks::{Feedback, FeedbackState, FeedbackStatesTuple}, inputs::Input, observers::{MapObserver, ObserversTuple}, state::{HasFeedbackStates, HasMetadata}, - utils::AsSlice, Error, }; diff --git a/libafl/src/fuzzer.rs b/libafl/src/fuzzer.rs index cf9af86022..7385e403b1 100644 --- a/libafl/src/fuzzer.rs +++ b/libafl/src/fuzzer.rs @@ -1,6 +1,7 @@ //! The `Fuzzer` is the main struct for a fuzz campaign. use crate::{ + bolts::current_time, corpus::{Corpus, CorpusScheduler, Testcase}, events::{Event, EventManager}, executors::{ @@ -13,7 +14,6 @@ use crate::{ stages::StagesTuple, start_timer, state::{HasClientPerfStats, HasCorpus, HasExecutions, HasSolutions}, - utils::current_time, Error, }; @@ -353,7 +353,7 @@ where observers_buf, corpus_size: state.corpus().count(), client_config: "TODO".into(), - time: crate::utils::current_time(), + time: current_time(), executions: *state.executions(), }, )?; diff --git a/libafl/src/generators/mod.rs b/libafl/src/generators/mod.rs index a923a669b9..e883500338 100644 --- a/libafl/src/generators/mod.rs +++ b/libafl/src/generators/mod.rs @@ -4,8 +4,8 @@ use alloc::vec::Vec; use core::{cmp::min, marker::PhantomData}; use crate::{ + bolts::rands::Rand, inputs::{bytes::BytesInput, Input}, - utils::Rand, Error, }; diff --git a/libafl/src/inputs/bytes.rs b/libafl/src/inputs/bytes.rs index d2cc0b9aa5..2eb97a7e15 100644 --- a/libafl/src/inputs/bytes.rs +++ b/libafl/src/inputs/bytes.rs @@ -105,7 +105,7 @@ impl BytesInput { #[cfg(test)] mod tests { - use crate::utils::{Rand, StdRand}; + use crate::bolts::rands::{Rand, StdRand}; #[test] fn test_input() { diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index c74f9fea24..4e013f4bbc 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -36,7 +36,6 @@ pub mod observers; pub mod stages; pub mod state; pub mod stats; -pub mod utils; pub mod fuzzer; pub use fuzzer::*; @@ -123,6 +122,13 @@ impl From for Error { } } +#[cfg(unix)] +impl From for Error { + fn from(err: nix::Error) -> Self { + Self::Unknown(format!("{:?}", err)) + } +} + /// Create an AFL Error from io Error #[cfg(feature = "std")] impl From for Error { @@ -157,7 +163,7 @@ impl From for Error { #[cfg(test)] mod tests { use crate::{ - bolts::tuples::tuple_list, + bolts::{rands::StdRand, tuples::tuple_list}, corpus::{Corpus, InMemoryCorpus, RandCorpusScheduler, Testcase}, executors::{ExitKind, InProcessExecutor}, inputs::BytesInput, @@ -165,7 +171,6 @@ mod tests { stages::StdMutationalStage, state::{HasCorpus, StdState}, stats::SimpleStats, - utils::StdRand, Fuzzer, StdFuzzer, }; diff --git a/libafl/src/mutators/mutations.rs b/libafl/src/mutators/mutations.rs index 67d61c35da..63a7acaaf5 100644 --- a/libafl/src/mutators/mutations.rs +++ b/libafl/src/mutators/mutations.rs @@ -1,12 +1,11 @@ //! A wide variety of mutations used during fuzzing. use crate::{ - bolts::tuples::Named, + bolts::{rands::Rand, tuples::Named}, corpus::Corpus, inputs::{HasBytesVec, Input}, mutators::{MutationResult, Mutator}, state::{HasCorpus, HasMaxSize, HasRand}, - utils::Rand, Error, }; @@ -1832,13 +1831,14 @@ mod tests { use super::*; use crate::{ - bolts::tuples::tuple_list, - bolts::tuples::HasLen, + bolts::{ + rands::StdRand, + tuples::{tuple_list, HasLen}, + }, corpus::{Corpus, InMemoryCorpus}, inputs::BytesInput, mutators::MutatorsTuple, state::{HasMetadata, StdState}, - utils::StdRand, }; fn test_mutations() -> impl MutatorsTuple diff --git a/libafl/src/mutators/scheduled.rs b/libafl/src/mutators/scheduled.rs index 96737b34b8..fe9b5fc2f4 100644 --- a/libafl/src/mutators/scheduled.rs +++ b/libafl/src/mutators/scheduled.rs @@ -1,7 +1,6 @@ //! The `ScheduledMutator` schedules multiple mutations internally. -use alloc::string::String; -use alloc::vec::Vec; +use alloc::{string::String, vec::Vec}; use core::{ fmt::{self, Debug}, marker::PhantomData, @@ -9,12 +8,15 @@ use core::{ use serde::{Deserialize, Serialize}; use crate::{ - bolts::tuples::{tuple_list, NamedTuple}, + bolts::{ + rands::Rand, + tuples::{tuple_list, NamedTuple}, + AsSlice, + }, corpus::Corpus, inputs::{HasBytesVec, Input}, mutators::{MutationResult, Mutator, MutatorsTuple}, state::{HasCorpus, HasMaxSize, HasMetadata, HasRand}, - utils::{AsSlice, Rand}, Error, }; @@ -397,6 +399,7 @@ where #[cfg(test)] mod tests { use crate::{ + bolts::rands::{Rand, StdRand, XkcdRand}, corpus::{Corpus, InMemoryCorpus, Testcase}, inputs::{BytesInput, HasBytesVec}, mutators::{ @@ -405,7 +408,6 @@ mod tests { Mutator, }, state::StdState, - utils::{Rand, StdRand, XkcdRand}, }; #[test] diff --git a/libafl/src/mutators/token_mutations.rs b/libafl/src/mutators/token_mutations.rs index 73c56d6208..1b3903e547 100644 --- a/libafl/src/mutators/token_mutations.rs +++ b/libafl/src/mutators/token_mutations.rs @@ -3,6 +3,9 @@ use alloc::vec::Vec; use core::marker::PhantomData; use serde::{Deserialize, Serialize}; + +#[cfg(feature = "std")] +use crate::mutators::str_decode; #[cfg(feature = "std")] use std::{ fs::File, @@ -11,16 +14,12 @@ use std::{ }; use crate::{ + bolts::rands::Rand, inputs::{HasBytesVec, Input}, - mutators::{buffer_self_copy, mutations, MutationResult, Mutator, Named}, + mutators::{buffer_self_copy, mutations::buffer_copy, MutationResult, Mutator, Named}, state::{HasMaxSize, HasMetadata, HasRand}, - utils::Rand, Error, }; -use mutations::buffer_copy; - -#[cfg(feature = "std")] -use crate::mutators::str_decode; /// A state metadata holding a list of tokens #[derive(Serialize, Deserialize)] @@ -56,7 +55,7 @@ impl Tokens { if self.token_vec.contains(token) { return false; } - self.token_vec.push(token.to_vec()); + self.token_vec.push(token.clone()); true } @@ -303,7 +302,7 @@ mod tests { #[cfg(feature = "std")] #[test] fn test_read_tokens() { - let _ = fs::remove_file("test.tkns"); + let _res = fs::remove_file("test.tkns"); let data = r###" # comment token1@123="AAA" @@ -316,6 +315,6 @@ token2="B" #[cfg(feature = "std")] println!("Token file entries: {:?}", tokens.tokens()); assert_eq!(tokens.tokens().len(), 2); - let _ = fs::remove_file("test.tkns"); + let _res = fs::remove_file("test.tkns"); } } diff --git a/libafl/src/observers/mod.rs b/libafl/src/observers/mod.rs index d0b2d08406..d9d2392337 100644 --- a/libafl/src/observers/mod.rs +++ b/libafl/src/observers/mod.rs @@ -8,9 +8,11 @@ use core::time::Duration; use serde::{Deserialize, Serialize}; use crate::{ - bolts::tuples::{MatchName, Named}, + bolts::{ + current_time, + tuples::{MatchName, Named}, + }, executors::HasExecHooks, - utils::current_time, Error, }; diff --git a/libafl/src/stages/mutational.rs b/libafl/src/stages/mutational.rs index ec7d976010..0e7e090115 100644 --- a/libafl/src/stages/mutational.rs +++ b/libafl/src/stages/mutational.rs @@ -1,6 +1,7 @@ use core::marker::PhantomData; use crate::{ + bolts::rands::Rand, corpus::Corpus, fuzzer::Evaluator, inputs::Input, @@ -9,7 +10,6 @@ use crate::{ stages::Stage, start_timer, state::{HasClientPerfStats, HasCorpus, HasRand}, - utils::Rand, Error, }; diff --git a/libafl/src/stages/power.rs b/libafl/src/stages/power.rs index 3e333b52d7..61183ee462 100644 --- a/libafl/src/stages/power.rs +++ b/libafl/src/stages/power.rs @@ -1,6 +1,7 @@ use core::marker::PhantomData; use crate::{ + bolts::rands::Rand, corpus::{Corpus, CorpusScheduler}, events::EventManager, executors::{Executor, HasObservers}, @@ -9,7 +10,6 @@ use crate::{ observers::ObserversTuple, stages::{Stage, MutationalStage}, state::{Evaluator, HasCorpus, HasRand}, - utils::Rand, Error, }; diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index 75167e5e33..737136d4b7 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -9,7 +9,10 @@ use std::{ }; use crate::{ - bolts::serdeany::{SerdeAny, SerdeAnyMap}, + bolts::{ + rands::Rand, + serdeany::{SerdeAny, SerdeAnyMap}, + }, corpus::Corpus, events::{Event, EventManager, LogSeverity}, feedbacks::FeedbackStatesTuple, @@ -17,7 +20,6 @@ use crate::{ generators::Generator, inputs::Input, stats::ClientPerfStats, - utils::Rand, Error, }; diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index 5c84c043ae..6641a858df 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -10,7 +10,7 @@ use alloc::string::ToString; #[cfg(feature = "introspection")] use core::convert::TryInto; -use crate::utils::current_time; +use crate::bolts::current_time; const CLIENT_STATS_TIME_WINDOW_SECS: u64 = 5; // 5 seconds diff --git a/libafl_cc/Cargo.toml b/libafl_cc/Cargo.toml index 0e82b0fe1a..1f59a71246 100644 --- a/libafl_cc/Cargo.toml +++ b/libafl_cc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_cc" -version = "0.2.1" +version = "0.3.0" authors = ["Andrea Fioraldi "] description = "Commodity library to wrap compilers and link LibAFL" documentation = "https://docs.rs/libafl_cc" diff --git a/libafl_derive/Cargo.toml b/libafl_derive/Cargo.toml index d38de29c32..a3cc6e4c59 100644 --- a/libafl_derive/Cargo.toml +++ b/libafl_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_derive" -version = "0.2.1" +version = "0.3.0" authors = ["Andrea Fioraldi "] description = "Derive proc-macro crate for LibAFL" documentation = "https://docs.rs/libafl_derive" diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index c0bfc0b349..62a52cb410 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_frida" -version = "0.2.1" +version = "0.3.0" authors = ["s1341 "] description = "Frida backend library for LibAFL" documentation = "https://docs.rs/libafl_frida" @@ -14,17 +14,18 @@ edition = "2018" cc = { version = "1.0", features = ["parallel"] } [dependencies] -libafl = { path = "../libafl", version = "0.2.1", features = ["std", "libafl_derive"] } -libafl_targets = { path = "../libafl_targets", version = "0.2.1" } +libafl = { path = "../libafl", version = "0.3.0", features = ["std", "libafl_derive"] } +libafl_targets = { path = "../libafl_targets", version = "0.3.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", 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.1", git = "https://github.com/frida/frida-rust", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] } +frida-gum-sys = { version = "0.2.4", git = "https://github.com/frida/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"] } +core_affinity = { version = "0.5", git = "https://github.com/s1341/core_affinity_rs" } 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 ffb2614ba2..c362d7fff1 100644 --- a/libafl_frida/src/asan_rt.rs +++ b/libafl_frida/src/asan_rt.rs @@ -8,14 +8,17 @@ this helps finding mem errors early. use hashbrown::HashMap; use libafl::{ - bolts::{ownedref::OwnedPtr, tuples::Named}, + bolts::{ + os::{find_mapping_for_address, find_mapping_for_path, walk_self_maps}, + ownedref::OwnedPtr, + tuples::Named, + }, corpus::Testcase, executors::{CustomExitKind, ExitKind, HasExecHooks}, feedbacks::Feedback, inputs::{HasTargetBytes, Input}, observers::{Observer, ObserversTuple}, state::HasMetadata, - utils::{find_mapping_for_address, find_mapping_for_path, walk_self_maps}, Error, SerdeAny, }; use nix::{ @@ -308,7 +311,6 @@ impl Allocator { let mut offset_to_closest = i64::max_value(); let mut closest = None; for metadata in metadatas { - println!("{:#x}", metadata.address); let new_offset = if hint_base == metadata.address { (ptr as i64 - metadata.address as i64).abs() } else { @@ -867,8 +869,6 @@ impl AsanRuntime { }; assert!(unsafe { getrlimit64(3, &mut stack_rlimit as *mut rlimit64) } == 0); - println!("stack_rlimit: {:?}", stack_rlimit); - let max_start = end - stack_rlimit.rlim_cur as usize; if start != max_start { diff --git a/libafl_frida/src/helper.rs b/libafl_frida/src/helper.rs index 623adac45d..2d00caf97e 100644 --- a/libafl_frida/src/helper.rs +++ b/libafl_frida/src/helper.rs @@ -4,15 +4,18 @@ use std::hash::Hasher; use libafl::inputs::{HasTargetBytes, Input}; #[cfg(any(target_os = "linux", target_os = "android"))] -use libafl::utils::find_mapping_for_path; +use libafl::bolts::os::find_mapping_for_path; use libafl_targets::drcov::{DrCovBasicBlock, DrCovWriter}; #[cfg(target_arch = "aarch64")] -use capstone::arch::{ - arch::{self, BuildsCapstone}, - arm64::{Arm64Extender, Arm64OperandType, Arm64Shift}, - ArchOperand::Arm64Operand, +use capstone::{ + arch::{ + self, + arm64::{Arm64Extender, Arm64OperandType, Arm64Shift}, + ArchOperand::Arm64Operand, + BuildsCapstone, + }, Capstone, Insn, }; @@ -359,6 +362,7 @@ impl<'a> FridaInstrumentationHelper<'a> { shift: Arm64Shift, extender: Arm64Extender, ) { + let redzone_size = frida_gum_sys::GUM_RED_ZONE_SIZE as i32; let writer = output.writer(); let basereg = self.writer_register(basereg); diff --git a/libafl_frida/src/lib.rs b/libafl_frida/src/lib.rs index b3ae390ac8..d14d38bc88 100644 --- a/libafl_frida/src/lib.rs +++ b/libafl_frida/src/lib.rs @@ -5,8 +5,12 @@ It can report coverage and, on supported architecutres, even reports memory acce /// The frida address sanitizer runtime pub mod asan_rt; -/// The `LibAFL` firda helper +/// The `LibAFL` frida helper pub mod helper; +// for parsing asan cores +use libafl::bolts::os::parse_core_bind_arg; +// for getting current core_id +use core_affinity::get_core_ids; /// A representation of the various Frida options #[derive(Clone, Debug)] @@ -31,6 +35,7 @@ impl FridaOptions { #[must_use] pub fn parse_env_options() -> Self { let mut options = Self::default(); + let mut asan_cores = None; if let Ok(env_options) = std::env::var("LIBAFL_FRIDA_OPTIONS") { for option in env_options.trim().split(':') { @@ -40,7 +45,6 @@ impl FridaOptions { match name { "asan" => { options.enable_asan = value.parse().unwrap(); - #[cfg(not(target_arch = "aarch64"))] if options.enable_asan { panic!("ASAN is not currently supported on targets other than aarch64"); @@ -55,6 +59,9 @@ impl FridaOptions { "asan-allocation-backtraces" => { options.enable_asan_allocation_backtraces = value.parse().unwrap(); } + "asan-cores" => { + asan_cores = parse_core_bind_arg(value); + } "instrument-suppress-locations" => { options.instrument_suppress_locations = Some( value @@ -92,6 +99,19 @@ impl FridaOptions { panic!("unknown FRIDA option: '{}'", option); } } + } // end of for loop + + if options.enable_asan { + if let Some(asan_cores) = asan_cores { + let core_ids = get_core_ids().unwrap(); + assert_eq!( + core_ids.len(), + 1, + "Client should only be enabled on one core" + ); + let core_id = core_ids[0].id; + options.enable_asan = asan_cores.contains(&core_id); + } } } diff --git a/libafl_targets/Cargo.toml b/libafl_targets/Cargo.toml index aa4b6bdb6d..ed9e282800 100644 --- a/libafl_targets/Cargo.toml +++ b/libafl_targets/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_targets" -version = "0.2.1" +version = "0.3.0" authors = ["Andrea Fioraldi "] description = "Common code for target instrumentation that can be used combined with LibAFL" documentation = "https://docs.rs/libafl_targets"