use alloc::{borrow::ToOwned, string::ToString, vec::Vec};
use core::{
fmt::{self, Debug, Formatter},
marker::PhantomData,
time::Duration,
};
use std::{
ffi::{OsStr, OsString},
io::{self, prelude::*, ErrorKind},
os::unix::{io::RawFd, process::CommandExt},
path::Path,
process::{Command, Stdio},
};
use nix::{
sys::{
select::{pselect, FdSet},
signal::{kill, SigSet, Signal},
time::{TimeSpec, TimeValLike},
},
unistd::Pid,
};
use crate::{
bolts::{
fs::{InputFile, INPUTFILE_STD},
os::{dup2, pipes::Pipe},
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::Prepend,
AsMutSlice, AsSlice,
},
executors::{Executor, ExitKind, HasObservers},
inputs::{HasTargetBytes, Input, UsesInput},
mutators::Tokens,
observers::{
get_asan_runtime_flags_with_log_path, AsanBacktraceObserver, MapObserver, Observer,
ObserversTuple, UsesObservers,
},
state::UsesState,
Error,
};
const FORKSRV_FD: i32 = 198;
#[allow(clippy::cast_possible_wrap)]
const FS_OPT_ENABLED: i32 = 0x80000001_u32 as i32;
#[allow(clippy::cast_possible_wrap)]
const FS_OPT_MAPSIZE: i32 = 0x40000000_u32 as i32;
#[allow(clippy::cast_possible_wrap)]
const FS_OPT_SHDMEM_FUZZ: i32 = 0x01000000_u32 as i32;
#[allow(clippy::cast_possible_wrap)]
const FS_OPT_AUTODICT: i32 = 0x10000000_u32 as i32;
const fn fs_opt_get_mapsize(x: i32) -> i32 {
((x & 0x00fffffe) >> 1) + 1
}
const SHMEM_FUZZ_HDR_SIZE: usize = 4;
const MAX_FILE: usize = 1024 * 1024;
pub trait ConfigTarget {
fn setsid(&mut self) -> &mut Self;
fn setlimit(&mut self, memlimit: u64) -> &mut Self;
fn setstdin(&mut self, fd: RawFd, use_stdin: bool) -> &mut Self;
fn setpipe(
&mut self,
st_read: RawFd,
st_write: RawFd,
ctl_read: RawFd,
ctl_write: RawFd,
) -> &mut Self;
}
impl ConfigTarget for Command {
fn setsid(&mut self) -> &mut Self {
let func = move || {
unsafe {
libc::setsid();
};
Ok(())
};
unsafe { self.pre_exec(func) }
}
fn setpipe(
&mut self,
st_read: RawFd,
st_write: RawFd,
ctl_read: RawFd,
ctl_write: RawFd,
) -> &mut Self {
let func = move || {
match dup2(ctl_read, FORKSRV_FD) {
Ok(_) => (),
Err(_) => {
return Err(io::Error::last_os_error());
}
}
match dup2(st_write, FORKSRV_FD + 1) {
Ok(_) => (),
Err(_) => {
return Err(io::Error::last_os_error());
}
}
unsafe {
libc::close(st_read);
libc::close(st_write);
libc::close(ctl_read);
libc::close(ctl_write);
}
Ok(())
};
unsafe { self.pre_exec(func) }
}
fn setstdin(&mut self, fd: RawFd, use_stdin: bool) -> &mut Self {
if use_stdin {
let func = move || {
match dup2(fd, libc::STDIN_FILENO) {
Ok(_) => (),
Err(_) => {
return Err(io::Error::last_os_error());
}
}
Ok(())
};
unsafe { self.pre_exec(func) }
} else {
self
}
}
#[allow(trivial_numeric_casts, clippy::cast_possible_wrap)]
fn setlimit(&mut self, memlimit: u64) -> &mut Self {
if memlimit == 0 {
return self;
}
let func = move || {
let memlimit: libc::rlim_t = (memlimit as libc::rlim_t) << 20;
let r = libc::rlimit {
rlim_cur: memlimit,
rlim_max: memlimit,
};
let r0 = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
#[cfg(target_os = "openbsd")]
let mut ret = unsafe { libc::setrlimit(libc::RLIMIT_RSS, &r) };
#[cfg(not(target_os = "openbsd"))]
let mut ret = unsafe { libc::setrlimit(libc::RLIMIT_AS, &r) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
ret = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &r0) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
};
unsafe { self.pre_exec(func) }
}
}
#[derive(Debug)]
pub struct Forkserver {
st_pipe: Pipe,
ctl_pipe: Pipe,
child_pid: Pid,
status: i32,
last_run_timed_out: i32,
}
#[allow(clippy::fn_params_excessive_bools)]
impl Forkserver {
#[allow(clippy::too_many_arguments)]
pub fn new(
target: OsString,
args: Vec<OsString>,
envs: Vec<(OsString, OsString)>,
input_filefd: RawFd,
use_stdin: bool,
memlimit: u64,
is_persistent: bool,
is_deferred_frksrv: bool,
debug_output: bool,
) -> Result<Self, Error> {
let mut st_pipe = Pipe::new().unwrap();
let mut ctl_pipe = Pipe::new().unwrap();
let (stdout, stderr) = if debug_output {
(Stdio::inherit(), Stdio::inherit())
} else {
(Stdio::null(), Stdio::null())
};
let mut command = Command::new(target);
command
.args(args)
.stdin(Stdio::null())
.stdout(stdout)
.stderr(stderr);
if is_persistent {
command.env("__AFL_PERSISTENT", "1");
}
if is_deferred_frksrv {
command.env("__AFL_DEFER_FORKSRV", "1");
}
match command
.env("LD_BIND_NOW", "1")
.env("ASAN_OPTIONS", get_asan_runtime_flags_with_log_path())
.envs(envs)
.setlimit(memlimit)
.setsid()
.setstdin(input_filefd, use_stdin)
.setpipe(
st_pipe.read_end().unwrap(),
st_pipe.write_end().unwrap(),
ctl_pipe.read_end().unwrap(),
ctl_pipe.write_end().unwrap(),
)
.spawn()
{
Ok(_) => (),
Err(err) => {
return Err(Error::illegal_state(format!(
"Could not spawn the forkserver: {err:#?}"
)))
}
};
ctl_pipe.close_read_end();
st_pipe.close_write_end();
Ok(Self {
st_pipe,
ctl_pipe,
child_pid: Pid::from_raw(0),
status: 0,
last_run_timed_out: 0,
})
}
#[must_use]
pub fn last_run_timed_out(&self) -> i32 {
self.last_run_timed_out
}
pub fn set_last_run_timed_out(&mut self, last_run_timed_out: i32) {
self.last_run_timed_out = last_run_timed_out;
}
#[must_use]
pub fn status(&self) -> i32 {
self.status
}
pub fn set_status(&mut self, status: i32) {
self.status = status;
}
#[must_use]
pub fn child_pid(&self) -> Pid {
self.child_pid
}
pub fn set_child_pid(&mut self, child_pid: Pid) {
self.child_pid = child_pid;
}
pub fn read_st(&mut self) -> Result<(usize, i32), Error> {
let mut buf: [u8; 4] = [0_u8; 4];
let rlen = self.st_pipe.read(&mut buf)?;
let val: i32 = i32::from_ne_bytes(buf);
Ok((rlen, val))
}
pub fn read_st_size(&mut self, size: usize) -> Result<(usize, Vec<u8>), Error> {
let mut buf = vec![0; size];
let rlen = self.st_pipe.read(&mut buf)?;
Ok((rlen, buf))
}
pub fn write_ctl(&mut self, val: i32) -> Result<usize, Error> {
let slen = self.ctl_pipe.write(&val.to_ne_bytes())?;
Ok(slen)
}
pub fn read_st_timed(&mut self, timeout: &TimeSpec) -> Result<Option<i32>, Error> {
let mut buf: [u8; 4] = [0_u8; 4];
let Some(st_read) = self.st_pipe.read_end() else {
return Err(Error::file(io::Error::new(
ErrorKind::BrokenPipe,
"Read pipe end was already closed",
)));
};
let mut readfds = FdSet::new();
readfds.insert(st_read);
let sret = pselect(
Some(readfds.highest().unwrap() + 1),
&mut readfds,
None,
None,
Some(timeout),
Some(&SigSet::empty()),
)?;
if sret > 0 {
if self.st_pipe.read_exact(&mut buf).is_ok() {
let val: i32 = i32::from_ne_bytes(buf);
Ok(Some(val))
} else {
Err(Error::unknown(
"Unable to communicate with fork server (OOM?)".to_string(),
))
}
} else {
Ok(None)
}
}
}
pub trait HasForkserver {
type SP: ShMemProvider;
fn forkserver(&self) -> &Forkserver;
fn forkserver_mut(&mut self) -> &mut Forkserver;
fn input_file(&self) -> &InputFile;
fn input_file_mut(&mut self) -> &mut InputFile;
fn shmem(&self) -> &Option<<<Self as HasForkserver>::SP as ShMemProvider>::ShMem>;
fn shmem_mut(&mut self) -> &mut Option<<<Self as HasForkserver>::SP as ShMemProvider>::ShMem>;
fn uses_shmem_testcase(&self) -> bool;
}
#[derive(Debug)]
pub struct TimeoutForkserverExecutor<E> {
executor: E,
timeout: TimeSpec,
signal: Signal,
}
impl<E> TimeoutForkserverExecutor<E> {
pub fn new(executor: E, exec_tmout: Duration) -> Result<Self, Error> {
let signal = Signal::SIGKILL;
Self::with_signal(executor, exec_tmout, signal)
}
pub fn with_signal(executor: E, exec_tmout: Duration, signal: Signal) -> Result<Self, Error> {
let milli_sec = exec_tmout.as_millis() as i64;
let timeout = TimeSpec::milliseconds(milli_sec);
Ok(Self {
executor,
timeout,
signal,
})
}
}
impl<E, EM, Z> Executor<EM, Z> for TimeoutForkserverExecutor<E>
where
E: Executor<EM, Z> + HasForkserver + Debug,
E::Input: HasTargetBytes,
EM: UsesState<State = E::State>,
Z: UsesState<State = E::State>,
{
#[inline]
fn run_target(
&mut self,
_fuzzer: &mut Z,
_state: &mut Self::State,
_mgr: &mut EM,
input: &Self::Input,
) -> Result<ExitKind, Error> {
let mut exit_kind = ExitKind::Ok;
let last_run_timed_out = self.executor.forkserver().last_run_timed_out();
if self.executor.uses_shmem_testcase() {
let shmem = unsafe { self.executor.shmem_mut().as_mut().unwrap_unchecked() };
let target_bytes = input.target_bytes();
let size = target_bytes.as_slice().len();
let size_in_bytes = size.to_ne_bytes();
shmem.as_mut_slice()[..4].copy_from_slice(&size_in_bytes[..4]);
shmem.as_mut_slice()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + size)]
.copy_from_slice(target_bytes.as_slice());
} else {
self.executor
.input_file_mut()
.write_buf(input.target_bytes().as_slice())?;
}
let send_len = self
.executor
.forkserver_mut()
.write_ctl(last_run_timed_out)?;
self.executor.forkserver_mut().set_last_run_timed_out(0);
if send_len != 4 {
return Err(Error::unknown(
"Unable to request new process from fork server (OOM?)".to_string(),
));
}
let (recv_pid_len, pid) = self.executor.forkserver_mut().read_st()?;
if recv_pid_len != 4 {
return Err(Error::unknown(
"Unable to request new process from fork server (OOM?)".to_string(),
));
}
if pid <= 0 {
return Err(Error::unknown(
"Fork server is misbehaving (OOM?)".to_string(),
));
}
self.executor
.forkserver_mut()
.set_child_pid(Pid::from_raw(pid));
if let Some(status) = self
.executor
.forkserver_mut()
.read_st_timed(&self.timeout)?
{
self.executor.forkserver_mut().set_status(status);
if libc::WIFSIGNALED(self.executor.forkserver().status()) {
exit_kind = ExitKind::Crash;
}
} else {
self.executor.forkserver_mut().set_last_run_timed_out(1);
let _ = kill(self.executor.forkserver().child_pid(), self.signal);
let (recv_status_len, _) = self.executor.forkserver_mut().read_st()?;
if recv_status_len != 4 {
return Err(Error::unknown("Could not kill timed-out child".to_string()));
}
exit_kind = ExitKind::Timeout;
}
self.executor
.forkserver_mut()
.set_child_pid(Pid::from_raw(0));
Ok(exit_kind)
}
}
pub struct ForkserverExecutor<OT, S, SP>
where
SP: ShMemProvider,
{
target: OsString,
args: Vec<OsString>,
input_file: InputFile,
uses_shmem_testcase: bool,
forkserver: Forkserver,
observers: OT,
map: Option<SP::ShMem>,
phantom: PhantomData<S>,
has_asan_observer: Option<bool>,
map_size: Option<usize>,
}
impl<OT, S, SP> Debug for ForkserverExecutor<OT, S, SP>
where
OT: Debug,
SP: ShMemProvider,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("ForkserverExecutor")
.field("target", &self.target)
.field("args", &self.args)
.field("input_file", &self.input_file)
.field("uses_shmem_testcase", &self.uses_shmem_testcase)
.field("forkserver", &self.forkserver)
.field("observers", &self.observers)
.field("map", &self.map)
.finish()
}
}
impl ForkserverExecutor<(), (), UnixShMemProvider> {
#[must_use]
pub fn builder() -> ForkserverExecutorBuilder<'static, UnixShMemProvider> {
ForkserverExecutorBuilder::new()
}
}
impl<OT, S, SP> ForkserverExecutor<OT, S, SP>
where
OT: ObserversTuple<S>,
S: UsesInput,
SP: ShMemProvider,
{
pub fn target(&self) -> &OsString {
&self.target
}
pub fn args(&self) -> &[OsString] {
&self.args
}
pub fn forkserver(&self) -> &Forkserver {
&self.forkserver
}
pub fn input_file(&self) -> &InputFile {
&self.input_file
}
pub fn coverage_map_size(&self) -> Option<usize> {
self.map_size
}
}
#[derive(Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct ForkserverExecutorBuilder<'a, SP> {
program: Option<OsString>,
arguments: Vec<OsString>,
envs: Vec<(OsString, OsString)>,
debug_child: bool,
use_stdin: bool,
uses_shmem_testcase: bool,
is_persistent: bool,
is_deferred_frksrv: bool,
autotokens: Option<&'a mut Tokens>,
input_filename: Option<OsString>,
shmem_provider: Option<&'a mut SP>,
map_size: Option<usize>,
real_map_size: i32,
}
impl<'a, SP> ForkserverExecutorBuilder<'a, SP> {
#[allow(clippy::pedantic)]
pub fn build<OT, S>(&mut self, observers: OT) -> Result<ForkserverExecutor<OT, S, SP>, Error>
where
OT: ObserversTuple<S>,
S: UsesInput,
S::Input: Input + HasTargetBytes,
SP: ShMemProvider,
{
let (forkserver, input_file, map) = self.build_helper()?;
let target = self.program.take().unwrap();
println!(
"ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}",
target,
self.arguments.clone(),
self.use_stdin
);
Ok(ForkserverExecutor {
target,
args: self.arguments.clone(),
input_file,
uses_shmem_testcase: self.uses_shmem_testcase,
forkserver,
observers,
map,
phantom: PhantomData,
has_asan_observer: None, map_size: self.map_size,
})
}
#[allow(clippy::pedantic)]
pub fn build_dynamic_map<MO, OT, S>(
&mut self,
mut map_observer: MO,
other_observers: OT,
) -> Result<ForkserverExecutor<(MO, OT), S, SP>, Error>
where
MO: Observer<S> + MapObserver, OT: ObserversTuple<S> + Prepend<MO, PreprendResult = OT>,
S: UsesInput,
S::Input: Input + HasTargetBytes,
SP: ShMemProvider,
{
let (forkserver, input_file, map) = self.build_helper()?;
let target = self.program.take().unwrap();
println!(
"ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}, map_size: {:?}",
target,
self.arguments.clone(),
self.use_stdin,
self.map_size
);
if let Some(dynamic_map_size) = self.map_size {
map_observer.downsize_map(dynamic_map_size);
}
let observers: (MO, OT) = other_observers.prepend(map_observer);
Ok(ForkserverExecutor {
target,
args: self.arguments.clone(),
input_file,
uses_shmem_testcase: self.uses_shmem_testcase,
forkserver,
observers,
map,
phantom: PhantomData,
has_asan_observer: None, map_size: self.map_size,
})
}
#[allow(clippy::pedantic)]
fn build_helper(&mut self) -> Result<(Forkserver, InputFile, Option<SP::ShMem>), Error>
where
SP: ShMemProvider,
{
let input_filename = match &self.input_filename {
Some(name) => name.clone(),
None => OsString::from(".cur_input"),
};
let input_file = InputFile::create(input_filename)?;
let map = match &mut self.shmem_provider {
None => None,
Some(provider) => {
let mut shmem = provider.new_shmem(MAX_FILE + SHMEM_FUZZ_HDR_SIZE)?;
shmem.write_to_env("__AFL_SHM_FUZZ_ID")?;
let size_in_bytes = (MAX_FILE + SHMEM_FUZZ_HDR_SIZE).to_ne_bytes();
shmem.as_mut_slice()[..4].clone_from_slice(&size_in_bytes[..4]);
Some(shmem)
}
};
let mut forkserver = match &self.program {
Some(t) => Forkserver::new(
t.clone(),
self.arguments.clone(),
self.envs.clone(),
input_file.as_raw_fd(),
self.use_stdin,
0,
self.is_persistent,
self.is_deferred_frksrv,
self.debug_child,
)?,
None => {
return Err(Error::illegal_argument(
"ForkserverExecutorBuilder::build: target file not found".to_string(),
))
}
};
let (rlen, status) = forkserver.read_st()?;
if rlen != 4 {
return Err(Error::unknown("Failed to start a forkserver".to_string()));
}
println!("All right - fork server is up.");
if status & FS_OPT_ENABLED == FS_OPT_ENABLED
&& (status & FS_OPT_SHDMEM_FUZZ == FS_OPT_SHDMEM_FUZZ
|| status & FS_OPT_AUTODICT == FS_OPT_AUTODICT
|| status & FS_OPT_MAPSIZE == FS_OPT_MAPSIZE)
{
let mut send_status = FS_OPT_ENABLED;
if (status & FS_OPT_SHDMEM_FUZZ == FS_OPT_SHDMEM_FUZZ) && map.is_some() {
println!("Using SHARED MEMORY FUZZING feature.");
send_status |= FS_OPT_SHDMEM_FUZZ;
self.uses_shmem_testcase = true;
}
if status & FS_OPT_MAPSIZE == FS_OPT_MAPSIZE {
let mut map_size = fs_opt_get_mapsize(status);
self.real_map_size = map_size;
if map_size % 64 != 0 {
map_size = ((map_size + 63) >> 6) << 6;
}
assert!(self.map_size.is_none() || map_size as usize <= self.map_size.unwrap());
println!("Target MAP SIZE = {:#x}", self.real_map_size);
self.map_size = Some(map_size as usize);
}
let send_len = forkserver.write_ctl(send_status)?;
if send_len != 4 {
return Err(Error::unknown("Writing to forkserver failed.".to_string()));
}
if (send_status & FS_OPT_AUTODICT) == FS_OPT_AUTODICT {
let (read_len, dict_size) = forkserver.read_st()?;
if read_len != 4 {
return Err(Error::unknown(
"Reading from forkserver failed.".to_string(),
));
}
if !(2..=0xffffff).contains(&dict_size) {
return Err(Error::illegal_state(
"Dictionary has an illegal size".to_string(),
));
}
println!("Autodict size {dict_size:x}");
let (rlen, buf) = forkserver.read_st_size(dict_size as usize)?;
if rlen != dict_size as usize {
return Err(Error::unknown("Failed to load autodictionary".to_string()));
}
if let Some(t) = &mut self.autotokens {
t.parse_autodict(&buf, dict_size as usize);
}
}
} else {
println!("Forkserver Options are not available.");
}
Ok((forkserver, input_file, map))
}
#[must_use]
pub fn autotokens(mut self, tokens: &'a mut Tokens) -> Self {
self.autotokens = Some(tokens);
self
}
#[must_use]
pub fn parse_afl_cmdline<IT, O>(mut self, args: IT) -> Self
where
IT: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
let mut res = vec![];
let mut use_stdin = true;
for item in args {
if item.as_ref() == "@@" && use_stdin {
use_stdin = false;
res.push(OsString::from(".cur_input"));
} else if let Some(name) = &self.input_filename {
if name == item.as_ref() && use_stdin {
use_stdin = false;
res.push(name.clone());
} else {
res.push(item.as_ref().to_os_string());
}
} else {
res.push(item.as_ref().to_os_string());
}
}
self.arguments = res;
self.use_stdin = use_stdin;
self
}
}
impl<'a> ForkserverExecutorBuilder<'a, UnixShMemProvider> {
#[must_use]
pub fn new() -> ForkserverExecutorBuilder<'a, UnixShMemProvider> {
ForkserverExecutorBuilder {
program: None,
arguments: vec![],
envs: vec![],
debug_child: false,
use_stdin: false,
uses_shmem_testcase: false,
is_persistent: false,
is_deferred_frksrv: false,
autotokens: None,
input_filename: None,
shmem_provider: None,
map_size: None,
real_map_size: 0,
}
}
#[must_use]
pub fn program<O>(mut self, program: O) -> Self
where
O: AsRef<OsStr>,
{
self.program = Some(program.as_ref().to_owned());
self
}
#[must_use]
pub fn arg<O>(mut self, arg: O) -> Self
where
O: AsRef<OsStr>,
{
self.arguments.push(arg.as_ref().to_owned());
self
}
#[must_use]
pub fn args<IT, O>(mut self, args: IT) -> Self
where
IT: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
let mut res = vec![];
for arg in args {
res.push(arg.as_ref().to_owned());
}
self.arguments.append(&mut res);
self
}
#[must_use]
pub fn env<K, V>(mut self, key: K, val: V) -> Self
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.envs
.push((key.as_ref().to_owned(), val.as_ref().to_owned()));
self
}
#[must_use]
pub fn envs<IT, K, V>(mut self, vars: IT) -> Self
where
IT: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
let mut res = vec![];
for (ref key, ref val) in vars {
res.push((key.as_ref().to_owned(), val.as_ref().to_owned()));
}
self.envs.append(&mut res);
self
}
#[must_use]
pub fn arg_input_file<P: AsRef<Path>>(self, path: P) -> Self {
let mut moved = self.arg(path.as_ref());
moved.input_filename = Some(path.as_ref().as_os_str().to_os_string());
moved
}
#[must_use]
pub fn arg_input_file_std(self) -> Self {
self.arg_input_file(INPUTFILE_STD)
}
#[must_use]
pub fn debug_child(mut self, debug_child: bool) -> Self {
self.debug_child = debug_child;
self
}
#[must_use]
pub fn is_persistent(mut self, is_persistent: bool) -> Self {
self.is_persistent = is_persistent;
self
}
#[must_use]
pub fn is_deferred_frksrv(mut self, is_deferred_frksrv: bool) -> Self {
self.is_deferred_frksrv = is_deferred_frksrv;
self
}
#[must_use]
pub fn coverage_map_size(mut self, size: usize) -> Self {
self.map_size = Some(size);
self
}
pub fn shmem_provider<SP: ShMemProvider>(
self,
shmem_provider: &'a mut SP,
) -> ForkserverExecutorBuilder<'a, SP> {
ForkserverExecutorBuilder {
program: self.program,
arguments: self.arguments,
envs: self.envs,
debug_child: self.debug_child,
use_stdin: self.use_stdin,
uses_shmem_testcase: self.uses_shmem_testcase,
is_persistent: self.is_persistent,
is_deferred_frksrv: self.is_deferred_frksrv,
autotokens: self.autotokens,
input_filename: self.input_filename,
shmem_provider: Some(shmem_provider),
map_size: self.map_size,
real_map_size: self.real_map_size,
}
}
}
impl<'a> Default for ForkserverExecutorBuilder<'a, UnixShMemProvider> {
fn default() -> Self {
Self::new()
}
}
impl<EM, OT, S, SP, Z> Executor<EM, Z> for ForkserverExecutor<OT, S, SP>
where
OT: ObserversTuple<S>,
SP: ShMemProvider,
S: UsesInput,
S::Input: HasTargetBytes,
EM: UsesState<State = S>,
Z: UsesState<State = S>,
{
#[inline]
fn run_target(
&mut self,
_fuzzer: &mut Z,
_state: &mut Self::State,
_mgr: &mut EM,
input: &Self::Input,
) -> Result<ExitKind, Error> {
let mut exit_kind = ExitKind::Ok;
if self.uses_shmem_testcase {
let map = unsafe { self.map.as_mut().unwrap_unchecked() };
let target_bytes = input.target_bytes();
let mut size = target_bytes.as_slice().len();
if size > MAX_FILE {
size = MAX_FILE;
}
let size_in_bytes = size.to_ne_bytes();
map.as_mut_slice()[..SHMEM_FUZZ_HDR_SIZE]
.copy_from_slice(&size_in_bytes[..SHMEM_FUZZ_HDR_SIZE]);
map.as_mut_slice()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + size)]
.copy_from_slice(target_bytes.as_slice());
} else {
self.input_file.write_buf(input.target_bytes().as_slice())?;
}
let send_len = self
.forkserver
.write_ctl(self.forkserver().last_run_timed_out())?;
if send_len != 4 {
return Err(Error::illegal_state(
"Unable to request new process from fork server (OOM?)".to_string(),
));
}
let (recv_pid_len, pid) = self.forkserver.read_st()?;
if recv_pid_len != 4 {
return Err(Error::illegal_state(
"Unable to request new process from fork server (OOM?)".to_string(),
));
}
if pid <= 0 {
return Err(Error::unknown(
"Fork server is misbehaving (OOM?)".to_string(),
));
}
self.forkserver.set_child_pid(Pid::from_raw(pid));
let (recv_status_len, status) = self.forkserver.read_st()?;
if recv_status_len != 4 {
return Err(Error::unknown(
"Unable to communicate with fork server (OOM?)".to_string(),
));
}
self.forkserver.set_status(status);
if libc::WIFSIGNALED(self.forkserver.status()) {
exit_kind = ExitKind::Crash;
if self.has_asan_observer.is_none() {
self.has_asan_observer = Some(
self.observers()
.match_name::<AsanBacktraceObserver>("AsanBacktraceObserver")
.is_some(),
);
}
if self.has_asan_observer.unwrap() {
self.observers_mut()
.match_name_mut::<AsanBacktraceObserver>("AsanBacktraceObserver")
.unwrap()
.parse_asan_output_from_asan_log_file(pid)?;
}
}
self.forkserver.set_child_pid(Pid::from_raw(0));
Ok(exit_kind)
}
}
impl<OT, S, SP> UsesState for ForkserverExecutor<OT, S, SP>
where
S: UsesInput,
SP: ShMemProvider,
{
type State = S;
}
impl<OT, S, SP> UsesObservers for ForkserverExecutor<OT, S, SP>
where
OT: ObserversTuple<S>,
S: UsesInput,
SP: ShMemProvider,
{
type Observers = OT;
}
impl<OT, S, SP> HasObservers for ForkserverExecutor<OT, S, SP>
where
OT: ObserversTuple<S>,
S: UsesInput,
SP: ShMemProvider,
{
#[inline]
fn observers(&self) -> &OT {
&self.observers
}
#[inline]
fn observers_mut(&mut self) -> &mut OT {
&mut self.observers
}
}
impl<OT, S, SP> HasForkserver for ForkserverExecutor<OT, S, SP>
where
OT: ObserversTuple<S>,
S: UsesInput,
S::Input: Input + HasTargetBytes,
SP: ShMemProvider,
{
type SP = SP;
#[inline]
fn forkserver(&self) -> &Forkserver {
&self.forkserver
}
#[inline]
fn forkserver_mut(&mut self) -> &mut Forkserver {
&mut self.forkserver
}
#[inline]
fn input_file(&self) -> &InputFile {
&self.input_file
}
#[inline]
fn input_file_mut(&mut self) -> &mut InputFile {
&mut self.input_file
}
#[inline]
fn shmem(&self) -> &Option<SP::ShMem> {
&self.map
}
#[inline]
fn shmem_mut(&mut self) -> &mut Option<SP::ShMem> {
&mut self.map
}
#[inline]
fn uses_shmem_testcase(&self) -> bool {
self.uses_shmem_testcase
}
}
impl<E> UsesState for TimeoutForkserverExecutor<E>
where
E: UsesState,
{
type State = E::State;
}
impl<E> UsesObservers for TimeoutForkserverExecutor<E>
where
E: UsesObservers,
{
type Observers = E::Observers;
}
impl<E> HasObservers for TimeoutForkserverExecutor<E>
where
E: HasObservers,
{
#[inline]
fn observers(&self) -> &Self::Observers {
self.executor.observers()
}
#[inline]
fn observers_mut(&mut self) -> &mut Self::Observers {
self.executor.observers_mut()
}
}
#[cfg(test)]
mod tests {
use std::ffi::OsString;
use serial_test::serial;
use crate::{
bolts::{
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::tuple_list,
AsMutSlice,
},
executors::forkserver::ForkserverExecutorBuilder,
observers::{ConstMapObserver, HitcountsMapObserver},
Error,
};
#[test]
#[serial]
fn test_forkserver() {
const MAP_SIZE: usize = 65536;
let bin = OsString::from("echo");
let args = vec![OsString::from("@@")];
let mut shmem_provider = UnixShMemProvider::new().unwrap();
let mut shmem = shmem_provider.new_shmem(MAP_SIZE).unwrap();
shmem.write_to_env("__AFL_SHM_ID").unwrap();
let shmem_buf = shmem.as_mut_slice();
let edges_observer = HitcountsMapObserver::new(ConstMapObserver::<_, MAP_SIZE>::new(
"shared_mem",
shmem_buf,
));
let executor = ForkserverExecutorBuilder::new()
.program(bin)
.args(args)
.debug_child(false)
.shmem_provider(&mut shmem_provider)
.build::<_, ()>(tuple_list!(edges_observer));
let result = match executor {
Ok(_) => true,
Err(e) => match e {
Error::Unknown(s, _) => s == "Failed to start a forkserver",
_ => false,
},
};
assert!(result);
}
}