use core::{marker::PhantomData, time::Duration};
use std::{
fs::{File, OpenOptions},
io::{self, prelude::*, ErrorKind, SeekFrom},
os::unix::{
io::{AsRawFd, RawFd},
process::CommandExt,
},
process::{Command, Stdio},
};
use crate::{
bolts::{
os::{dup2, pipes::Pipe},
shmem::{ShMem, ShMemProvider, StdShMem, StdShMemProvider},
},
executors::{Executor, ExitKind, HasObservers},
inputs::{HasTargetBytes, Input},
observers::ObserversTuple,
Error,
};
use nix::{
sys::{
select::{pselect, FdSet},
signal::{kill, SigSet, Signal},
time::{TimeSpec, TimeValLike},
},
unistd::Pid,
};
const FORKSRV_FD: i32 = 198;
#[allow(clippy::cast_possible_wrap)]
const FS_OPT_ENABLED: i32 = 0x80000001u32 as i32;
#[allow(clippy::cast_possible_wrap)]
const FS_OPT_SHDMEM_FUZZ: i32 = 0x01000000u32 as i32;
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
}
}
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,
};
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) }
}
}
pub struct OutFile {
file: File,
}
impl OutFile {
pub fn new(file_name: &str) -> Result<Self, Error> {
let f = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(file_name)?;
Ok(Self { file: f })
}
#[must_use]
pub fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
pub fn write_buf(&mut self, buf: &[u8]) {
self.rewind();
self.file.write_all(buf).unwrap();
self.file.set_len(buf.len() as u64).unwrap();
self.file.flush().unwrap();
self.rewind();
}
pub fn rewind(&mut self) {
self.file.seek(SeekFrom::Start(0)).unwrap();
}
}
pub struct Forkserver {
st_pipe: Pipe,
ctl_pipe: Pipe,
child_pid: Pid,
status: i32,
last_run_timed_out: i32,
}
impl Forkserver {
pub fn new(
target: String,
args: Vec<String>,
out_filefd: RawFd,
use_stdin: bool,
memlimit: u64,
) -> Result<Self, Error> {
let mut st_pipe = Pipe::new().unwrap();
let mut ctl_pipe = Pipe::new().unwrap();
match Command::new(target)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.env("LD_BIND_LAZY", "1")
.setlimit(memlimit)
.setsid()
.setstdin(out_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::Forkserver(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] = [0u8; 4];
let rlen = self.st_pipe.read(&mut buf)?;
let val: i32 = i32::from_ne_bytes(buf);
Ok((rlen, val))
}
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] = [0u8; 4];
let st_read = match self.st_pipe.read_end() {
Some(fd) => fd,
None => {
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::Forkserver(
"Unable to communicate with fork server (OOM?)".to_string(),
))
}
} else {
Ok(None)
}
}
}
pub trait HasForkserver {
fn forkserver(&self) -> &Forkserver;
fn forkserver_mut(&mut self) -> &mut Forkserver;
fn out_file(&self) -> &OutFile;
fn out_file_mut(&mut self) -> &mut OutFile;
fn map(&self) -> &Option<StdShMem>;
fn map_mut(&mut self) -> &mut Option<StdShMem>;
}
pub struct TimeoutForkserverExecutor<E> {
executor: E,
timeout: TimeSpec,
}
impl<E> TimeoutForkserverExecutor<E> {
pub fn new(executor: E, exec_tmout: Duration) -> Result<Self, Error> {
let milli_sec = exec_tmout.as_millis() as i64;
let timeout = TimeSpec::milliseconds(milli_sec);
Ok(Self { executor, timeout })
}
}
impl<E, EM, I, S, Z> Executor<EM, I, S, Z> for TimeoutForkserverExecutor<E>
where
I: Input + HasTargetBytes,
E: Executor<EM, I, S, Z> + HasForkserver,
{
#[inline]
fn run_target(
&mut self,
_fuzzer: &mut Z,
_state: &mut S,
_mgr: &mut EM,
input: &I,
) -> Result<ExitKind, Error> {
let mut exit_kind = ExitKind::Ok;
let last_run_timed_out = self.executor.forkserver().last_run_timed_out();
match &mut self.executor.map_mut() {
Some(map) => {
let size = input.target_bytes().as_slice().len();
let size_in_bytes = size.to_ne_bytes();
map.map_mut()[..4].copy_from_slice(&size_in_bytes[..4]);
map.map_mut()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + size)]
.copy_from_slice(input.target_bytes().as_slice());
}
None => {
self.executor
.out_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::Forkserver(
"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::Forkserver(
"Unable to request new process from fork server (OOM?)".to_string(),
));
}
if pid <= 0 {
return Err(Error::Forkserver(
"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(), Signal::SIGKILL);
let (recv_status_len, _) = self.executor.forkserver_mut().read_st()?;
if recv_status_len != 4 {
return Err(Error::Forkserver(
"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<I, OT, S>
where
I: Input + HasTargetBytes,
OT: ObserversTuple<I, S>,
{
target: String,
args: Vec<String>,
out_file: OutFile,
forkserver: Forkserver,
observers: OT,
map: Option<StdShMem>,
phantom: PhantomData<(I, S)>,
}
impl<I, OT, S> ForkserverExecutor<I, OT, S>
where
I: Input + HasTargetBytes,
OT: ObserversTuple<I, S>,
{
pub fn new(
target: String,
arguments: &[String],
use_shmem_testcase: bool,
observers: OT,
) -> Result<Self, Error> {
let mut args = Vec::<String>::new();
let mut use_stdin = true;
let out_filename = ".cur_input".to_string();
for item in arguments {
if item == "@@" && use_stdin {
use_stdin = false;
args.push(out_filename.clone());
} else {
args.push(item.to_string());
}
}
let out_file = OutFile::new(&out_filename)?;
let mut map = None;
if use_shmem_testcase {
let mut provider = StdShMemProvider::new()?;
let mut shmem = provider.new_map(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.map_mut()[..4].clone_from_slice(&size_in_bytes[..4]);
map = Some(shmem);
}
let mut forkserver = Forkserver::new(
target.clone(),
args.clone(),
out_file.as_raw_fd(),
use_stdin,
0,
)?;
let (rlen, status) = forkserver.read_st()?;
if rlen != 4 {
return Err(Error::Forkserver(
"Failed to start a forkserver".to_string(),
));
}
println!("All right - fork server is up.");
if status & FS_OPT_ENABLED == FS_OPT_ENABLED {
if (status & FS_OPT_SHDMEM_FUZZ == FS_OPT_SHDMEM_FUZZ) & use_shmem_testcase {
println!("Using SHARED MEMORY FUZZING feature.");
let send_status = FS_OPT_ENABLED | FS_OPT_SHDMEM_FUZZ;
let send_len = forkserver.write_ctl(send_status)?;
if send_len != 4 {
return Err(Error::Forkserver(
"Writing to forkserver failed.".to_string(),
));
}
}
} else {
println!("Forkserver Options are not available.");
}
Ok(Self {
target,
args,
out_file,
forkserver,
observers,
map,
phantom: PhantomData,
})
}
pub fn target(&self) -> &String {
&self.target
}
pub fn args(&self) -> &[String] {
&self.args
}
pub fn forkserver(&self) -> &Forkserver {
&self.forkserver
}
pub fn out_file(&self) -> &OutFile {
&self.out_file
}
}
impl<EM, I, OT, S, Z> Executor<EM, I, S, Z> for ForkserverExecutor<I, OT, S>
where
I: Input + HasTargetBytes,
OT: ObserversTuple<I, S>,
{
#[inline]
fn run_target(
&mut self,
_fuzzer: &mut Z,
_state: &mut S,
_mgr: &mut EM,
input: &I,
) -> Result<ExitKind, Error> {
let mut exit_kind = ExitKind::Ok;
match &mut self.map {
Some(map) => {
let size = input.target_bytes().as_slice().len();
let size_in_bytes = size.to_ne_bytes();
map.map_mut()[..4].copy_from_slice(&size_in_bytes[..4]);
map.map_mut()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + size)]
.copy_from_slice(input.target_bytes().as_slice());
}
None => {
self.out_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::Forkserver(
"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::Forkserver(
"Unable to request new process from fork server (OOM?)".to_string(),
));
}
if pid <= 0 {
return Err(Error::Forkserver(
"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::Forkserver(
"Unable to communicate with fork server (OOM?)".to_string(),
));
}
self.forkserver.set_status(status);
if libc::WIFSIGNALED(self.forkserver.status()) {
exit_kind = ExitKind::Crash;
}
self.forkserver.set_child_pid(Pid::from_raw(0));
Ok(exit_kind)
}
}
impl<I, OT, S> HasObservers<I, OT, S> for ForkserverExecutor<I, OT, S>
where
I: Input + HasTargetBytes,
OT: ObserversTuple<I, S>,
{
#[inline]
fn observers(&self) -> &OT {
&self.observers
}
#[inline]
fn observers_mut(&mut self) -> &mut OT {
&mut self.observers
}
}
impl<I, OT, S> HasForkserver for ForkserverExecutor<I, OT, S>
where
I: Input + HasTargetBytes,
OT: ObserversTuple<I, S>,
{
#[inline]
fn forkserver(&self) -> &Forkserver {
&self.forkserver
}
#[inline]
fn forkserver_mut(&mut self) -> &mut Forkserver {
&mut self.forkserver
}
#[inline]
fn out_file(&self) -> &OutFile {
&self.out_file
}
#[inline]
fn out_file_mut(&mut self) -> &mut OutFile {
&mut self.out_file
}
#[inline]
fn map(&self) -> &Option<StdShMem> {
&self.map
}
#[inline]
fn map_mut(&mut self) -> &mut Option<StdShMem> {
&mut self.map
}
}
impl<E, I, OT, S> HasObservers<I, OT, S> for TimeoutForkserverExecutor<E>
where
E: HasObservers<I, OT, S>,
OT: ObserversTuple<I, S>,
{
#[inline]
fn observers(&self) -> &OT {
self.executor.observers()
}
#[inline]
fn observers_mut(&mut self) -> &mut OT {
self.executor.observers_mut()
}
}
#[cfg(test)]
mod tests {
use serial_test::serial;
use crate::{
bolts::{
shmem::{ShMem, ShMemProvider, StdShMemProvider},
tuples::tuple_list,
},
executors::ForkserverExecutor,
inputs::NopInput,
observers::{ConstMapObserver, HitcountsMapObserver},
Error,
};
#[test]
#[serial]
fn test_forkserver() {
const MAP_SIZE: usize = 65536;
let bin = "echo";
let args = vec![String::from("@@")];
let mut shmem = StdShMemProvider::new()
.unwrap()
.new_map(MAP_SIZE as usize)
.unwrap();
shmem.write_to_env("__AFL_SHM_ID").unwrap();
let mut shmem_map = shmem.map_mut();
let edges_observer = HitcountsMapObserver::new(ConstMapObserver::<_, MAP_SIZE>::new(
"shared_mem",
&mut shmem_map,
));
let executor = ForkserverExecutor::<NopInput, _, ()>::new(
bin.to_string(),
&args,
false,
tuple_list!(edges_observer),
);
let result = match executor {
Ok(_) => true,
Err(e) => match e {
Error::Forkserver(s) => s == "Failed to start a forkserver",
_ => false,
},
};
assert!(result);
}
}