Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6206451
Fork as runtime flag
domenukk Nov 20, 2025
5af0dfe
fun
domenukk Nov 20, 2025
b63c164
more
domenukk Nov 20, 2025
612452f
more
domenukk Nov 20, 2025
7bf7a4c
Merge branch 'main' into fork_on_demand
domenukk Dec 3, 2025
aa7daed
Merge branch 'main' into fork_on_demand
domenukk Dec 5, 2025
93a1dc2
Added fork/nofork tests
domenukk Dec 5, 2025
b1a4447
Fixes for things (like NaN in introspection..)
domenukk Dec 5, 2025
81509e4
opps
domenukk Dec 5, 2025
fca0d0c
fix win
domenukk Dec 5, 2025
39388fd
fmt
domenukk Dec 5, 2025
b9daa50
Fix, fmt
domenukk Dec 5, 2025
f9cd579
fmt
domenukk Dec 5, 2025
818ba90
more unfix
domenukk Dec 5, 2025
b6b66d2
Merge branch 'main' into fork_on_demand
domenukk Dec 5, 2025
8fb5ece
fix?
domenukk Dec 5, 2025
c3c09c7
fix?
domenukk Dec 6, 2025
8f83d96
Merge branch 'main' into fork_on_demand
domenukk Dec 6, 2025
4226de7
bring back merge foo
domenukk Dec 6, 2025
32a9c47
merge more
domenukk Dec 6, 2025
8611491
more
domenukk Dec 6, 2025
f5c26d1
fmt
domenukk Dec 6, 2025
0e69dbb
More fix?
domenukk Dec 6, 2025
df656c3
tcp
domenukk Dec 6, 2025
d249b06
more_fix
domenukk Dec 6, 2025
1e1df8b
more commit
domenukk Dec 6, 2025
e8da37e
more tcp
domenukk Dec 6, 2025
bd921a0
fix
domenukk Dec 7, 2025
b1de5c4
fix fixes
domenukk Dec 7, 2025
05950aa
fix?
domenukk Dec 7, 2025
5ca858a
fix
domenukk Dec 7, 2025
39b056c
mas
domenukk Dec 7, 2025
d98ff49
more
domenukk Dec 7, 2025
4414495
centralized test
domenukk Dec 7, 2025
a3923a8
add stuff
domenukk Dec 7, 2025
23a8d23
implement builder manually
domenukk Dec 8, 2025
ff56255
More cleanup
domenukk Dec 8, 2025
e137b82
clp
domenukk Dec 8, 2025
027637f
more commit
domenukk Dec 8, 2025
43b1e1f
more test
domenukk Dec 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,453 changes: 989 additions & 464 deletions crates/libafl/src/events/launcher.rs

Large diffs are not rendered by default.

85 changes: 46 additions & 39 deletions crates/libafl/src/events/llmp/restarting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ use std::net::TcpStream;

