Fork feature flag to disable fork in Launcher (#351)

* Fork feature flag to disable fork in launcher

* FnOnce instead of FnMut

* cleanup

* fix no_std

* doc/warning fix

* useless clippy fix
This commit is contained in:
Dominik Maier 2021-11-05 17:26:49 +01:00 committed by GitHub
parent 380196cf41
commit ea820a1694
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 94 additions and 66 deletions

View File

@ -109,7 +109,7 @@ pub fn main() {
let automaton = read_automaton_from_file(PathBuf::from("auto.postcard")); let automaton = read_automaton_from_file(PathBuf::from("auto.postcard"));
let mut generator = GramatronGenerator::new(&automaton); 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 libafl::generators::Generator;
use std::collections::HashSet; use std::collections::HashSet;

View File

@ -37,10 +37,11 @@ harness = false
#debug = true #debug = true
[features] [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 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. anymap_debug = ["serde_json"] # uses serde_json to Debug the anymap trait. Disable for smaller footprint.
derive = ["libafl_derive"] # provide derive(SerdeAny) macro. 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` 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_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 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 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] [target.'cfg(target_os = "android")'.dependencies]
backtrace = { version = "0.3", optional = true, default-features = false, features = ["std", "libbacktrace"] } # for llmp_debug backtrace = { version = "0.3", optional = true, default-features = false, features = ["std", "libbacktrace"] } # for llmp_debug

View File

@ -1,6 +1,19 @@
#[cfg(feature = "std")] //! The [`Launcher`] launches multiple fuzzer instances in parallel.
use serde::de::DeserializeOwned; //! 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")] #[cfg(feature = "std")]
use crate::{ use crate::{
bolts::shmem::ShMemProvider, bolts::shmem::ShMemProvider,
@ -11,46 +24,35 @@ use crate::{
Error, Error,
}; };
#[cfg(all(windows, feature = "std"))] #[cfg(feature = "std")]
use crate::bolts::os::startable_self; use core::marker::PhantomData;
#[cfg(all(feature = "std", any(windows, not(feature = "fork"))))]
#[cfg(all(unix, feature = "std"))] use core_affinity::CoreId;
use crate::bolts::os::{dup2, fork, ForkResult}; #[cfg(feature = "std")]
use serde::de::DeserializeOwned;
#[cfg(all(unix, feature = "std"))]
use std::{fs::File, os::unix::io::AsRawFd};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::net::SocketAddr; use std::net::SocketAddr;
#[cfg(all(windows, feature = "std"))] #[cfg(all(feature = "std", any(windows, not(feature = "fork"))))]
use std::process::Stdio; use std::process::Stdio;
#[cfg(all(unix, feature = "std", feature = "fork"))]
#[cfg(all(windows, feature = "std"))] use std::{fs::File, os::unix::io::AsRawFd};
use core_affinity::CoreId;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use typed_builder::TypedBuilder; use typed_builder::TypedBuilder;
/// The Launcher client callback type reference /// The (internal) `env` that indicates we're running as client.
#[cfg(feature = "std")]
pub type LauncherClientFnRef<'a, I, OT, S, SP> = &'a mut dyn FnMut(
Option<S>,
LlmpRestartingEventManager<I, OT, S, SP>,
usize,
) -> Result<(), Error>;
const _AFL_LAUNCHER_CLIENT: &str = "AFL_LAUNCHER_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 /// Provides a Launcher, which can be used to launch a fuzzing run on a specified list of cores
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[derive(TypedBuilder)] #[derive(TypedBuilder)]
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub struct Launcher<'a, I, OT, S, SP, ST> pub struct Launcher<'a, CF, I, OT, S, SP, ST>
where where
I: Input, CF: FnOnce(Option<S>, LlmpRestartingEventManager<I, OT, S, SP>, usize) -> Result<(), Error>,
I: Input + 'a,
ST: Stats, ST: Stats,
SP: ShMemProvider + 'static, SP: ShMemProvider + 'static,
OT: ObserversTuple<I, S>, OT: ObserversTuple<I, S> + 'a,
S: DeserializeOwned, S: DeserializeOwned + 'a,
{ {
/// The ShmemProvider to use /// The ShmemProvider to use
shmem_provider: SP, shmem_provider: SP,
@ -59,7 +61,8 @@ where
/// The configuration /// The configuration
configuration: EventConfig, configuration: EventConfig,
/// The 'main' function to run for each client forked. This probably shouldn't return /// 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<CF>,
/// The broker port to use (or to attach to, in case [`Self::spawn_broker`] is `false`) /// The broker port to use (or to attach to, in case [`Self::spawn_broker`] is `false`)
#[builder(default = 1337_u16)] #[builder(default = 1337_u16)]
broker_port: u16, broker_port: u16,
@ -78,11 +81,14 @@ where
/// Then, clients launched by this [`Launcher`] can connect to the original `broker`. /// Then, clients launched by this [`Launcher`] can connect to the original `broker`.
#[builder(default = true)] #[builder(default = true)]
spawn_broker: bool, spawn_broker: bool,
#[builder(default = PhantomData)]
phantom_data: PhantomData<(&'a I, &'a OT, &'a S, &'a SP)>,
} }
#[cfg(feature = "std")] #[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 where
CF: FnOnce(Option<S>, LlmpRestartingEventManager<I, OT, S, SP>, usize) -> Result<(), Error>,
I: Input, I: Input,
OT: ObserversTuple<I, S> + serde::de::DeserializeOwned, OT: ObserversTuple<I, S> + serde::de::DeserializeOwned,
ST: Stats + Clone, ST: Stats + Clone,
@ -90,9 +96,15 @@ where
S: DeserializeOwned, S: DeserializeOwned,
{ {
/// Launch the broker and the clients and fuzz /// Launch the broker and the clients and fuzz
#[cfg(all(unix, feature = "std"))] #[cfg(all(unix, feature = "std", feature = "fork"))]
#[allow(clippy::similar_names)] #[allow(clippy::similar_names)]
pub fn launch(&mut self) -> Result<(), Error> { 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 core_ids = core_affinity::get_core_ids().unwrap();
let num_cores = core_ids.len(); let num_cores = core_ids.len();
let mut handles = vec![]; let mut handles = vec![];
@ -134,7 +146,8 @@ where
.build() .build()
.launch()?; .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; break;
} }
}; };
@ -179,13 +192,15 @@ where
} }
/// Launch the broker and the clients and fuzz /// 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)] #[allow(unused_mut, clippy::match_wild_err_arm)]
pub fn launch(&mut self) -> Result<(), Error> { pub fn launch(&mut self) -> Result<(), Error> {
let is_client = std::env::var(_AFL_LAUNCHER_CLIENT); let is_client = std::env::var(_AFL_LAUNCHER_CLIENT);
let mut handles = match is_client { let mut handles = match is_client {
Ok(core_conf) => { Ok(core_conf) => {
let core_id = core_conf.parse()?;
//todo: silence stdout and stderr for clients //todo: silence stdout and stderr for clients
// the actual client. do the fuzzing // the actual client. do the fuzzing
@ -193,15 +208,14 @@ where
.shmem_provider(self.shmem_provider.clone()) .shmem_provider(self.shmem_provider.clone())
.broker_port(self.broker_port) .broker_port(self.broker_port)
.kind(ManagerKind::Client { .kind(ManagerKind::Client {
cpu_core: Some(CoreId { cpu_core: Some(CoreId { id: core_id }),
id: core_conf.parse()?,
}),
}) })
.configuration(self.configuration) .configuration(self.configuration)
.build() .build()
.launch()?; .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!"); unreachable!("Fuzzer client code should never get here!");
} }
@ -228,11 +242,9 @@ where
Stdio::null() Stdio::null()
}; };
if self.cores.iter().any(|&x| x == id) { std::env::set_var(_AFL_LAUNCHER_CLIENT, id.to_string());
std::env::set_var(_AFL_LAUNCHER_CLIENT, id.to_string()); let child = startable_self()?.stdout(stdio).spawn()?;
let child = startable_self()?.stdout(stdio).spawn()?; handles.push(child);
handles.push(child);
}
} }
} }

