diff --git a/fuzzers/baby_fuzzer_gramatron/src/main.rs b/fuzzers/baby_fuzzer_gramatron/src/main.rs index d0c5ea9532..577c89006f 100644 --- a/fuzzers/baby_fuzzer_gramatron/src/main.rs +++ b/fuzzers/baby_fuzzer_gramatron/src/main.rs @@ -109,7 +109,7 @@ pub fn main() { let automaton = read_automaton_from_file(PathBuf::from("auto.postcard")); let mut generator = GramatronGenerator::new(&automaton); - /// Use this code to profile the generator performance + // Use this code to profile the generator performance /* use libafl::generators::Generator; use std::collections::HashSet; diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 6dfdcec40f..8e0ee5b75f 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -37,10 +37,11 @@ harness = false #debug = true [features] -default = ["std", "anymap_debug", "derive", "llmp_compression", "rand_trait"] +default = ["std", "anymap_debug", "derive", "llmp_compression", "rand_trait", "fork"] std = ["serde_json", "hostname", "core_affinity", "nix", "serde/std", "bincode", "wait-timeout", "regex", "build_id", "uuid"] # 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. +fork = [] # uses the fork() syscall to spawn children, instead of launching a new command, if supported by the OS (has no effect on Windows, no_std). rand_trait = ["rand_core"] # If set, libafl's rand implementations will implement `rand::Rng` llmp_bind_public = [] # If set, llmp will bind to 0.0.0.0, allowing cross-device communication. Binds to localhost by default. llmp_compression = ["miniz_oxide"] # llmp compression using GZip @@ -84,7 +85,7 @@ libm = "0.2.1" wait-timeout = { version = "0.2", optional = true } # used by CommandExecutor to wait for child process -z3 = { version = "0.11", optional = true } # for concolic mutation +z3 = { version = "0.11", features = ["static-link-z3"], optional = true } # for concolic mutation [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/src/bolts/launcher.rs b/libafl/src/bolts/launcher.rs index d953fa33a7..91c1f9694b 100644 --- a/libafl/src/bolts/launcher.rs +++ b/libafl/src/bolts/launcher.rs @@ -1,6 +1,19 @@ -#[cfg(feature = "std")] -use serde::de::DeserializeOwned; +//! The [`Launcher`] launches multiple fuzzer instances in parallel. +//! Thanks to it, we won't need a `for` loop in a shell script... +//! +//! To use multiple [`Launcher`]`s` for individual configurations, +//! we can set `spawn_broker` to `false` on all but one. +//! +//! To connect multiple nodes together via TCP, we can use the `remote_broker_addr`. +//! (this requires the `llmp_bind_public` compile-time feature for `LibAFL`). +//! +//! On `Unix` systems, the [`Launcher`] will use `fork` if the `fork` feature is used for `LibAFL`. +//! Else, it will start subsequent nodes with the same commandline, and will set special `env` variables accordingly. +#[cfg(all(feature = "std", any(windows, not(feature = "fork"))))] +use crate::bolts::os::startable_self; +#[cfg(all(unix, feature = "std", feature = "fork"))] +use crate::bolts::os::{dup2, fork, ForkResult}; #[cfg(feature = "std")] use crate::{ bolts::shmem::ShMemProvider, @@ -11,46 +24,35 @@ use crate::{ 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 core::marker::PhantomData; +#[cfg(all(feature = "std", any(windows, not(feature = "fork"))))] +use core_affinity::CoreId; +#[cfg(feature = "std")] +use serde::de::DeserializeOwned; #[cfg(feature = "std")] use std::net::SocketAddr; -#[cfg(all(windows, feature = "std"))] +#[cfg(all(feature = "std", any(windows, not(feature = "fork"))))] use std::process::Stdio; - -#[cfg(all(windows, feature = "std"))] -use core_affinity::CoreId; - +#[cfg(all(unix, feature = "std", feature = "fork"))] +use std::{fs::File, os::unix::io::AsRawFd}; #[cfg(feature = "std")] use typed_builder::TypedBuilder; -/// The Launcher client callback type reference -#[cfg(feature = "std")] -pub type LauncherClientFnRef<'a, I, OT, S, SP> = &'a mut dyn FnMut( - Option, - LlmpRestartingEventManager, - usize, -) -> Result<(), Error>; - +/// The (internal) `env` that indicates we're running as client. const _AFL_LAUNCHER_CLIENT: &str = "AFL_LAUNCHER_CLIENT"; /// 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> +pub struct Launcher<'a, CF, I, OT, S, SP, ST> where - I: Input, + CF: FnOnce(Option, LlmpRestartingEventManager, usize) -> Result<(), Error>, + I: Input + 'a, ST: Stats, SP: ShMemProvider + 'static, - OT: ObserversTuple, - S: DeserializeOwned, + OT: ObserversTuple + 'a, + S: DeserializeOwned + 'a, { /// The ShmemProvider to use shmem_provider: SP, @@ -59,7 +61,8 @@ where /// The configuration configuration: EventConfig, /// The 'main' function to run for each client forked. This probably shouldn't return - run_client: LauncherClientFnRef<'a, I, OT, S, SP>, + #[builder(default, setter(strip_option))] + run_client: Option, /// The broker port to use (or to attach to, in case [`Self::spawn_broker`] is `false`) #[builder(default = 1337_u16)] broker_port: u16, @@ -78,11 +81,14 @@ where /// Then, clients launched by this [`Launcher`] can connect to the original `broker`. #[builder(default = true)] spawn_broker: bool, + #[builder(default = PhantomData)] + phantom_data: PhantomData<(&'a I, &'a OT, &'a S, &'a SP)>, } #[cfg(feature = "std")] -impl<'a, I, OT, S, SP, ST> Launcher<'a, I, OT, S, SP, ST> +impl<'a, CF, I, OT, S, SP, ST> Launcher<'a, CF, I, OT, S, SP, ST> where + CF: FnOnce(Option, LlmpRestartingEventManager, usize) -> Result<(), Error>, I: Input, OT: ObserversTuple + serde::de::DeserializeOwned, ST: Stats + Clone, @@ -90,9 +96,15 @@ where S: DeserializeOwned, { /// Launch the broker and the clients and fuzz - #[cfg(all(unix, feature = "std"))] + #[cfg(all(unix, feature = "std", feature = "fork"))] #[allow(clippy::similar_names)] pub fn launch(&mut self) -> Result<(), Error> { + if self.run_client.is_none() { + return Err(Error::IllegalArgument( + "No client callback provided".to_string(), + )); + } + let core_ids = core_affinity::get_core_ids().unwrap(); let num_cores = core_ids.len(); let mut handles = vec![]; @@ -134,7 +146,8 @@ where .build() .launch()?; - (self.run_client)(state, mgr, bind_to.id).expect("Client closure failed"); + (self.run_client.take().unwrap())(state, mgr, bind_to.id) + .expect("Client closure failed"); break; } }; @@ -179,13 +192,15 @@ where } /// Launch the broker and the clients and fuzz - #[cfg(all(windows, feature = "std"))] + #[cfg(all(feature = "std", any(windows, not(feature = "fork"))))] #[allow(unused_mut, clippy::match_wild_err_arm)] 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) => { + let core_id = core_conf.parse()?; + //todo: silence stdout and stderr for clients // the actual client. do the fuzzing @@ -193,15 +208,14 @@ where .shmem_provider(self.shmem_provider.clone()) .broker_port(self.broker_port) .kind(ManagerKind::Client { - cpu_core: Some(CoreId { - id: core_conf.parse()?, - }), + cpu_core: Some(CoreId { id: core_id }), }) .configuration(self.configuration) .build() .launch()?; - (self.run_client)(state, mgr, core_conf.parse()?)?; + (self.run_client.take().unwrap())(state, mgr, core_id) + .expect("Client closure failed"); unreachable!("Fuzzer client code should never get here!"); } @@ -228,11 +242,9 @@ where 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); - } + std::env::set_var(_AFL_LAUNCHER_CLIENT, id.to_string()); + let child = startable_self()?.stdout(stdio).spawn()?; + handles.push(child); } } diff --git a/libafl/src/bolts/mod.rs b/libafl/src/bolts/mod.rs index 196ed929bc..b60b6f3e04 100644 --- a/libafl/src/bolts/mod.rs +++ b/libafl/src/bolts/mod.rs @@ -6,6 +6,7 @@ pub mod compress; pub mod cpu; #[cfg(feature = "std")] pub mod fs; +#[cfg(feature = "std")] pub mod launcher; pub mod llmp; pub mod os; diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index e8845e3522..281618910e 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -42,10 +42,10 @@ use crate::bolts::{ llmp::{LLMP_FLAG_COMPRESSED, LLMP_FLAG_INITIALIZED}, }; -#[cfg(all(feature = "std", windows))] +#[cfg(all(feature = "std", any(windows, not(feature = "fork"))))] use crate::bolts::os::startable_self; -#[cfg(all(feature = "std", unix))] +#[cfg(all(feature = "std", feature = "fork", unix))] use crate::bolts::os::{fork, ForkResult}; #[cfg(feature = "std")] @@ -760,7 +760,7 @@ where broker.broker_loop() }; - // We get here if we are on Unix, or we are a broker on Windows. + // We get here if we are on Unix, or we are a broker on Windows (or without forks). let (mgr, core_id) = match self.kind { ManagerKind::Any => { let connection = @@ -831,7 +831,7 @@ where dbg!("Spawning next client (id {})", ctr); // On Unix, we fork - #[cfg(unix)] + #[cfg(all(unix, feature = "fork"))] let child_status = { self.shmem_provider.pre_fork()?; match unsafe { fork() }? { @@ -846,9 +846,11 @@ where } }; - // On windows, we spawn ourself again - #[cfg(windows)] + // On windows (or in any case without fork), we spawn ourself again + #[cfg(any(windows, not(feature = "fork")))] let child_status = startable_self()?.status()?; + #[cfg(all(unix, not(feature = "fork")))] + let child_status = child_status.code().unwrap_or_default(); compiler_fence(Ordering::SeqCst); diff --git a/libafl/src/events/simple.rs b/libafl/src/events/simple.rs index fe76642b08..2555f8700a 100644 --- a/libafl/src/events/simple.rs +++ b/libafl/src/events/simple.rs @@ -18,9 +18,9 @@ use core::{ #[cfg(feature = "std")] use serde::{de::DeserializeOwned, Serialize}; -#[cfg(all(feature = "std", windows))] +#[cfg(all(feature = "std", any(windows, not(feature = "fork"))))] use crate::bolts::os::startable_self; -#[cfg(all(feature = "std", unix))] +#[cfg(all(feature = "std", feature = "fork", unix))] use crate::bolts::os::{fork, ForkResult}; #[cfg(feature = "std")] use crate::{ @@ -343,7 +343,7 @@ where dbg!("Spawning next client (id {})", ctr); // On Unix, we fork - #[cfg(unix)] + #[cfg(all(unix, feature = "fork"))] let child_status = { shmem_provider.pre_fork()?; match unsafe { fork() }? { @@ -358,9 +358,11 @@ where } }; - // On windows, we spawn ourself again - #[cfg(windows)] + // On windows (or in any case without forks), we spawn ourself again + #[cfg(any(windows, not(feature = "fork")))] let child_status = startable_self()?.status()?; + #[cfg(all(unix, not(feature = "fork")))] + let child_status = child_status.code().unwrap_or_default(); compiler_fence(Ordering::SeqCst); diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index 32f81db846..30ca20c4ac 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -1,5 +1,7 @@ //! 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. +//! +//! Needs the `fork` feature flag. use core::{ffi::c_void, marker::PhantomData, ptr}; @@ -1008,18 +1010,17 @@ where mod tests { use core::{marker::PhantomData, ptr}; + #[cfg(all(feature = "std", feature = "fork", unix))] + use crate::{ + bolts::shmem::{ShMemProvider, StdShMemProvider}, + executors::InProcessForkExecutor, + }; use crate::{ bolts::tuples::tuple_list, executors::{Executor, ExitKind, InProcessExecutor}, inputs::NopInput, }; - #[cfg(all(feature = "std", unix))] - use crate::{ - bolts::shmem::{ShMemProvider, StdShMemProvider}, - executors::InProcessForkExecutor, - }; - #[test] fn test_inmem_exec() { let mut harness = |_buf: &NopInput| ExitKind::Ok; @@ -1038,7 +1039,7 @@ mod tests { } #[test] - #[cfg(all(feature = "std", unix))] + #[cfg(all(feature = "std", feature = "fork", unix))] fn test_inprocessfork_exec() { let provider = StdShMemProvider::new().unwrap(); diff --git a/libafl/src/executors/mod.rs b/libafl/src/executors/mod.rs index eecd236f72..88058eb387 100644 --- a/libafl/src/executors/mod.rs +++ b/libafl/src/executors/mod.rs @@ -2,7 +2,7 @@ pub mod inprocess; pub use inprocess::InProcessExecutor; -#[cfg(all(feature = "std", unix))] +#[cfg(all(feature = "std", feature = "fork", unix))] pub use inprocess::InProcessForkExecutor; /// Timeout executor. @@ -12,9 +12,9 @@ pub mod timeout; #[cfg(any(unix, feature = "std"))] pub use timeout::TimeoutExecutor; -#[cfg(all(feature = "std", unix))] +#[cfg(all(feature = "std", feature = "fork", unix))] pub mod forkserver; -#[cfg(all(feature = "std", unix))] +#[cfg(all(feature = "std", feature = "fork", unix))] pub use forkserver::{Forkserver, ForkserverExecutor, OutFile, TimeoutForkserverExecutor}; pub mod combined; diff --git a/libafl_frida/src/alloc.rs b/libafl_frida/src/alloc.rs index c05fd0db00..7380444753 100644 --- a/libafl_frida/src/alloc.rs +++ b/libafl_frida/src/alloc.rs @@ -63,7 +63,16 @@ impl Allocator { #[allow(clippy::cast_sign_loss)] let page_size = ret as usize; // probe to find a usable shadow bit: + #[cfg(any( + target_arch = "aarch64", + all(target_arch = "x86_64", target_os = "linux") + ))] let mut shadow_bit: usize = 0; + #[cfg(not(any( + target_arch = "aarch64", + all(target_arch = "x86_64", target_os = "linux") + )))] + let shadow_bit = 0; #[cfg(target_arch = "aarch64")] for try_shadow_bit in &[46usize, 36usize] {