#[cfg(feature = "std")]
use libafl_bolts::llmp::{TcpRequest, TcpResponse, recv_tcp_msg, send_tcp_msg};
#[cfg(any(windows, not(feature = "fork")))]
use libafl_bolts::os::startable_self;
#[cfg(all(unix, not(miri)))]
use libafl_bolts::os::unix_signals::setup_signal_handler;
#[cfg(all(feature = "fork", unix))]
#[cfg(unix)]
use libafl_bolts::os::{ForkResult, fork};
#[cfg(feature = "llmp_compression")]
use libafl_bolts::{
Expand All @@ -35,7 +33,7 @@ use libafl_bolts::{
llmp::{
Broker, LLMP_FLAG_FROM_MM, LlmpBroker, LlmpClient, LlmpClientDescription, LlmpConnection,
},
os::CTRL_C_EXIT,
os::{CTRL_C_EXIT, startable_self},
shmem::{ShMem, ShMemProvider, StdShMem, StdShMemProvider},
staterestore::StateRestorer,
tuples::tuple_list,
Expand Down Expand Up @@ -695,6 +693,10 @@ pub struct RestartingMgr<EMH, I, MT, S, SP> {
serialize_state: LlmpShouldSaveState,
/// The hooks passed to event manager:
hooks: EMH,
/// If this manager should use `fork` to spawn a new instance. Otherwise it will try to re-launch the current process with exactly the same parameters.
#[cfg(unix)]
#[builder(default = true)]
fork: bool,
#[builder(setter(skip), default = PhantomData)]
phantom_data: PhantomData<(EMH, I, S)>,
}
Expand Down Expand Up @@ -833,49 +835,54 @@ where
loop {
log::info!("Spawning next client (id {ctr})");

// On Unix, we fork (when fork feature is enabled)
#[cfg(all(unix, feature = "fork"))]
let child_status = {
self.shmem_provider.pre_fork()?;
match unsafe { fork() }? {
ForkResult::Parent(handle) => {
unsafe {
libc::signal(libc::SIGINT, libc::SIG_IGN);
#[cfg(unix)]
let use_fork = self.fork;
#[cfg(not(unix))]
let use_fork = false;

if use_fork {
#[cfg(unix)]
{
self.shmem_provider.pre_fork()?;
match unsafe { fork() }? {
ForkResult::Parent(handle) => {
unsafe {
libc::signal(libc::SIGINT, libc::SIG_IGN);
}
self.shmem_provider.post_fork(false)?;
handle.status()
}
ForkResult::Child => {
log::debug!(
"{} has been forked into {}",
std::os::unix::process::parent_id(),
std::process::id()
);
self.shmem_provider.post_fork(true)?;
break (staterestorer, self.shmem_provider.clone(), core_id);
}
}
self.shmem_provider.post_fork(false)?;
handle.status()
}
ForkResult::Child => {
log::debug!(
"{} has been forked into {}",
std::os::unix::process::parent_id(),
std::process::id()
#[cfg(not(unix))]
unreachable!("Forking not supported")
} else {
// spawn
unsafe {
#[cfg(windows)]
libafl_bolts::os::windows_exceptions::signal(
libafl_bolts::os::windows_exceptions::SIGINT,
libafl_bolts::os::windows_exceptions::sig_ign(),
);
self.shmem_provider.post_fork(true)?;
break (staterestorer, self.shmem_provider.clone(), core_id);

#[cfg(unix)]
libc::signal(libc::SIGINT, libc::SIG_IGN);
}
let status = startable_self()?.status()?;
status.code().unwrap_or_default()
}
};

// If this guy wants to fork, then ignore sigint
#[cfg(any(windows, not(feature = "fork")))]
unsafe {
#[cfg(windows)]
libafl_bolts::os::windows_exceptions::signal(
libafl_bolts::os::windows_exceptions::SIGINT,
libafl_bolts::os::windows_exceptions::sig_ign(),
);

#[cfg(unix)]
libc::signal(libc::SIGINT, libc::SIG_IGN);
}

// 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(any(windows, not(feature = "fork")))]
let child_status = child_status.code().unwrap_or_default();

compiler_fence(Ordering::SeqCst); // really useful?

if child_status == CTRL_C_EXIT || staterestorer.wants_to_exit() {
Expand Down
48 changes: 25 additions & 23 deletions crates/libafl/src/events/simple.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! A very simple event manager, that just supports log outputs, but no multiprocessing

use alloc::vec::Vec;
#[cfg(feature = "std")]
use core::sync::atomic::{Ordering, compiler_fence};
Expand All @@ -7,9 +8,9 @@ use core::{fmt::Debug, marker::PhantomData, time::Duration};
#[cfg(feature = "std")]
use hashbrown::HashMap;
use libafl_bolts::ClientId;
#[cfg(all(feature = "std", any(windows, not(feature = "fork"))))]
#[cfg(feature = "std")]
use libafl_bolts::os::startable_self;
#[cfg(all(feature = "std", feature = "fork", unix))]
#[cfg(all(feature = "std", unix))]
use libafl_bolts::os::{ForkResult, fork};
#[cfg(all(unix, feature = "std", not(miri)))]
use libafl_bolts::os::{SIGNAL_RECURSION_EXIT, unix_signals::setup_signal_handler};
Expand Down Expand Up @@ -188,6 +189,7 @@ impl<I, MT, S> SimpleEventManager<I, MT, S>
where
I: Debug,
MT: Monitor,
S: Stoppable,
{
/// Creates a new [`SimpleEventManager`].
pub fn new(monitor: MT) -> Self {
Expand Down Expand Up @@ -413,7 +415,18 @@ where
/// Launch the simple restarting manager.
/// This `EventManager` is simple and single threaded,
/// but can still used shared maps to recover from crashes and timeouts.
pub fn launch(monitor: MT, shmem_provider: &mut SP) -> Result<(Option<S>, Self), Error>
///
/// # Arguments
///
/// * `monitor` - The monitor to use for the event manager.
/// * `shmem_provider` - The shared memory provider to use for the event manager.
/// * `use_fork` - Whether to use fork to spawn child processes (on Unix only)
/// or to spawn the binary again with the same parameters.
pub fn launch(
monitor: MT,
shmem_provider: &mut SP,
_use_fork: bool,
) -> Result<(Option<S>, Self), Error>
where
S: DeserializeOwned + Serialize + HasSolutions<I>,
MT: Debug,
Expand Down Expand Up @@ -442,8 +455,8 @@ where
log::info!("Spawning next client (id {ctr})");

// On Unix, we fork
#[cfg(all(unix, feature = "fork"))]
let child_status = {
#[cfg(unix)]
let child_status = if _use_fork {
shmem_provider.pre_fork()?;
match unsafe { fork() }? {
ForkResult::Parent(handle) => {
Expand All @@ -458,26 +471,15 @@ where
break staterestorer;
}
}
} else {
unsafe {
libc::signal(libc::SIGINT, libc::SIG_IGN);
}
startable_self()?.status()?.code().unwrap_or_default()
};

// If this guy wants to fork, then ignore sigit
#[cfg(any(windows, not(feature = "fork")))]
unsafe {
#[cfg(windows)]
libafl_bolts::os::windows_exceptions::signal(
libafl_bolts::os::windows_exceptions::SIGINT,
libafl_bolts::os::windows_exceptions::sig_ign(),
);

#[cfg(unix)]
libc::signal(libc::SIGINT, libc::SIG_IGN);
}

// 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(any(windows, not(feature = "fork")))]
let child_status = child_status.code().unwrap_or_default();
#[cfg(not(unix))]
let child_status = startable_self()?.status()?.code().unwrap_or_default();

compiler_fence(Ordering::SeqCst);

Expand Down
51 changes: 29 additions & 22 deletions crates/libafl/src/events/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ use std::{

#[cfg(feature = "tcp_compression")]
use libafl_bolts::compress::GzipCompressor;
#[cfg(any(windows, not(feature = "fork")))]
#[cfg(any(windows, unix))]
use libafl_bolts::os::startable_self;
#[cfg(all(unix, not(miri)))]
use libafl_bolts::os::unix_signals::setup_signal_handler;
#[cfg(all(feature = "fork", unix))]
use libafl_bolts::os::{ForkResult, fork};
#[cfg(unix)]
use libafl_bolts::os::{ForkResult, dup_and_mute_outputs, fork};
use libafl_bolts::{
ClientId,
core_affinity::CoreId,
Expand Down Expand Up @@ -1037,6 +1037,10 @@ pub struct TcpRestartingMgr<EMH, I, MT, S, SP> {
serialize_state: bool,
/// The hooks for `handle_in_client`
hooks: EMH,
/// If this manager should use `fork` to spawn a new instance. Otherwise it will try to re-launch the current process with exactly the same parameters.
#[cfg(unix)]
#[builder(default = true)]
fork: bool,
#[builder(setter(skip), default = PhantomData)]
phantom_data: PhantomData<(I, S)>,
}
Expand Down Expand Up @@ -1171,8 +1175,8 @@ where
println!("Spawning next client (id {ctr}) {core_id:?}");

// On Unix, we fork (when fork feature is enabled)
#[cfg(all(unix, feature = "fork"))]
let child_status = {
#[cfg(unix)]
let child_status = if self.fork {
self.shmem_provider.pre_fork()?;
match unsafe { fork() }? {
ForkResult::Parent(handle) => {
Expand All @@ -1184,29 +1188,32 @@ where
}
ForkResult::Child => {
self.shmem_provider.post_fork(true)?;
if env::var("LIBAFL_DEBUG_OUTPUT").is_err() {
unsafe {
let _ = dup_and_mute_outputs()?;
}
}
break (staterestorer, self.shmem_provider.clone(), core_id);
}
}
} else {
unsafe {
libc::signal(libc::SIGINT, libc::SIG_IGN);
}
startable_self()?.status()?.code().unwrap_or_default()
};

// If this guy wants to fork, then ignore sigit
#[cfg(any(windows, not(feature = "fork")))]
unsafe {
#[cfg(windows)]
libafl_bolts::os::windows_exceptions::signal(
libafl_bolts::os::windows_exceptions::SIGINT,
libafl_bolts::os::windows_exceptions::sig_ign(),
);

#[cfg(unix)]
libc::signal(libc::SIGINT, libc::SIG_IGN);
}

// 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(any(windows, not(feature = "fork")))]
let child_status = child_status.code().unwrap_or_default();
#[cfg(windows)]
let child_status = {
unsafe {
libafl_bolts::os::windows_exceptions::signal(
libafl_bolts::os::windows_exceptions::SIGINT,
libafl_bolts::os::windows_exceptions::sig_ign(),
);
}
startable_self()?.status()?.code().unwrap_or_default()
};

compiler_fence(Ordering::SeqCst);

Expand Down
6 changes: 3 additions & 3 deletions crates/libafl/src/executors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ pub use combined::CombinedExecutor;
#[cfg(feature = "std")]
pub use command::CommandExecutor;
pub use differential::DiffExecutor;
#[cfg(all(feature = "std", feature = "fork", unix))]
#[cfg(all(feature = "std", unix))]
pub use forkserver::{Forkserver, ForkserverExecutor};
pub use inprocess::InProcessExecutor;
#[cfg(all(feature = "std", feature = "fork", unix))]
#[cfg(all(feature = "std", unix))]
pub use inprocess_fork::InProcessForkExecutor;
#[cfg(unix)]
use libafl_bolts::os::unix_signals::Signal;
Expand All @@ -31,7 +31,7 @@ pub mod combined;
#[cfg(feature = "std")]
pub mod command;
pub mod differential;
#[cfg(all(feature = "std", feature = "fork", unix))]
#[cfg(all(feature = "std", unix))]
pub mod forkserver;
pub mod inprocess;
pub mod nop;
Expand Down
10 changes: 5 additions & 5 deletions crates/libafl/src/monitors/multi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ where
// Only print perf monitor if the feature is enabled
#[cfg(feature = "introspection")]
{
// Print the client performance monitor. Skip the Client 0 which is the broker
for (i, (_, client)) in client_stats_manager
// Print the client performance monitor. Skip clients with no introspection data
// (e.g., broker that never fuzzes will have elapsed_cycles == 0)
for (client_id, client) in client_stats_manager
.client_stats()
.iter()
.filter(|(_, x)| x.enabled())
.enumerate()
.filter(|(_, x)| x.enabled() && x.introspection_stats.elapsed_cycles() > 0)
{
let fmt = format!("Client {:03}:\n{}", i + 1, client.introspection_stats);
let fmt = format!("Client {:03}:\n{}", client_id.0, client.introspection_stats);
(self.print_fn)(&fmt);
}

Expand Down
8 changes: 8 additions & 0 deletions crates/libafl/src/monitors/stats/perf_stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,14 @@ impl fmt::Display for ClientPerfStats {
// Calculate the elapsed time from the monitor
let elapsed: f64 = self.elapsed_cycles() as f64;

// Guard against division by zero which would produce NaN
if elapsed == 0.0 {
return write!(
f,
" NaN: Scheduler\n NaN: Manager\n Feedbacks:\n NaN: Not Measured"
);
}

// Calculate the percentages for each benchmark
let scheduler_percent = self.scheduler as f64 / elapsed;
let manager_percent = self.manager as f64 / elapsed;
Expand Down
10 changes: 5 additions & 5 deletions crates/libafl/src/monitors/tui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,18 +436,18 @@ impl Monitor for TuiMonitor {

#[cfg(feature = "introspection")]
{
// Print the client performance monitor. Skip the Client IDs that have never sent anything.
for (i, (_, client)) in client_stats_manager
// Print the client performance monitor. Skip clients with no introspection data
// (e.g., broker that never fuzzes will have elapsed_cycles == 0)
for (client_id, client) in client_stats_manager
.client_stats()
.iter()
.filter(|(_, x)| x.enabled())
.enumerate()
.filter(|(_, x)| x.enabled() && x.introspection_stats.elapsed_cycles() > 0)
{
self.context
.write()
.unwrap()
.introspection
.entry(i + 1)
.entry(client_id.0 as usize)
.or_default()
.grab_data(&client.introspection_stats);
}
Expand Down
6 changes: 5 additions & 1 deletion crates/libafl_libfuzzer/runtime/src/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@ where
let (state, mgr): (
Option<StdState<_, _, _, _>>,
SimpleRestartingEventManager<_, _, StdState<_, _, _, _>, _, _>,
) = match SimpleRestartingEventManager::launch(monitor, &mut shmem_provider) {
) = match SimpleRestartingEventManager::launch(
monitor,
&mut shmem_provider,
cfg!(feature = "fork"),
) {
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
Ok(res) => res,
Err(err) => match err {
Expand Down
Loading
Loading