View File

@ -6,6 +6,7 @@ pub mod compress;
pub mod cpu; pub mod cpu;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod fs; pub mod fs;
#[cfg(feature = "std")]
pub mod launcher; pub mod launcher;
pub mod llmp; pub mod llmp;
pub mod os; pub mod os;

View File

@ -42,10 +42,10 @@ use crate::bolts::{
llmp::{LLMP_FLAG_COMPRESSED, LLMP_FLAG_INITIALIZED}, 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; use crate::bolts::os::startable_self;
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", feature = "fork", unix))]
use crate::bolts::os::{fork, ForkResult}; use crate::bolts::os::{fork, ForkResult};
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -760,7 +760,7 @@ where
broker.broker_loop() 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 { let (mgr, core_id) = match self.kind {
ManagerKind::Any => { ManagerKind::Any => {
let connection = let connection =
@ -831,7 +831,7 @@ where
dbg!("Spawning next client (id {})", ctr); dbg!("Spawning next client (id {})", ctr);
// On Unix, we fork // On Unix, we fork
#[cfg(unix)] #[cfg(all(unix, feature = "fork"))]
let child_status = { let child_status = {
self.shmem_provider.pre_fork()?; self.shmem_provider.pre_fork()?;
match unsafe { fork() }? { match unsafe { fork() }? {
@ -846,9 +846,11 @@ where
} }
}; };
// On windows, we spawn ourself again // On windows (or in any case without fork), we spawn ourself again
#[cfg(windows)] #[cfg(any(windows, not(feature = "fork")))]
let child_status = startable_self()?.status()?; 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); compiler_fence(Ordering::SeqCst);

View File

@ -18,9 +18,9 @@ use core::{
#[cfg(feature = "std")] #[cfg(feature = "std")]
use serde::{de::DeserializeOwned, Serialize}; 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; use crate::bolts::os::startable_self;
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", feature = "fork", unix))]
use crate::bolts::os::{fork, ForkResult}; use crate::bolts::os::{fork, ForkResult};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use crate::{ use crate::{
@ -343,7 +343,7 @@ where
dbg!("Spawning next client (id {})", ctr); dbg!("Spawning next client (id {})", ctr);
// On Unix, we fork // On Unix, we fork
#[cfg(unix)] #[cfg(all(unix, feature = "fork"))]
let child_status = { let child_status = {
shmem_provider.pre_fork()?; shmem_provider.pre_fork()?;
match unsafe { fork() }? { match unsafe { fork() }? {
@ -358,9 +358,11 @@ where
} }
}; };
// On windows, we spawn ourself again // On windows (or in any case without forks), we spawn ourself again
#[cfg(windows)] #[cfg(any(windows, not(feature = "fork")))]
let child_status = startable_self()?.status()?; 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); compiler_fence(Ordering::SeqCst);

View File

@ -1,5 +1,7 @@
//! The [`InProcessExecutor`] is a libfuzzer-like executor, that will simply call a function. //! 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. //! 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}; use core::{ffi::c_void, marker::PhantomData, ptr};
@ -1008,18 +1010,17 @@ where
mod tests { mod tests {
use core::{marker::PhantomData, ptr}; use core::{marker::PhantomData, ptr};
#[cfg(all(feature = "std", feature = "fork", unix))]
use crate::{
bolts::shmem::{ShMemProvider, StdShMemProvider},
executors::InProcessForkExecutor,
};
use crate::{ use crate::{
bolts::tuples::tuple_list, bolts::tuples::tuple_list,
executors::{Executor, ExitKind, InProcessExecutor}, executors::{Executor, ExitKind, InProcessExecutor},
inputs::NopInput, inputs::NopInput,
}; };
#[cfg(all(feature = "std", unix))]
use crate::{
bolts::shmem::{ShMemProvider, StdShMemProvider},
executors::InProcessForkExecutor,
};
#[test] #[test]
fn test_inmem_exec() { fn test_inmem_exec() {
let mut harness = |_buf: &NopInput| ExitKind::Ok; let mut harness = |_buf: &NopInput| ExitKind::Ok;
@ -1038,7 +1039,7 @@ mod tests {
} }
#[test] #[test]
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", feature = "fork", unix))]
fn test_inprocessfork_exec() { fn test_inprocessfork_exec() {
let provider = StdShMemProvider::new().unwrap(); let provider = StdShMemProvider::new().unwrap();

View File

@ -2,7 +2,7 @@
pub mod inprocess; pub mod inprocess;
pub use inprocess::InProcessExecutor; pub use inprocess::InProcessExecutor;
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", feature = "fork", unix))]
pub use inprocess::InProcessForkExecutor; pub use inprocess::InProcessForkExecutor;
/// Timeout executor. /// Timeout executor.
@ -12,9 +12,9 @@ pub mod timeout;
#[cfg(any(unix, feature = "std"))] #[cfg(any(unix, feature = "std"))]
pub use timeout::TimeoutExecutor; pub use timeout::TimeoutExecutor;
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", feature = "fork", unix))]
pub mod forkserver; pub mod forkserver;
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", feature = "fork", unix))]
pub use forkserver::{Forkserver, ForkserverExecutor, OutFile, TimeoutForkserverExecutor}; pub use forkserver::{Forkserver, ForkserverExecutor, OutFile, TimeoutForkserverExecutor};
pub mod combined; pub mod combined;

View File

@ -63,7 +63,16 @@ impl Allocator {
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss)]
let page_size = ret as usize; let page_size = ret as usize;
// probe to find a usable shadow bit: // 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; 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")] #[cfg(target_arch = "aarch64")]
for try_shadow_bit in &[46usize, 36usize] { for try_shadow_bit in &[46usize, 36usize] {