Refactor to new forkserver (#3183)
* Refactor to new forkserver * Fix fuzzer examples and delete forkserver.c * Fix clippy and doc warnings * Fix symbol error * Format Cargo.toml; Fix wrong doc link * Fix silly typo. * Rename ForkServer to Forkserver to make it more consistent * Fix build.rs * Merge StdForkserverParent and PersistentForkserverParent since the forkserver parent has not idea of whether it is persistent and the persistent version can handle the non-persistent version * Fix clippy * Do not take ownership for last_child_pid since it may be in persistent mode
This commit is contained in:
parent
4ae6f34ab4
commit
c0e32cdbba
@ -1,9 +1,19 @@
|
|||||||
use libafl_targets::{map_shared_memory, start_forkserver};
|
use libafl_targets::{
|
||||||
|
map_input_shared_memory, map_shared_memory, start_forkserver, MaybePersistentForkserverParent,
|
||||||
|
};
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn libafl_start_forkserver() {
|
pub extern "C" fn libafl_start_forkserver() {
|
||||||
// Map shared memory region for the edge coverage map
|
// Map shared memory region for the edge coverage map
|
||||||
map_shared_memory();
|
if map_shared_memory().is_err() {
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
// Map shared memory region for input and its len
|
||||||
|
if map_input_shared_memory().is_err() {
|
||||||
|
std::process::exit(1);
|
||||||
|
};
|
||||||
// Start the forkserver
|
// Start the forkserver
|
||||||
start_forkserver();
|
if start_forkserver(&mut MaybePersistentForkserverParent::new()).is_err() {
|
||||||
|
std::process::exit(1);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
use libafl_targets::{map_shared_memory, start_forkserver};
|
use libafl_targets::{
|
||||||
|
map_input_shared_memory, map_shared_memory, start_forkserver, MaybePersistentForkserverParent,
|
||||||
|
};
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn libafl_start_forkserver() {
|
pub extern "C" fn libafl_start_forkserver() {
|
||||||
// Map shared memory region for the edge coverage map
|
// Map shared memory region for the edge coverage map
|
||||||
map_shared_memory();
|
if map_shared_memory().is_err() {
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
// Map shared memory region for input and its len
|
||||||
|
if map_input_shared_memory().is_err() {
|
||||||
|
std::process::exit(1);
|
||||||
|
};
|
||||||
// Start the forkserver
|
// Start the forkserver
|
||||||
start_forkserver();
|
if start_forkserver(&mut MaybePersistentForkserverParent::new()).is_err() {
|
||||||
|
std::process::exit(1);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,14 @@ use std::{
|
|||||||
process::{Child, Command, Stdio},
|
process::{Child, Command, Stdio},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "regex")]
|
||||||
|
use libafl_bolts::tuples::{Handle, Handled, MatchNameRef};
|
||||||
use libafl_bolts::{
|
use libafl_bolts::{
|
||||||
AsSlice, AsSliceMut, InputLocation, TargetArgs, Truncate,
|
AsSlice, AsSliceMut, InputLocation, TargetArgs, Truncate,
|
||||||
fs::{InputFile, get_unique_std_input_file},
|
fs::{InputFile, get_unique_std_input_file},
|
||||||
os::{dup2, pipes::Pipe},
|
os::{dup2, pipes::Pipe},
|
||||||
shmem::{ShMem, ShMemProvider, UnixShMem, UnixShMemProvider},
|
shmem::{ShMem, ShMemProvider, UnixShMem, UnixShMemProvider},
|
||||||
tuples::{Handle, Handled, MatchNameRef, Prepend, RefIndexable},
|
tuples::{Prepend, RefIndexable},
|
||||||
};
|
};
|
||||||
use libc::RLIM_INFINITY;
|
use libc::RLIM_INFINITY;
|
||||||
use nix::{
|
use nix::{
|
||||||
@ -49,45 +51,65 @@ use crate::{
|
|||||||
state::HasExecutions,
|
state::HasExecutions,
|
||||||
};
|
};
|
||||||
|
|
||||||
const FORKSRV_FD: i32 = 198;
|
/// Pinned fd number for forkserver communication
|
||||||
|
pub const FORKSRV_FD: i32 = 198;
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_NEW_ERROR: i32 = 0xeffe0000_u32 as i32;
|
const FS_NEW_ERROR: i32 = 0xeffe0000_u32 as i32;
|
||||||
|
|
||||||
const FS_NEW_VERSION_MIN: u32 = 1;
|
/// Minimum number for new version
|
||||||
const FS_NEW_VERSION_MAX: u32 = 1;
|
pub const FS_NEW_VERSION_MIN: u32 = 1;
|
||||||
|
/// Maximum number for new version
|
||||||
|
pub const FS_NEW_VERSION_MAX: u32 = 1;
|
||||||
|
|
||||||
|
/// Whether forkserver option customization for old forkserver is enabled
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_OPT_ENABLED: i32 = 0x80000001_u32 as i32;
|
pub const FS_OPT_ENABLED: i32 = 0x80000001_u32 as i32;
|
||||||
|
|
||||||
|
/// Set map size option for new forkserver
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_NEW_OPT_MAPSIZE: i32 = 1_u32 as i32;
|
pub const FS_NEW_OPT_MAPSIZE: i32 = 1_u32 as i32;
|
||||||
|
/// Set map size option for old forkserver
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_OPT_MAPSIZE: i32 = 0x40000000_u32 as i32;
|
pub const FS_OPT_MAPSIZE: i32 = 0x40000000_u32 as i32;
|
||||||
|
|
||||||
|
/// Enable shared memory fuzzing option for old forkserver
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_OPT_SHDMEM_FUZZ: i32 = 0x01000000_u32 as i32;
|
pub const FS_OPT_SHDMEM_FUZZ: i32 = 0x01000000_u32 as i32;
|
||||||
|
/// Enable shared memory fuzzing option for new forkserver
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_NEW_OPT_SHDMEM_FUZZ: i32 = 2_u32 as i32;
|
pub const FS_NEW_OPT_SHDMEM_FUZZ: i32 = 2_u32 as i32;
|
||||||
|
|
||||||
|
/// Enable autodict option for new forkserver
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_NEW_OPT_AUTODTCT: i32 = 0x00000800_u32 as i32;
|
pub const FS_NEW_OPT_AUTODTCT: i32 = 0x00000800_u32 as i32;
|
||||||
|
/// Enable autodict option for old forkserver
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_OPT_AUTODTCT: i32 = 0x10000000_u32 as i32;
|
pub const FS_OPT_AUTODTCT: i32 = 0x10000000_u32 as i32;
|
||||||
|
|
||||||
|
/// Failed to set map size
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_ERROR_MAP_SIZE: i32 = 1_u32 as i32;
|
pub const FS_ERROR_MAP_SIZE: i32 = 1_u32 as i32;
|
||||||
|
/// Failed to map address
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_ERROR_MAP_ADDR: i32 = 2_u32 as i32;
|
pub const FS_ERROR_MAP_ADDR: i32 = 2_u32 as i32;
|
||||||
|
/// Failed to open shared memory
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_ERROR_SHM_OPEN: i32 = 4_u32 as i32;
|
pub const FS_ERROR_SHM_OPEN: i32 = 4_u32 as i32;
|
||||||
|
/// Failed to do `shmat`
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_ERROR_SHMAT: i32 = 8_u32 as i32;
|
pub const FS_ERROR_SHMAT: i32 = 8_u32 as i32;
|
||||||
|
/// Failed to do `mmap`
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_ERROR_MMAP: i32 = 16_u32 as i32;
|
pub const FS_ERROR_MMAP: i32 = 16_u32 as i32;
|
||||||
|
/// Old cmplog error
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_ERROR_OLD_CMPLOG: i32 = 32_u32 as i32;
|
pub const FS_ERROR_OLD_CMPLOG: i32 = 32_u32 as i32;
|
||||||
|
/// Old QEMU cmplog error
|
||||||
#[expect(clippy::cast_possible_wrap)]
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
const FS_ERROR_OLD_CMPLOG_QEMU: i32 = 64_u32 as i32;
|
pub const FS_ERROR_OLD_CMPLOG_QEMU: i32 = 64_u32 as i32;
|
||||||
|
/// Flag indicating this is an error
|
||||||
|
#[expect(clippy::cast_possible_wrap)]
|
||||||
|
pub const FS_OPT_ERROR: i32 = 0xf800008f_u32 as i32;
|
||||||
|
|
||||||
/// Forkserver message. We'll reuse it in a testcase.
|
/// Forkserver message. We'll reuse it in a testcase.
|
||||||
const FAILED_TO_START_FORKSERVER_MSG: &str = "Failed to start forkserver";
|
const FAILED_TO_START_FORKSERVER_MSG: &str = "Failed to start forkserver";
|
||||||
@ -121,6 +143,10 @@ fn report_error_and_exit(status: i32) -> Result<(), Error> {
|
|||||||
const SHMEM_FUZZ_HDR_SIZE: usize = 4;
|
const SHMEM_FUZZ_HDR_SIZE: usize = 4;
|
||||||
const MAX_INPUT_SIZE_DEFAULT: usize = 1024 * 1024;
|
const MAX_INPUT_SIZE_DEFAULT: usize = 1024 * 1024;
|
||||||
const MIN_INPUT_SIZE_DEFAULT: usize = 1;
|
const MIN_INPUT_SIZE_DEFAULT: usize = 1;
|
||||||
|
/// Environment variable key for shared memory id for input and its len
|
||||||
|
pub const SHM_FUZZ_ENV_VAR: &str = "__AFL_SHM_FUZZ_ID";
|
||||||
|
/// Environment variable key for shared memory id for edge map
|
||||||
|
pub const SHM_ENV_VAR: &str = "__AFL_SHM_ID";
|
||||||
|
|
||||||
/// The default signal to use to kill child processes
|
/// The default signal to use to kill child processes
|
||||||
const KILL_SIGNAL_DEFAULT: Signal = Signal::SIGTERM;
|
const KILL_SIGNAL_DEFAULT: Signal = Signal::SIGTERM;
|
||||||
@ -341,7 +367,7 @@ impl Forkserver {
|
|||||||
log::warn!("AFL_MAP_SIZE not set. If it is unset, the forkserver may fail to start up");
|
log::warn!("AFL_MAP_SIZE not set. If it is unset, the forkserver may fail to start up");
|
||||||
}
|
}
|
||||||
|
|
||||||
if env::var("__AFL_SHM_ID").is_err() {
|
if env::var(SHM_ENV_VAR).is_err() {
|
||||||
return Err(Error::unknown("__AFL_SHM_ID not set. It is necessary to set this env, otherwise the forkserver cannot communicate with the fuzzer".to_string()));
|
return Err(Error::unknown("__AFL_SHM_ID not set. It is necessary to set this env, otherwise the forkserver cannot communicate with the fuzzer".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,6 +417,8 @@ impl Forkserver {
|
|||||||
};
|
};
|
||||||
command.env("ASAN_OPTIONS", asan_options);
|
command.env("ASAN_OPTIONS", asan_options);
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "regex"))]
|
||||||
|
let _ = dump_asan_logs;
|
||||||
|
|
||||||
let fsrv_handle = match command
|
let fsrv_handle = match command
|
||||||
.env("LD_BIND_NOW", "1")
|
.env("LD_BIND_NOW", "1")
|
||||||
@ -980,7 +1008,7 @@ where
|
|||||||
// # Safety
|
// # Safety
|
||||||
// This is likely single threade here, we're likely fine if it's not.
|
// This is likely single threade here, we're likely fine if it's not.
|
||||||
unsafe {
|
unsafe {
|
||||||
shmem.write_to_env("__AFL_SHM_FUZZ_ID")?;
|
shmem.write_to_env(SHM_FUZZ_ENV_VAR)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let size_in_bytes = (self.max_input_size + SHMEM_FUZZ_HDR_SIZE).to_ne_bytes();
|
let size_in_bytes = (self.max_input_size + SHMEM_FUZZ_HDR_SIZE).to_ne_bytes();
|
||||||
|
@ -58,7 +58,12 @@ common = [
|
|||||||
] # Compile common C code defining sanitizer options and cross-platform intrinsics
|
] # Compile common C code defining sanitizer options and cross-platform intrinsics
|
||||||
coverage = ["common"] # Compile C code definining coverage maps
|
coverage = ["common"] # Compile C code definining coverage maps
|
||||||
cmplog = ["common"] # Compile C code defining cmp log maps
|
cmplog = ["common"] # Compile C code defining cmp log maps
|
||||||
forkserver = ["common"] # Compile C code for forkserver support
|
forkserver = [
|
||||||
|
"common",
|
||||||
|
"nix",
|
||||||
|
"libafl/std",
|
||||||
|
"libafl/fork",
|
||||||
|
] # Compile C code for forkserver support
|
||||||
windows_asan = ["common"] # Compile C code for ASAN on Windows
|
windows_asan = ["common"] # Compile C code for ASAN on Windows
|
||||||
whole_archive = [] # use +whole-archive to ensure the presence of weak symbols
|
whole_archive = [] # use +whole-archive to ensure the presence of weak symbols
|
||||||
cmplog_extended_instrumentation = [
|
cmplog_extended_instrumentation = [
|
||||||
@ -74,6 +79,7 @@ rustversion = "1.0.17"
|
|||||||
libafl = { workspace = true, features = [], default-features = false }
|
libafl = { workspace = true, features = [], default-features = false }
|
||||||
libafl_bolts = { workspace = true, features = [] }
|
libafl_bolts = { workspace = true, features = [] }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
|
nix = { workspace = true, optional = true }
|
||||||
hashbrown = { workspace = true, default-features = true }
|
hashbrown = { workspace = true, default-features = true }
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
@ -241,43 +241,25 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "forkserver", feature = "windows_asan"))]
|
#[cfg(feature = "windows_asan")]
|
||||||
let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
|
|
||||||
|
|
||||||
#[cfg(feature = "forkserver")]
|
|
||||||
{
|
{
|
||||||
if target_family == "unix" {
|
let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
|
||||||
println!("cargo:rerun-if-changed=src/forkserver.c");
|
if target_family == "windows" {
|
||||||
|
println!("cargo:rerun-if-changed=src/windows_asan.c");
|
||||||
|
|
||||||
let mut forkserver = cc::Build::new();
|
let mut windows_asan = cc::Build::new();
|
||||||
|
|
||||||
#[cfg(feature = "whole_archive")]
|
#[cfg(feature = "whole_archive")]
|
||||||
{
|
{
|
||||||
forkserver.link_lib_modifier("+whole-archive");
|
windows_asan.link_lib_modifier("+whole-archive");
|
||||||
}
|
}
|
||||||
|
|
||||||
forkserver
|
windows_asan
|
||||||
.file(src_dir.join("forkserver.c"))
|
.file(src_dir.join("windows_asan.c"))
|
||||||
.compile("forkserver");
|
.compile("windows_asan");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "windows_asan")]
|
|
||||||
if target_family == "windows" {
|
|
||||||
println!("cargo:rerun-if-changed=src/windows_asan.c");
|
|
||||||
|
|
||||||
let mut windows_asan = cc::Build::new();
|
|
||||||
|
|
||||||
#[cfg(feature = "whole_archive")]
|
|
||||||
{
|
|
||||||
windows_asan.link_lib_modifier("+whole-archive");
|
|
||||||
}
|
|
||||||
|
|
||||||
windows_asan
|
|
||||||
.file(src_dir.join("windows_asan.c"))
|
|
||||||
.compile("windows_asan");
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Sanitizer interfaces doesn't require common
|
// NOTE: Sanitizer interfaces doesn't require common
|
||||||
#[cfg(feature = "sanitizer_interfaces")]
|
#[cfg(feature = "sanitizer_interfaces")]
|
||||||
if env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap() == "64" {
|
if env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap() == "64" {
|
||||||
|
@ -33,6 +33,9 @@ pub use __afl_acc_memop_ptr_local as ACCOUNTING_MEMOP_MAP;
|
|||||||
pub static mut MAX_EDGES_FOUND: usize = 0;
|
pub static mut MAX_EDGES_FOUND: usize = 0;
|
||||||
|
|
||||||
unsafe extern "C" {
|
unsafe extern "C" {
|
||||||
|
/// The sharedmemort fuzzing flag
|
||||||
|
pub static mut __afl_sharedmem_fuzzing: core::ffi::c_uint;
|
||||||
|
|
||||||
/// The area pointer points to the edges map.
|
/// The area pointer points to the edges map.
|
||||||
pub static mut __afl_area_ptr: *mut u8;
|
pub static mut __afl_area_ptr: *mut u8;
|
||||||
|
|
||||||
@ -49,6 +52,9 @@ unsafe extern "C" {
|
|||||||
}
|
}
|
||||||
pub use __afl_acc_memop_ptr as ACCOUNTING_MEMOP_MAP_PTR;
|
pub use __afl_acc_memop_ptr as ACCOUNTING_MEMOP_MAP_PTR;
|
||||||
pub use __afl_area_ptr as EDGES_MAP_PTR;
|
pub use __afl_area_ptr as EDGES_MAP_PTR;
|
||||||
|
pub use __afl_fuzz_len as INPUT_LENGTH_PTR;
|
||||||
|
pub use __afl_fuzz_ptr as INPUT_PTR;
|
||||||
|
pub use __afl_sharedmem_fuzzing as SHM_FUZZING;
|
||||||
|
|
||||||
/// Return Tokens from the compile-time token section
|
/// Return Tokens from the compile-time token section
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
@ -71,6 +77,16 @@ pub fn autotokens() -> Result<Tokens, Error> {
|
|||||||
#[allow(non_upper_case_globals)] // expect breaks here for some reason
|
#[allow(non_upper_case_globals)] // expect breaks here for some reason
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub static mut __afl_map_size: usize = EDGES_MAP_DEFAULT_SIZE;
|
pub static mut __afl_map_size: usize = EDGES_MAP_DEFAULT_SIZE;
|
||||||
|
/// The pointer points to the AFL++ inputs
|
||||||
|
#[allow(non_upper_case_globals)] // expect breaks here for some reason
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub static mut __afl_fuzz_ptr: *mut u8 = core::ptr::null_mut();
|
||||||
|
#[allow(non_upper_case_globals)] // expect breaks here for some reason
|
||||||
|
static mut __afl_fuzz_len_local: u32 = 0;
|
||||||
|
/// The pointer points to the length of AFL++ inputs
|
||||||
|
#[allow(non_upper_case_globals)] // expect breaks here for some reason
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub static mut __afl_fuzz_len: *mut u32 = &raw mut __afl_fuzz_len_local;
|
||||||
|
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
feature = "sancov_pcguard_edges",
|
feature = "sancov_pcguard_edges",
|
||||||
|
@ -1,381 +0,0 @@
|
|||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include "android-ashmem.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#ifndef USEMMAP
|
|
||||||
#include <sys/shm.h>
|
|
||||||
#else
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#endif
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#define write_error(s) \
|
|
||||||
fprintf(stderr, "Error at %s:%d: %s\n", __FILE__, __LINE__, s)
|
|
||||||
|
|
||||||
// AFL++ constants
|
|
||||||
#define FORKSRV_FD 198
|
|
||||||
#define MAX_FILE (1024 * 1024)
|
|
||||||
#define SHMEM_FUZZ_HDR_SIZE 4
|
|
||||||
#define SHM_ENV_VAR "__AFL_SHM_ID"
|
|
||||||
#define SHM_FUZZ_ENV_VAR "__AFL_SHM_FUZZ_ID"
|
|
||||||
#define DEFAULT_PERMISSION 0600
|
|
||||||
|
|
||||||
/* Reporting errors */
|
|
||||||
#define FS_OPT_ERROR 0xf800008f
|
|
||||||
#define FS_OPT_GET_ERROR(x) ((x & 0x00ffff00) >> 8)
|
|
||||||
#define FS_OPT_SET_ERROR(x) ((x & 0x0000ffff) << 8)
|
|
||||||
#define FS_ERROR_MAP_SIZE 1
|
|
||||||
#define FS_ERROR_MAP_ADDR 2
|
|
||||||
#define FS_ERROR_SHM_OPEN 4
|
|
||||||
#define FS_ERROR_SHMAT 8
|
|
||||||
#define FS_ERROR_MMAP 16
|
|
||||||
#define FS_ERROR_OLD_CMPLOG 32
|
|
||||||
#define FS_ERROR_OLD_CMPLOG_QEMU 64
|
|
||||||
|
|
||||||
#define FS_NEW_VERSION_MAX 1
|
|
||||||
#define FS_NEW_OPT_MAPSIZE 0x1
|
|
||||||
#define FS_NEW_OPT_SHDMEM_FUZZ 0x2
|
|
||||||
#define FS_NEW_OPT_AUTODICT 0x800
|
|
||||||
|
|
||||||
/* Reporting options */
|
|
||||||
#define FS_OPT_ENABLED 0x80000001
|
|
||||||
#define FS_OPT_MAPSIZE 0x40000000
|
|
||||||
#define FS_OPT_SNAPSHOT 0x20000000
|
|
||||||
#define FS_OPT_AUTODICT 0x10000000
|
|
||||||
#define FS_OPT_SHDMEM_FUZZ 0x01000000
|
|
||||||
#define FS_OPT_NEWCMPLOG 0x02000000
|
|
||||||
#define FS_OPT_OLD_AFLPP_WORKAROUND 0x0f000000
|
|
||||||
// FS_OPT_MAX_MAPSIZE is 8388608 = 0x800000 = 2^23 = 1 << 22
|
|
||||||
#define FS_OPT_MAX_MAPSIZE ((0x00fffffeU >> 1) + 1)
|
|
||||||
#define FS_OPT_GET_MAPSIZE(x) (((x & 0x00fffffe) >> 1) + 1)
|
|
||||||
#define FS_OPT_SET_MAPSIZE(x) \
|
|
||||||
(x <= 1 || x > FS_OPT_MAX_MAPSIZE ? 0 : ((x - 1) << 1))
|
|
||||||
|
|
||||||
// Set by this macro
|
|
||||||
// https://github.com/AFLplusplus/AFLplusplus/blob/stable/src/afl-cc.c#L993
|
|
||||||
|
|
||||||
int __afl_sharedmem_fuzzing __attribute__((weak));
|
|
||||||
|
|
||||||
extern uint8_t *__afl_area_ptr;
|
|
||||||
extern size_t __afl_map_size;
|
|
||||||
extern uint8_t *__token_start;
|
|
||||||
extern uint8_t *__token_stop;
|
|
||||||
|
|
||||||
uint8_t *__afl_fuzz_ptr;
|
|
||||||
static uint32_t __afl_fuzz_len_local;
|
|
||||||
uint32_t *__afl_fuzz_len = &__afl_fuzz_len_local;
|
|
||||||
|
|
||||||
int already_initialized_shm;
|
|
||||||
int already_initialized_forkserver;
|
|
||||||
|
|
||||||
static int child_pid;
|
|
||||||
static void (*old_sigterm_handler)(int) = 0;
|
|
||||||
|
|
||||||
static uint8_t is_persistent;
|
|
||||||
|
|
||||||
void __afl_set_persistent_mode(uint8_t mode) {
|
|
||||||
is_persistent = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Error reporting to forkserver controller */
|
|
||||||
|
|
||||||
static void send_forkserver_error(int error) {
|
|
||||||
uint32_t status;
|
|
||||||
if (!error || error > 0xffff) return;
|
|
||||||
status = (FS_OPT_ERROR | FS_OPT_SET_ERROR(error));
|
|
||||||
if (write(FORKSRV_FD + 1, (char *)&status, 4) != 4) { return; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure we kill the child on termination */
|
|
||||||
|
|
||||||
static void at_exit(int signal) {
|
|
||||||
(void)signal;
|
|
||||||
|
|
||||||
if (child_pid > 0) {
|
|
||||||
kill(child_pid, SIGKILL);
|
|
||||||
child_pid = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
_exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SHM fuzzing setup. */
|
|
||||||
|
|
||||||
void __afl_map_shm(void) {
|
|
||||||
if (already_initialized_shm) return;
|
|
||||||
already_initialized_shm = 1;
|
|
||||||
|
|
||||||
char *id_str = getenv(SHM_ENV_VAR);
|
|
||||||
|
|
||||||
if (id_str) {
|
|
||||||
#ifdef USEMMAP
|
|
||||||
const char *shm_file_path = id_str;
|
|
||||||
int shm_fd = -1;
|
|
||||||
unsigned char *shm_base = NULL;
|
|
||||||
|
|
||||||
/* create the shared memory segment as if it was a file */
|
|
||||||
shm_fd = shm_open(shm_file_path, O_RDWR, DEFAULT_PERMISSION);
|
|
||||||
if (shm_fd == -1) {
|
|
||||||
fprintf(stderr, "shm_open() failed\n");
|
|
||||||
send_forkserver_error(FS_ERROR_SHM_OPEN);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
shm_base =
|
|
||||||
mmap(0, __afl_map_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
|
|
||||||
|
|
||||||
close(shm_fd);
|
|
||||||
shm_fd = -1;
|
|
||||||
|
|
||||||
if (shm_base == MAP_FAILED) {
|
|
||||||
fprintf(stderr, "mmap() failed\n");
|
|
||||||
perror("mmap for map");
|
|
||||||
send_forkserver_error(FS_ERROR_MMAP);
|
|
||||||
exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
__afl_area_ptr = shm_base;
|
|
||||||
#else
|
|
||||||
uint32_t shm_id = atoi(id_str);
|
|
||||||
__afl_area_ptr = (uint8_t *)shmat(shm_id, NULL, 0);
|
|
||||||
|
|
||||||
/* Whooooops. */
|
|
||||||
|
|
||||||
if (!__afl_area_ptr || __afl_area_ptr == (void *)-1) {
|
|
||||||
send_forkserver_error(FS_ERROR_SHMAT);
|
|
||||||
perror("shmat for map");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Write something into the bitmap so that even with low AFL_INST_RATIO,
|
|
||||||
our parent doesn't give up on us. */
|
|
||||||
|
|
||||||
__afl_area_ptr[0] = 1;
|
|
||||||
} else {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Error: variable for edge coverage shared memory is not set\n");
|
|
||||||
send_forkserver_error(FS_ERROR_SHM_OPEN);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void map_input_shared_memory() {
|
|
||||||
char *id_str = getenv(SHM_FUZZ_ENV_VAR);
|
|
||||||
|
|
||||||
if (id_str) {
|
|
||||||
uint8_t *map = NULL;
|
|
||||||
|
|
||||||
#ifdef USEMMAP
|
|
||||||
const char *shm_file_path = id_str;
|
|
||||||
int shm_fd = -1;
|
|
||||||
|
|
||||||
/* create the shared memory segment as if it was a file */
|
|
||||||
shm_fd = shm_open(shm_file_path, O_RDWR, DEFAULT_PERMISSION);
|
|
||||||
if (shm_fd == -1) {
|
|
||||||
fprintf(stderr, "shm_open() failed for fuzz\n");
|
|
||||||
send_forkserver_error(FS_ERROR_SHM_OPEN);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
map = (uint8_t *)mmap(0, MAX_FILE + sizeof(uint32_t), PROT_READ, MAP_SHARED,
|
|
||||||
shm_fd, 0);
|
|
||||||
|
|
||||||
#else
|
|
||||||
uint32_t shm_id = atoi(id_str);
|
|
||||||
map = (uint8_t *)shmat(shm_id, NULL, 0);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Whooooops. */
|
|
||||||
|
|
||||||
if (!map || map == (void *)-1) {
|
|
||||||
perror("Could not access fuzzing shared memory");
|
|
||||||
send_forkserver_error(FS_ERROR_SHM_OPEN);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
__afl_fuzz_len = (uint32_t *)map;
|
|
||||||
__afl_fuzz_ptr = map + sizeof(uint32_t);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "Error: variable for fuzzing shared memory is not set\n");
|
|
||||||
send_forkserver_error(FS_ERROR_SHM_OPEN);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fork server logic. */
|
|
||||||
|
|
||||||
void __afl_start_forkserver(void) {
|
|
||||||
if (already_initialized_forkserver) return;
|
|
||||||
already_initialized_forkserver = 1;
|
|
||||||
|
|
||||||
struct sigaction orig_action;
|
|
||||||
sigaction(SIGTERM, NULL, &orig_action);
|
|
||||||
old_sigterm_handler = orig_action.sa_handler;
|
|
||||||
signal(SIGTERM, at_exit);
|
|
||||||
|
|
||||||
uint32_t already_read_first = 0;
|
|
||||||
uint32_t was_killed;
|
|
||||||
uint32_t version = 0x41464c00 + FS_NEW_VERSION_MAX;
|
|
||||||
uint32_t tmp = version ^ 0xffffffff;
|
|
||||||
uint32_t status = version;
|
|
||||||
uint32_t status2 = version;
|
|
||||||
|
|
||||||
uint8_t *msg = (uint8_t *)&status;
|
|
||||||
uint8_t *reply = (uint8_t *)&status2;
|
|
||||||
|
|
||||||
uint8_t child_stopped = 0;
|
|
||||||
|
|
||||||
void (*old_sigchld_handler)(int) = signal(SIGCHLD, SIG_DFL);
|
|
||||||
|
|
||||||
int autotokens_on = __token_start != NULL && __token_stop != NULL;
|
|
||||||
|
|
||||||
/* Phone home and tell the parent that we're OK. If parent isn't there,
|
|
||||||
assume we're not running in forkserver mode and just execute program. */
|
|
||||||
|
|
||||||
// return because possible non-forkserver usage
|
|
||||||
if (write(FORKSRV_FD + 1, msg, 4) != 4) { return; }
|
|
||||||
|
|
||||||
if (read(FORKSRV_FD, reply, 4) != 4) { _exit(1); }
|
|
||||||
|
|
||||||
if (tmp != status2) {
|
|
||||||
write_error("wrong forkserver message from AFL++ tool");
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
status = FS_NEW_OPT_MAPSIZE;
|
|
||||||
if (__afl_sharedmem_fuzzing) { status |= FS_NEW_OPT_SHDMEM_FUZZ; }
|
|
||||||
if (autotokens_on) { status |= FS_NEW_OPT_AUTODICT; }
|
|
||||||
|
|
||||||
if (write(FORKSRV_FD + 1, msg, 4) != 4) { _exit(1); }
|
|
||||||
|
|
||||||
// Now send the parameters for the set options, increasing by option number
|
|
||||||
|
|
||||||
// FS_NEW_OPT_MAPSIZE - we always send the map size
|
|
||||||
status = __afl_map_size;
|
|
||||||
if (write(FORKSRV_FD + 1, msg, 4) != 4) { _exit(1); }
|
|
||||||
|
|
||||||
// FS_NEW_OPT_AUTODICT - send autotokens
|
|
||||||
if (autotokens_on) {
|
|
||||||
// pass the autotokens through the forkserver FD
|
|
||||||
uint32_t len = (__token_stop - __token_start), offset = 0;
|
|
||||||
|
|
||||||
if (write(FORKSRV_FD + 1, &len, 4) != 4) {
|
|
||||||
fprintf(stderr, "Error: could not send autotokens len\n");
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (len != 0) {
|
|
||||||
int32_t ret;
|
|
||||||
ret = write(FORKSRV_FD + 1, __token_start + offset, len);
|
|
||||||
|
|
||||||
if (ret < 1) {
|
|
||||||
write_error("could not send autotokens");
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
len -= ret;
|
|
||||||
offset += ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// send welcome message as final message
|
|
||||||
status = version;
|
|
||||||
if (write(FORKSRV_FD + 1, msg, 4) != 4) { _exit(1); }
|
|
||||||
|
|
||||||
if (__afl_sharedmem_fuzzing) { map_input_shared_memory(); }
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
int status;
|
|
||||||
|
|
||||||
/* Wait for parent by reading from the pipe. Abort if read fails. */
|
|
||||||
|
|
||||||
if (already_read_first) {
|
|
||||||
already_read_first = 0;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (read(FORKSRV_FD, &was_killed, 4) != 4) {
|
|
||||||
// write_error("read from afl-fuzz");
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we stopped the child in persistent mode, but there was a race
|
|
||||||
condition and afl-fuzz already issued SIGKILL, write off the old
|
|
||||||
process. */
|
|
||||||
|
|
||||||
if (child_stopped && was_killed) {
|
|
||||||
child_stopped = 0;
|
|
||||||
if (waitpid(child_pid, &status, 0) < 0) {
|
|
||||||
write_error("child_stopped && was_killed");
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!child_stopped) {
|
|
||||||
/* Once woken up, create a clone of our process. */
|
|
||||||
|
|
||||||
child_pid = fork();
|
|
||||||
if (child_pid < 0) {
|
|
||||||
write_error("fork");
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* In child process: close fds, resume execution. */
|
|
||||||
|
|
||||||
if (!child_pid) {
|
|
||||||
//(void)nice(-20);
|
|
||||||
|
|
||||||
signal(SIGCHLD, old_sigchld_handler);
|
|
||||||
signal(SIGTERM, old_sigterm_handler);
|
|
||||||
|
|
||||||
close(FORKSRV_FD);
|
|
||||||
close(FORKSRV_FD + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
/* Special handling for persistent mode: if the child is alive but
|
|
||||||
currently stopped, simply restart it with SIGCONT. */
|
|
||||||
|
|
||||||
kill(child_pid, SIGCONT);
|
|
||||||
child_stopped = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* In parent process: write PID to pipe, then wait for child. */
|
|
||||||
|
|
||||||
if (write(FORKSRV_FD + 1, &child_pid, 4) != 4) {
|
|
||||||
write_error("write to afl-fuzz");
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (waitpid(child_pid, &status, is_persistent ? WUNTRACED : 0) < 0) {
|
|
||||||
write_error("waitpid");
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* In persistent mode, the child stops itself with SIGSTOP to indicate
|
|
||||||
a successful run. In this case, we want to wake it up without forking
|
|
||||||
again. */
|
|
||||||
|
|
||||||
if (WIFSTOPPED(status)) child_stopped = 1;
|
|
||||||
|
|
||||||
/* Relay wait status to pipe, then loop back. */
|
|
||||||
|
|
||||||
if (write(FORKSRV_FD + 1, &status, 4) != 4) {
|
|
||||||
write_error("writing to afl-fuzz");
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +1,433 @@
|
|||||||
//! Forkserver logic into targets
|
//! Forkserver logic into targets
|
||||||
|
|
||||||
unsafe extern "C" {
|
use std::{
|
||||||
/// Map a shared memory region for the edge coverage map.
|
os::fd::{AsFd, AsRawFd, BorrowedFd},
|
||||||
fn __afl_map_shm();
|
sync::OnceLock,
|
||||||
/// Start the forkserver.
|
};
|
||||||
fn __afl_start_forkserver();
|
|
||||||
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
Error,
|
||||||
|
executors::forkserver::{
|
||||||
|
FORKSRV_FD, FS_ERROR_SHM_OPEN, FS_NEW_OPT_AUTODTCT, FS_NEW_OPT_MAPSIZE,
|
||||||
|
FS_NEW_OPT_SHDMEM_FUZZ, FS_NEW_VERSION_MAX, FS_OPT_ERROR, SHM_ENV_VAR, SHM_FUZZ_ENV_VAR,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use libafl_bolts::os::{ChildHandle, ForkResult};
|
||||||
|
|
||||||
|
use nix::{
|
||||||
|
sys::signal::{SigHandler, Signal},
|
||||||
|
unistd::Pid,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::coverage::{__afl_map_size, EDGES_MAP_PTR, INPUT_LENGTH_PTR, INPUT_PTR, SHM_FUZZING};
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
|
use crate::coverage::{__token_start, __token_stop};
|
||||||
|
|
||||||
|
/// SAFETY:
|
||||||
|
///
|
||||||
|
/// This fd will be closed after being forked as a child. Thus this fd shall never be
|
||||||
|
/// used after that.
|
||||||
|
const FORKSRV_R_FD: BorrowedFd<'static> = unsafe { BorrowedFd::borrow_raw(FORKSRV_FD) };
|
||||||
|
/// SAFETY:
|
||||||
|
///
|
||||||
|
/// This fd will be closed after being forked as a child. Thus this fd shall never be
|
||||||
|
/// used after that.
|
||||||
|
const FORKSRV_W_FD: BorrowedFd<'static> = unsafe { BorrowedFd::borrow_raw(FORKSRV_FD + 1) };
|
||||||
|
|
||||||
|
fn fs_opt_set_error(error: i32) -> i32 {
|
||||||
|
(error & 0xFFFF) << 8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_to_forkserver(message: &[u8]) -> Result<(), Error> {
|
||||||
|
let bytes_written = nix::unistd::write(FORKSRV_W_FD, message)?;
|
||||||
|
if bytes_written != message.len() {
|
||||||
|
return Err(Error::illegal_state(format!(
|
||||||
|
"Could not write to target fd. Expected {} bytes, wrote {bytes_written} bytes",
|
||||||
|
message.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn write_all_to_forkserver(message: &[u8]) -> Result<(), Error> {
|
||||||
|
let mut remain_len = message.len();
|
||||||
|
while remain_len > 0 {
|
||||||
|
let bytes_written = nix::unistd::write(FORKSRV_W_FD, message)?;
|
||||||
|
remain_len -= bytes_written;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn write_u32_to_forkserver(message: u32) -> Result<(), Error> {
|
||||||
|
write_to_forkserver(&message.to_ne_bytes())
|
||||||
|
}
|
||||||
|
fn write_error_to_forkserver(error: i32) -> Result<(), Error> {
|
||||||
|
if error == 0 || error > 0xFFFF {
|
||||||
|
return Err(Error::illegal_argument("illegal error sent to forkserver"));
|
||||||
|
}
|
||||||
|
#[expect(clippy::cast_sign_loss)]
|
||||||
|
write_u32_to_forkserver((fs_opt_set_error(error) | FS_OPT_ERROR) as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_from_forkserver(message: &mut [u8]) -> Result<(), Error> {
|
||||||
|
let bytes_read = nix::unistd::read(FORKSRV_R_FD.as_fd().as_raw_fd(), message)?;
|
||||||
|
if bytes_read != message.len() {
|
||||||
|
return Err(Error::illegal_state(format!(
|
||||||
|
"Could not read from st pipe. Expected {} bytes, got {bytes_read} bytes",
|
||||||
|
message.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn read_u32_from_forkserver() -> Result<u32, Error> {
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
read_from_forkserver(&mut buf)?;
|
||||||
|
Ok(u32::from_ne_bytes(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Guard [`map_shared_memory`] is invoked only once
|
||||||
|
static SHM_MAP_GUARD: OnceLock<()> = OnceLock::new();
|
||||||
|
|
||||||
/// Map a shared memory region for the edge coverage map.
|
/// Map a shared memory region for the edge coverage map.
|
||||||
|
/// The [`EDGES_MAP_PTR`] will be updated.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// If anything failed, the forkserver will be notified with
|
||||||
///
|
/// [`FS_ERROR_SHM_OPEN`].
|
||||||
/// The function's logic is written in C and this code is a wrapper.
|
pub fn map_shared_memory() -> Result<(), Error> {
|
||||||
pub fn map_shared_memory() {
|
if SHM_MAP_GUARD.set(()).is_err() {
|
||||||
unsafe { __afl_map_shm() }
|
return Err(Error::illegal_state("shared memory has been mapped before"));
|
||||||
|
}
|
||||||
|
map_shared_memory_internal()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start the forkserver from this point. Any shared memory must be created before.
|
fn map_shared_memory_internal() -> Result<(), Error> {
|
||||||
///
|
let Ok(id_str) = std::env::var(SHM_ENV_VAR) else {
|
||||||
/// # Note
|
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
|
||||||
///
|
return Err(Error::illegal_argument(
|
||||||
/// The forkserver logic is written in C and this code is a wrapper.
|
"Error: variable for edge coverage shared memory is not set",
|
||||||
pub fn start_forkserver() {
|
));
|
||||||
unsafe { __afl_start_forkserver() }
|
};
|
||||||
|
let Ok(shm_id) = id_str.parse() else {
|
||||||
|
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
|
||||||
|
return Err(Error::illegal_argument("Invalid __AFL_SHM_ID value"));
|
||||||
|
};
|
||||||
|
let map = unsafe { libc::shmat(shm_id, core::ptr::null(), 0) };
|
||||||
|
if map.is_null() || core::ptr::eq(map, libc::MAP_FAILED) {
|
||||||
|
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
|
||||||
|
return Err(Error::illegal_state("shmat for map"));
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
EDGES_MAP_PTR = map.cast();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Guard [`map_input_shared_memory`] is invoked only once
|
||||||
|
static INPUT_SHM_MAP_GUARD: OnceLock<()> = OnceLock::new();
|
||||||
|
|
||||||
|
/// Map the input shared memory region.
|
||||||
|
/// The [`INPUT_LENGTH_PTR`] and [`INPUT_PTR`] will be updated.
|
||||||
|
///
|
||||||
|
/// If anything failed, the forkserver will be notified with
|
||||||
|
/// [`FS_ERROR_SHM_OPEN`].
|
||||||
|
pub fn map_input_shared_memory() -> Result<(), Error> {
|
||||||
|
if INPUT_SHM_MAP_GUARD.set(()).is_err() {
|
||||||
|
return Err(Error::illegal_state("shared memory has been mapped before"));
|
||||||
|
}
|
||||||
|
map_input_shared_memory_internal()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_input_shared_memory_internal() -> Result<(), Error> {
|
||||||
|
let Ok(id_str) = std::env::var(SHM_FUZZ_ENV_VAR) else {
|
||||||
|
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
|
||||||
|
return Err(Error::illegal_argument(
|
||||||
|
"Error: variable for fuzzing shared memory is not set",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let Ok(shm_id) = id_str.parse() else {
|
||||||
|
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
|
||||||
|
return Err(Error::illegal_argument("Invalid __AFL_SHM_FUZZ_ID value"));
|
||||||
|
};
|
||||||
|
let map = unsafe { libc::shmat(shm_id, core::ptr::null(), 0) };
|
||||||
|
if map.is_null() || core::ptr::eq(map, libc::MAP_FAILED) {
|
||||||
|
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
|
||||||
|
return Err(Error::illegal_state(
|
||||||
|
"Could not access fuzzing shared memory",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let map: *mut u32 = map.cast();
|
||||||
|
unsafe {
|
||||||
|
INPUT_LENGTH_PTR = map;
|
||||||
|
INPUT_PTR = map.add(1).cast();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parent to handle all logics with forkserver children
|
||||||
|
pub trait ForkserverParent {
|
||||||
|
/// Conduct initializing routine before fuzzing loop.
|
||||||
|
///
|
||||||
|
/// Usually, several signal handlers are registered in this function.
|
||||||
|
fn pre_fuzzing(&mut self) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Spawn a child after the forkserver is ready.
|
||||||
|
///
|
||||||
|
/// If the forkserver has killed previous child, `was_killed` will be
|
||||||
|
/// set `true`.
|
||||||
|
///
|
||||||
|
/// The actual forking should be conduct in this function, and in persistent mode,
|
||||||
|
/// some tricks can be done to "fool" the forkserver that a child has been spawned.
|
||||||
|
fn spawn_child(&mut self, was_killed: bool) -> Result<ForkResult, Error>;
|
||||||
|
|
||||||
|
/// Interact with spawned child until the child has done its part.
|
||||||
|
///
|
||||||
|
/// This function should return a status indicating the status of child. Usually,
|
||||||
|
/// that status is determined by `waitpid`.
|
||||||
|
fn handle_child_requests(&mut self) -> Result<i32, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the forkserver loop is going to stop soon.
|
||||||
|
///
|
||||||
|
/// This will be set to true if user send SIGTERM.
|
||||||
|
static STOP_SOON: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
/// Set [`STOP_SOON`] to be `true`. Then the forkserver parent will kill all children
|
||||||
|
/// and then exit asynchrously.
|
||||||
|
extern "C" fn std_handle_sigterm(_signal: libc::c_int) {
|
||||||
|
STOP_SOON.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Forkserver parent that can handle both non-persistent and persistent mode
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct MaybePersistentForkserverParent {
|
||||||
|
last_child_pid: Option<i32>,
|
||||||
|
/// This field is only touched for persistent mode to indicating
|
||||||
|
/// whether the child is temporarily stopped or terminated
|
||||||
|
child_stopped: bool,
|
||||||
|
old_sigchld_handler: Option<SigHandler>,
|
||||||
|
old_sigterm_handler: Option<SigHandler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaybePersistentForkserverParent {
|
||||||
|
/// Create a new forkserver parent.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
MaybePersistentForkserverParent::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ForkserverParent for MaybePersistentForkserverParent {
|
||||||
|
fn pre_fuzzing(&mut self) -> Result<(), Error> {
|
||||||
|
let old_sigchld_handler =
|
||||||
|
(unsafe { nix::sys::signal::signal(Signal::SIGCHLD, SigHandler::SigDfl) })
|
||||||
|
.inspect_err(|_| {
|
||||||
|
log::error!("Fail to swap signal handler for SIGCHLD.");
|
||||||
|
})?;
|
||||||
|
self.old_sigchld_handler = Some(old_sigchld_handler);
|
||||||
|
let old_sigterm_handler = (unsafe {
|
||||||
|
nix::sys::signal::signal(Signal::SIGTERM, SigHandler::Handler(std_handle_sigterm))
|
||||||
|
})
|
||||||
|
.inspect_err(|_| {
|
||||||
|
log::error!("Fail to swap signal handler for SIGTERM.");
|
||||||
|
})?;
|
||||||
|
self.old_sigterm_handler = Some(old_sigterm_handler);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_child(&mut self, was_killed: bool) -> Result<ForkResult, Error> {
|
||||||
|
if STOP_SOON.load(Ordering::Relaxed) {
|
||||||
|
if let Some(child_pid) = self.last_child_pid.take() {
|
||||||
|
nix::sys::signal::kill(Pid::from_raw(child_pid), Signal::SIGKILL)?;
|
||||||
|
}
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
// If we stopped the child in persistent mode, but there was a race
|
||||||
|
// condition and afl-fuzz already issued SIGKILL, write off the old
|
||||||
|
// process.
|
||||||
|
if self.child_stopped && was_killed {
|
||||||
|
self.child_stopped = false;
|
||||||
|
// unwrap here: child_stopped is set as true only if it has spawned
|
||||||
|
// a child, wait it, and get a stopped signal. Moreover, was_killed is
|
||||||
|
// true only if the forkserver killed such child. In all cases, the
|
||||||
|
// last_child_pid will never be None.
|
||||||
|
if nix::sys::wait::waitpid(
|
||||||
|
Pid::from_raw(self.last_child_pid.take().unwrap()),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Err(Error::illegal_state("child_stopped && was_killed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.child_stopped {
|
||||||
|
// Special handling for persistent mode: if the child is alive but
|
||||||
|
// currently stopped, simply restart it with SIGCONT.
|
||||||
|
|
||||||
|
// unwrap here: child_stopped is true only if last_child_pid is some.
|
||||||
|
let child_pid = *self.last_child_pid.as_ref().unwrap();
|
||||||
|
nix::sys::signal::kill(Pid::from_raw(child_pid), Signal::SIGCONT)?;
|
||||||
|
self.child_stopped = false;
|
||||||
|
Ok(ForkResult::Parent(ChildHandle { pid: child_pid }))
|
||||||
|
} else {
|
||||||
|
// Once woken up, create a clone of our process.
|
||||||
|
let fork_result = (unsafe { libafl_bolts::os::fork() }).inspect_err(|_| {
|
||||||
|
log::error!("fork");
|
||||||
|
})?;
|
||||||
|
match &fork_result {
|
||||||
|
ForkResult::Parent(child_pid) => {
|
||||||
|
self.last_child_pid = Some(child_pid.pid);
|
||||||
|
}
|
||||||
|
ForkResult::Child => unsafe {
|
||||||
|
// unwrap here: the field is assigned in `pre_fuzzing`
|
||||||
|
nix::sys::signal::signal(Signal::SIGCHLD, self.old_sigchld_handler.take().unwrap())
|
||||||
|
.inspect_err(|_| {
|
||||||
|
log::error!("Fail to restore signal handler for SIGCHLD.");
|
||||||
|
})?;
|
||||||
|
// unwrap here: the field is assigned in `pre_fuzzing`
|
||||||
|
nix::sys::signal::signal(Signal::SIGTERM, self.old_sigterm_handler.take().unwrap())
|
||||||
|
.inspect_err(|_| {
|
||||||
|
log::error!("Fail to restore signal handler for SIGTERM.");
|
||||||
|
})?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ok(fork_result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_child_requests(&mut self) -> Result<i32, Error> {
|
||||||
|
let mut status = 0i32;
|
||||||
|
// unwrap here: the field is assigned if we are parent process in `spawn_child`
|
||||||
|
if unsafe { libc::waitpid(*self.last_child_pid.as_ref().unwrap(), &raw mut status, 0) < 0 } {
|
||||||
|
return Err(Error::illegal_state("waitpid"));
|
||||||
|
}
|
||||||
|
if libc::WIFSTOPPED(status) {
|
||||||
|
self.child_stopped = true;
|
||||||
|
}
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Success state when [`start_forkserver`] returned.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ForkserverState {
|
||||||
|
/// There is no AFL forkserver responded. In such case,
|
||||||
|
/// we should allow user to do a normal execution.
|
||||||
|
NoAfl,
|
||||||
|
/// Current process is a spawned child.
|
||||||
|
Child,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Guard [`start_forkserver`] is invoked only once
|
||||||
|
static FORKSERVER_GUARD: OnceLock<()> = OnceLock::new();
|
||||||
|
|
||||||
|
/// Start a forkserver. This function will handle all communication
|
||||||
|
/// with AFL forkserver end, and use `forkserver_parent` to interact
|
||||||
|
/// with forked child.
|
||||||
|
///
|
||||||
|
/// This function will spawn a child in each round, and in the root process,
|
||||||
|
/// the loop will never return if everything is OK.
|
||||||
|
///
|
||||||
|
/// Before invoking this function, you should initialize [`EDGES_MAP_PTR`],
|
||||||
|
/// [`INPUT_PTR`] and [`INPUT_LENGTH_PTR`] properly. [`map_shared_memory`] and
|
||||||
|
/// [`map_input_shared_memory`] can be used, for example.
|
||||||
|
pub fn start_forkserver<P: ForkserverParent>(
|
||||||
|
forkserver_parent: &mut P,
|
||||||
|
) -> Result<ForkserverState, Error> {
|
||||||
|
if FORKSERVER_GUARD.set(()).is_err() {
|
||||||
|
return Err(Error::illegal_state("forkserver has been started before"));
|
||||||
|
}
|
||||||
|
start_forkserver_internal(forkserver_parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
const VERSION: u32 = 0x41464c00 + FS_NEW_VERSION_MAX;
|
||||||
|
fn start_forkserver_internal<P: ForkserverParent>(
|
||||||
|
forkserver_parent: &mut P,
|
||||||
|
) -> Result<ForkserverState, Error> {
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
|
let autotokens_on = unsafe { !__token_start.is_null() && !__token_stop.is_null() };
|
||||||
|
let sharedmem_fuzzing = unsafe { SHM_FUZZING == 1 };
|
||||||
|
|
||||||
|
// Parent supports testcases via shared map - and the user wants to use it. Tell AFL.
|
||||||
|
// Phone home and tell the parent that we're OK. If parent isn't there, assume we're
|
||||||
|
// not running in forkserver mode and just execute program.
|
||||||
|
if write_u32_to_forkserver(VERSION).is_err() {
|
||||||
|
return Ok(ForkserverState::NoAfl);
|
||||||
|
}
|
||||||
|
|
||||||
|
let reply = read_u32_from_forkserver()?;
|
||||||
|
if reply != VERSION ^ 0xFFFFFFFF {
|
||||||
|
return Err(Error::illegal_state(
|
||||||
|
"wrong forkserver message from AFL++ tool",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut status = FS_NEW_OPT_MAPSIZE;
|
||||||
|
if sharedmem_fuzzing {
|
||||||
|
status |= FS_NEW_OPT_SHDMEM_FUZZ;
|
||||||
|
}
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
|
if autotokens_on {
|
||||||
|
status |= FS_NEW_OPT_AUTODTCT;
|
||||||
|
}
|
||||||
|
#[expect(clippy::cast_sign_loss)]
|
||||||
|
write_u32_to_forkserver(status as u32)?;
|
||||||
|
|
||||||
|
// Now send the parameters for the set options, increasing by option number
|
||||||
|
|
||||||
|
// FS_NEW_OPT_MAPSIZE - we always send the map size
|
||||||
|
write_u32_to_forkserver(unsafe { __afl_map_size as u32 })?;
|
||||||
|
|
||||||
|
// FS_NEW_OPT_AUTODICT - send autotokens
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
|
if autotokens_on {
|
||||||
|
#[expect(clippy::cast_sign_loss)]
|
||||||
|
let tokens_len = unsafe { __token_stop.offset_from(__token_start) } as u32;
|
||||||
|
write_u32_to_forkserver(tokens_len).inspect_err(|_| {
|
||||||
|
log::error!("Error: could not send autotokens len");
|
||||||
|
})?;
|
||||||
|
write_all_to_forkserver(unsafe {
|
||||||
|
core::slice::from_raw_parts(__token_start, tokens_len as usize)
|
||||||
|
})
|
||||||
|
.inspect_err(|_| {
|
||||||
|
log::error!("could not send autotokens");
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send welcome message as final message
|
||||||
|
write_u32_to_forkserver(VERSION)?;
|
||||||
|
|
||||||
|
forkserver_parent.pre_fuzzing()?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Wait for parent by reading from the pipe. Abort if read fails.
|
||||||
|
|
||||||
|
let was_killed = read_u32_from_forkserver()?;
|
||||||
|
|
||||||
|
let fork_result = forkserver_parent.spawn_child(was_killed != 0)?;
|
||||||
|
|
||||||
|
match fork_result {
|
||||||
|
ForkResult::Child => {
|
||||||
|
// FORKSRV_FD is for communication with AFL, we don't need it in the child
|
||||||
|
let _ = nix::unistd::close(FORKSRV_R_FD.as_raw_fd());
|
||||||
|
let _ = nix::unistd::close(FORKSRV_W_FD.as_raw_fd());
|
||||||
|
return Ok(ForkserverState::Child);
|
||||||
|
}
|
||||||
|
ForkResult::Parent(child_pid) => {
|
||||||
|
#[expect(clippy::cast_sign_loss)]
|
||||||
|
write_u32_to_forkserver(child_pid.pid as u32).inspect_err(|_| {
|
||||||
|
log::error!("write to afl-fuzz");
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = forkserver_parent.handle_child_requests()?;
|
||||||
|
|
||||||
|
// Relay wait status to AFL pipe, then loop back.
|
||||||
|
#[expect(clippy::cast_sign_loss)]
|
||||||
|
write_u32_to_forkserver(status as u32).inspect_err(|_| {
|
||||||
|
log::error!("writing to afl-fuzz");
|
||||||
|
})?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ pub mod windows_asan;
|
|||||||
#[cfg(all(windows, feature = "std", feature = "windows_asan"))]
|
#[cfg(all(windows, feature = "std", feature = "windows_asan"))]
|
||||||
pub use windows_asan::*;
|
pub use windows_asan::*;
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "forkserver"))]
|
#[cfg(all(unix, feature = "std", feature = "forkserver"))]
|
||||||
pub mod forkserver;
|
pub mod forkserver;
|
||||||
#[cfg(all(unix, feature = "forkserver"))]
|
#[cfg(all(unix, feature = "std", feature = "forkserver"))]
|
||||||
pub use forkserver::*;
|
pub use forkserver::*;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user