From 9bc1e93f97feaefeaa56bf63840ba4b2251662ae Mon Sep 17 00:00:00 2001 From: 20urc3 <94982366+20urc3@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:37:14 +0100 Subject: [PATCH 1/3] Update forkserver.rs We're adding these comments in strategic spots where they'll be most useful - mainly at the public API boundaries like struct and function definitions. The style is concise but informative, focusing on explaining what each component does and how to use it properly. --- libafl/src/executors/forkserver.rs | 97 +++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 8 deletions(-) diff --git a/libafl/src/executors/forkserver.rs b/libafl/src/executors/forkserver.rs index 692cbe1cbac..5c698bd6175 100644 --- a/libafl/src/executors/forkserver.rs +++ b/libafl/src/executors/forkserver.rs @@ -1,4 +1,22 @@ -//! Expose an `Executor` based on a `Forkserver` in order to execute AFL/AFL++ binaries +//! # LibAFL Forkserver Executor +//! +//! This module implements an executor that communicates with LibAFL instrumented binaries +//! through a forkserver mechanism. The forkserver allows for efficient process creation by +//! forking from a pre-initialized parent process, avoiding the overhead of repeatedly executing +//! the program from scratch. +//! +//! Key features: +//! - Support for LibAFL (libafl_cc/libafl_cxx) instrumented binaries +//! - Compatible with AFL/AFL++ instrumentation +//! - Shared memory testcase passing for improved performance +//! - Support for persistent mode execution +//! - Handling of deferred forkserver initialization +//! - Automatic dictionary token extraction +//! - Timeout and signal handling for target programs +//! - Compatible with various observer types for feedback collection +//! +//! This implementation follows the forkserver protocol and provides +//! a flexible builder pattern for configuration. use alloc::{borrow::ToOwned, string::ToString, vec::Vec}; use core::{ @@ -94,6 +112,28 @@ const FS_ERROR_OLD_CMPLOG_QEMU: i32 = 64_u32 as i32; /// Forkserver message. We'll reuse it in a testcase. const FAILED_TO_START_FORKSERVER_MSG: &str = "Failed to start forkserver"; +/// Translates forkserver error codes into human-readable error messages +/// +/// This function interprets error statuses received from the forkserver and returns +/// appropriate error messages with troubleshooting advice. It handles various failure modes +/// such as map size issues, shared memory problems, and compatibility errors. +/// +/// # Arguments +/// * `status` - The error status code received from the forkserver +/// +/// # Returns +/// * `Result<(), Error>` - Always returns `Err` with a descriptive error message +/// that explains the failure and suggests possible solutions +/// +/// # Error Codes +/// * `FS_ERROR_MAP_SIZE` - Coverage map size configuration issues +/// * `FS_ERROR_MAP_ADDR` - Hardcoded map address conflicts +/// * `FS_ERROR_SHM_OPEN` - Shared memory opening failures +/// * `FS_ERROR_SHMAT` - Shared memory attachment failures +/// * `FS_ERROR_MMAP` - Memory mapping failures +/// * `FS_ERROR_OLD_CMPLOG` - Outdated comparison logging instrumentation +/// * `FS_ERROR_OLD_CMPLOG_QEMU` - Outdated QEMU/FRIDA loader versions + fn report_error_and_exit(status: i32) -> Result<(), Error> { /* Report on the error received via the forkserver controller and exit */ match status { @@ -253,8 +293,12 @@ impl ConfigTarget for Command { } } -/// The [`Forkserver`] is communication channel with a child process that forks on request of the fuzzer. -/// The communication happens via pipe. +/// A communication channel with an instrumented target process that handles efficient forking +/// +/// The Forkserver implements the LibAFL/AFL++ forkserver protocol, allowing the fuzzer to +/// request new process instances without the overhead of loading the program from scratch. +/// It communicates with the target via pipes for control messages and status information, +/// and manages child process creation, monitoring, and termination. #[derive(Debug)] pub struct Forkserver { /// The "actual" forkserver we spawned in the target @@ -500,7 +544,16 @@ impl Forkserver { } } - /// Read bytes of any length from the st pipe + /// Reads exactly `size` bytes from the status pipe + /// + /// Efficiently allocates and fills a buffer with the exact number of bytes + /// requested from the forkserver's status pipe. + /// + /// # Arguments + /// * `size` - Number of bytes to read + /// + /// # Returns + /// The read bytes or an error if the read fails pub fn read_st_of_len(&mut self, size: usize) -> Result, Error> { let mut buf = Vec::with_capacity(size); // SAFETY: `buf` will not be returned with `Ok` unless it is filled with `size` bytes. @@ -550,6 +603,13 @@ impl Forkserver { let mut readfds = FdSet::new(); readfds.insert(st_read); // We'll pass a copied timeout to keep the original timeout intact, because select updates timeout to indicate how much time was left. See select(2) + + // Use pselect to wait for data with timeout protection + // If data is available (sret > 0), read a 4-byte integer from the pipe + // Returns: + // - Ok(Some(val)) if we successfully read a value + // - Err if communication fails + // - Ok(None) if timeout occurs with no data let sret = pselect( Some(readfds.highest().unwrap().as_raw_fd() + 1), &mut readfds, @@ -573,10 +633,21 @@ impl Forkserver { } } -/// This [`Executor`] can run binaries compiled for AFL/AFL++ that make use of a forkserver. +/// An executor that runs LibAFL-instrumented binaries via a forkserver protocol +/// +/// This executor communicates with instrumented targets using the forkserver mechanism +/// for efficient process creation. It supports shared memory-based test case passing, +/// customizable timeouts, and can handle persistent mode execution. +/// +/// For persistent mode details, see: +/// /// -/// Shared memory feature is also available, but you have to set things up in your code. -/// Please refer to AFL++'s docs. +/// # Type Parameters +/// * `I` - Input type +/// * `OT` - Observer tuple type +/// * `S` - State type +/// * `SHM` - Shared memory type +/// * `TC` - Target bytes converter type pub struct ForkserverExecutor { target: OsString, args: Vec, @@ -769,7 +840,17 @@ where } } -/// The builder for `ForkserverExecutor` +/// Builder for `ForkserverExecutor` with a fluent interface for configuration +/// +/// Provides methods to customize all aspects of forkserver execution: +/// - Target program path and arguments +/// - Input handling (file, stdin, shared memory) +/// - Execution parameters (timeouts, signals) +/// - Performance options (persistent mode, map size) +/// - Instrumentation features (deferred mode, ASan support) +/// +/// Use methods like `program()`, `arg()`, and `timeout()` to configure +/// the executor before calling `build()`. #[derive(Debug)] #[expect(clippy::struct_excessive_bools)] pub struct ForkserverExecutorBuilder<'a, TC, SP> { From c700f262621412e8e4de59075f2b531b5740c839 Mon Sep 17 00:00:00 2001 From: 20urc3 Date: Mon, 14 Apr 2025 16:54:25 +0100 Subject: [PATCH 2/3] improved comments --- libafl/src/executors/forkserver.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/libafl/src/executors/forkserver.rs b/libafl/src/executors/forkserver.rs index 5c698bd6175..948ceb4cd64 100644 --- a/libafl/src/executors/forkserver.rs +++ b/libafl/src/executors/forkserver.rs @@ -1,10 +1,10 @@ //! # LibAFL Forkserver Executor -//! +//! //! This module implements an executor that communicates with LibAFL instrumented binaries //! through a forkserver mechanism. The forkserver allows for efficient process creation by //! forking from a pre-initialized parent process, avoiding the overhead of repeatedly executing //! the program from scratch. -//! +//! //! Key features: //! - Support for LibAFL (libafl_cc/libafl_cxx) instrumented binaries //! - Compatible with AFL/AFL++ instrumentation @@ -14,7 +14,7 @@ //! - Automatic dictionary token extraction //! - Timeout and signal handling for target programs //! - Compatible with various observer types for feedback collection -//! +//! //! This implementation follows the forkserver protocol and provides //! a flexible builder pattern for configuration. @@ -113,18 +113,18 @@ const FS_ERROR_OLD_CMPLOG_QEMU: i32 = 64_u32 as i32; const FAILED_TO_START_FORKSERVER_MSG: &str = "Failed to start forkserver"; /// Translates forkserver error codes into human-readable error messages -/// +/// /// This function interprets error statuses received from the forkserver and returns /// appropriate error messages with troubleshooting advice. It handles various failure modes /// such as map size issues, shared memory problems, and compatibility errors. -/// +/// /// # Arguments /// * `status` - The error status code received from the forkserver -/// +/// /// # Returns /// * `Result<(), Error>` - Always returns `Err` with a descriptive error message /// that explains the failure and suggests possible solutions -/// +/// /// # Error Codes /// * `FS_ERROR_MAP_SIZE` - Coverage map size configuration issues /// * `FS_ERROR_MAP_ADDR` - Hardcoded map address conflicts @@ -639,15 +639,9 @@ impl Forkserver { /// for efficient process creation. It supports shared memory-based test case passing, /// customizable timeouts, and can handle persistent mode execution. /// -/// For persistent mode details, see: +/// For persistent mode details, see: /// -/// -/// # Type Parameters -/// * `I` - Input type -/// * `OT` - Observer tuple type -/// * `S` - State type -/// * `SHM` - Shared memory type -/// * `TC` - Target bytes converter type + pub struct ForkserverExecutor { target: OsString, args: Vec, @@ -840,9 +834,9 @@ where } } -/// Builder for `ForkserverExecutor` with a fluent interface for configuration +/// Builder for [`ForkserverExecutor`] with a fluent interface for configuration /// -/// Provides methods to customize all aspects of forkserver execution: +/// Provides methods to customize all aspects of forkserver instantiation: /// - Target program path and arguments /// - Input handling (file, stdin, shared memory) /// - Execution parameters (timeouts, signals) From 5e0915bd54d9b6d7500776bbc676c3930e243837 Mon Sep 17 00:00:00 2001 From: 20urc3 Date: Mon, 14 Apr 2025 18:49:51 +0100 Subject: [PATCH 3/3] proper error handling --- libafl/src/executors/forkserver.rs | 53 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/libafl/src/executors/forkserver.rs b/libafl/src/executors/forkserver.rs index 948ceb4cd64..346c46fb682 100644 --- a/libafl/src/executors/forkserver.rs +++ b/libafl/src/executors/forkserver.rs @@ -1,10 +1,10 @@ //! # LibAFL Forkserver Executor -//! +//! //! This module implements an executor that communicates with LibAFL instrumented binaries //! through a forkserver mechanism. The forkserver allows for efficient process creation by //! forking from a pre-initialized parent process, avoiding the overhead of repeatedly executing //! the program from scratch. -//! +//! //! Key features: //! - Support for LibAFL (libafl_cc/libafl_cxx) instrumented binaries //! - Compatible with AFL/AFL++ instrumentation @@ -14,7 +14,7 @@ //! - Automatic dictionary token extraction //! - Timeout and signal handling for target programs //! - Compatible with various observer types for feedback collection -//! +//! //! This implementation follows the forkserver protocol and provides //! a flexible builder pattern for configuration. @@ -122,40 +122,39 @@ const FAILED_TO_START_FORKSERVER_MSG: &str = "Failed to start forkserver"; /// * `status` - The error status code received from the forkserver /// /// # Returns -/// * `Result<(), Error>` - Always returns `Err` with a descriptive error message -/// that explains the failure and suggests possible solutions +/// * `Result<(), Error>` - Always returns `Err` with a specific error type that includes +/// a descriptive message explaining the failure and suggesting possible solutions /// -/// # Error Codes -/// * `FS_ERROR_MAP_SIZE` - Coverage map size configuration issues -/// * `FS_ERROR_MAP_ADDR` - Hardcoded map address conflicts -/// * `FS_ERROR_SHM_OPEN` - Shared memory opening failures -/// * `FS_ERROR_SHMAT` - Shared memory attachment failures -/// * `FS_ERROR_MMAP` - Memory mapping failures -/// * `FS_ERROR_OLD_CMPLOG` - Outdated comparison logging instrumentation -/// * `FS_ERROR_OLD_CMPLOG_QEMU` - Outdated QEMU/FRIDA loader versions - +/// # Error Codes and Corresponding Error Types +/// * `FS_ERROR_MAP_SIZE` - Returns `Error::Configuration` for coverage map size issues +/// * `FS_ERROR_MAP_ADDR` - Returns `Error::Compilation` for hardcoded map address conflicts +/// * `FS_ERROR_SHM_OPEN` - Returns `Error::System` for shared memory opening failures +/// * `FS_ERROR_SHMAT` - Returns `Error::System` for shared memory attachment failures +/// * `FS_ERROR_MMAP` - Returns `Error::System` for memory mapping failures +/// * `FS_ERROR_OLD_CMPLOG` - Returns `Error::Version` for outdated instrumentation +/// * `FS_ERROR_OLD_CMPLOG_QEMU` - Returns `Error::Version` for outdated loader versions fn report_error_and_exit(status: i32) -> Result<(), Error> { /* Report on the error received via the forkserver controller and exit */ match status { FS_ERROR_MAP_SIZE => - Err(Error::unknown( - "AFL_MAP_SIZE is not set and fuzzing target reports that the required size is very large. Solution: Run the fuzzing target stand-alone with the environment variable AFL_DEBUG=1 set and set the value for __afl_final_loc in the AFL_MAP_SIZE environment variable for afl-fuzz.".to_string())), + Err(Error::configuration( + "AFL_MAP_SIZE is not set and fuzzing target reports that the required size is very large. Solution: Run the fuzzing target stand-alone with the environment variable AFL_DEBUG=1 set and set the value for __afl_final_loc in the AFL_MAP_SIZE environment variable for afl-fuzz.")), FS_ERROR_MAP_ADDR => - Err(Error::unknown( - "the fuzzing target reports that hardcoded map address might be the reason the mmap of the shared memory failed. Solution: recompile the target with either afl-clang-lto and do not set AFL_LLVM_MAP_ADDR or recompile with afl-clang-fast.".to_string())), + Err(Error::compilation( + "The fuzzing target reports that hardcoded map address might be the reason the mmap of the shared memory failed. Solution: recompile the target with either afl-clang-lto and do not set AFL_LLVM_MAP_ADDR or recompile with afl-clang-fast.")), FS_ERROR_SHM_OPEN => - Err(Error::unknown("the fuzzing target reports that the shm_open() call failed.".to_string())), + Err(Error::system("The fuzzing target reports that the shm_open() call failed.")), FS_ERROR_SHMAT => - Err(Error::unknown("the fuzzing target reports that the shmat() call failed.".to_string())), + Err(Error::system("The fuzzing target reports that the shmat() call failed.")), FS_ERROR_MMAP => - Err(Error::unknown("the fuzzing target reports that the mmap() call to the shared memory failed.".to_string())), + Err(Error::system("The fuzzing target reports that the mmap() call to the shared memory failed.")), FS_ERROR_OLD_CMPLOG => - Err(Error::unknown( - "the -c cmplog target was instrumented with an too old AFL++ version, you need to recompile it.".to_string())), + Err(Error::version( + "The -c cmplog target was instrumented with an too old AFL++ version, you need to recompile it.")), FS_ERROR_OLD_CMPLOG_QEMU => - Err(Error::unknown("The AFL++ QEMU/FRIDA loaders are from an older version, for -c you need to recompile it.".to_string())), + Err(Error::version("The AFL++ QEMU/FRIDA loaders are from an older version, for -c you need to recompile it.")), _ => - Err(Error::unknown(format!("unknown error code {status} from fuzzing target!"))), + Err(Error::unknown(format!("Unknown error code {status} from fuzzing target!"))), } } @@ -639,7 +638,7 @@ impl Forkserver { /// for efficient process creation. It supports shared memory-based test case passing, /// customizable timeouts, and can handle persistent mode execution. /// -/// For persistent mode details, see: +/// For persistent mode details, see: /// pub struct ForkserverExecutor { @@ -836,7 +835,7 @@ where /// Builder for [`ForkserverExecutor`] with a fluent interface for configuration /// -/// Provides methods to customize all aspects of forkserver instantiation: +/// Provides methods to customize all aspects of forkserver execution: /// - Target program path and arguments /// - Input handling (file, stdin, shared memory) /// - Execution parameters (timeouts, signals)