ForkserverExecutor: stop forked children on exit (#1493)

* wip

* Fix forkserver exit

* undo change in forkserver_simple

* less map_err

---------

Co-authored-by: Marco Vanotti <mvanotti@google.com>
This commit is contained in:
Dominik Maier 2023-08-31 22:51:21 +02:00 committed by GitHub
parent d0d378c174
commit 134fe6a992
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -12,7 +12,7 @@ use std::{
io::{self, prelude::*, ErrorKind}, io::{self, prelude::*, ErrorKind},
os::unix::{io::RawFd, process::CommandExt}, os::unix::{io::RawFd, process::CommandExt},
path::Path, path::Path,
process::{Command, Stdio}, process::{Child, Command, Stdio},
}; };
use libafl_bolts::{ use libafl_bolts::{
@ -27,6 +27,7 @@ use nix::{
select::{pselect, FdSet}, select::{pselect, FdSet},
signal::{kill, SigSet, Signal}, signal::{kill, SigSet, Signal},
time::{TimeSpec, TimeValLike}, time::{TimeSpec, TimeValLike},
wait::waitpid,
}, },
unistd::Pid, unistd::Pid,
}; };
@ -150,6 +151,9 @@ impl ConfigTarget for Command {
if memlimit == 0 { if memlimit == 0 {
return self; return self;
} }
// SAFETY
// This method does not do shady pointer foo.
// It merely call libc functions.
let func = move || { let func = move || {
let memlimit: libc::rlim_t = (memlimit as libc::rlim_t) << 20; let memlimit: libc::rlim_t = (memlimit as libc::rlim_t) << 20;
let r = libc::rlimit { let r = libc::rlimit {
@ -174,6 +178,8 @@ impl ConfigTarget for Command {
} }
Ok(()) Ok(())
}; };
// # SAFETY
// This calls our non-shady function from above.
unsafe { self.pre_exec(func) } unsafe { self.pre_exec(func) }
} }
} }
@ -182,13 +188,40 @@ impl ConfigTarget for Command {
/// The communication happens via pipe. /// The communication happens via pipe.
#[derive(Debug)] #[derive(Debug)]
pub struct Forkserver { pub struct Forkserver {
/// The "actual" forkserver we spawned in the target
fsrv_handle: Child,
/// Status pipe
st_pipe: Pipe, st_pipe: Pipe,
/// Control pipe
ctl_pipe: Pipe, ctl_pipe: Pipe,
child_pid: Pid, /// Pid of the current forked child (child of the forkserver) during execution
child_pid: Option<Pid>,
/// The last status reported to us by the in-target forkserver
status: i32, status: i32,
/// If the last run timed out (in in-target i32)
last_run_timed_out: i32, last_run_timed_out: i32,
} }
impl Drop for Forkserver {
fn drop(&mut self) {
if let Err(err) = self.fsrv_handle.kill() {
log::warn!("Failed kill forkserver: {err}",);
}
if let Some(pid) = self.child_pid {
if let Err(err) = kill(pid, Signal::SIGKILL) {
log::warn!(
"Failed to deliver kill signal to child process {}: {err} ({})",
pid,
io::Error::last_os_error()
);
}
if let Err(err) = waitpid(pid, None) {
log::warn!("Failed to wait for child pid ({pid}): {err}",);
}
}
}
}
#[allow(clippy::fn_params_excessive_bools)] #[allow(clippy::fn_params_excessive_bools)]
impl Forkserver { impl Forkserver {
/// Create a new [`Forkserver`] /// Create a new [`Forkserver`]
@ -234,7 +267,7 @@ impl Forkserver {
#[cfg(feature = "regex")] #[cfg(feature = "regex")]
command.env("ASAN_OPTIONS", get_asan_runtime_flags_with_log_path()); command.env("ASAN_OPTIONS", get_asan_runtime_flags_with_log_path());
match command let fsrv_handle = match command
.env("LD_BIND_NOW", "1") .env("LD_BIND_NOW", "1")
.envs(envs) .envs(envs)
.setlimit(memlimit) .setlimit(memlimit)
@ -248,7 +281,7 @@ impl Forkserver {
) )
.spawn() .spawn()
{ {
Ok(_) => (), Ok(fsrv_handle) => fsrv_handle,
Err(err) => { Err(err) => {
return Err(Error::illegal_state(format!( return Err(Error::illegal_state(format!(
"Could not spawn the forkserver: {err:#?}" "Could not spawn the forkserver: {err:#?}"
@ -261,25 +294,39 @@ impl Forkserver {
st_pipe.close_write_end(); st_pipe.close_write_end();
Ok(Self { Ok(Self {
fsrv_handle,
st_pipe, st_pipe,
ctl_pipe, ctl_pipe,
child_pid: Pid::from_raw(0), child_pid: None,
status: 0, status: 0,
last_run_timed_out: 0, last_run_timed_out: 0,
}) })
} }
/// If the last run timed out /// If the last run timed out (as in-target i32)
#[must_use] #[must_use]
pub fn last_run_timed_out(&self) -> i32 { pub fn last_run_timed_out_raw(&self) -> i32 {
self.last_run_timed_out self.last_run_timed_out
} }
/// Sets if the last run timed out /// If the last run timed out
pub fn set_last_run_timed_out(&mut self, last_run_timed_out: i32) { #[must_use]
pub fn last_run_timed_out(&self) -> bool {
self.last_run_timed_out_raw() != 0
}
/// Sets if the last run timed out (as in-target i32)
#[inline]
pub fn set_last_run_timed_out_raw(&mut self, last_run_timed_out: i32) {
self.last_run_timed_out = last_run_timed_out; self.last_run_timed_out = last_run_timed_out;
} }
/// Sets if the last run timed out
#[inline]
pub fn set_last_run_timed_out(&mut self, last_run_timed_out: bool) {
self.last_run_timed_out = i32::from(last_run_timed_out);
}
/// The status /// The status
#[must_use] #[must_use]
pub fn status(&self) -> i32 { pub fn status(&self) -> i32 {
@ -294,12 +341,17 @@ impl Forkserver {
/// The child pid /// The child pid
#[must_use] #[must_use]
pub fn child_pid(&self) -> Pid { pub fn child_pid(&self) -> Pid {
self.child_pid self.child_pid.unwrap()
} }
/// Set the child pid /// Set the child pid
pub fn set_child_pid(&mut self, child_pid: Pid) { pub fn set_child_pid(&mut self, child_pid: Pid) {
self.child_pid = child_pid; self.child_pid = Some(child_pid);
}
/// Remove the child pid.
pub fn reset_child_pid(&mut self) {
self.child_pid = None;
} }
/// Read from the st pipe /// Read from the st pipe
@ -433,9 +485,15 @@ where
) -> Result<ExitKind, Error> { ) -> Result<ExitKind, Error> {
let mut exit_kind = ExitKind::Ok; let mut exit_kind = ExitKind::Ok;
let last_run_timed_out = self.executor.forkserver().last_run_timed_out(); let last_run_timed_out = self.executor.forkserver().last_run_timed_out_raw();
if self.executor.uses_shmem_testcase() { if self.executor.uses_shmem_testcase() {
debug_assert!(
self.executor.shmem_mut().is_some(),
"The uses_shmem_testcase() bool can only exist when a map is set"
);
// # SAFETY
// Struct can never be created when uses_shmem_testcase is true and map is none.
let map = unsafe { self.executor.shmem_mut().as_mut().unwrap_unchecked() }; let map = unsafe { self.executor.shmem_mut().as_mut().unwrap_unchecked() };
let target_bytes = input.target_bytes(); let target_bytes = input.target_bytes();
let mut size = target_bytes.as_slice().len(); let mut size = target_bytes.as_slice().len();
@ -461,7 +519,7 @@ where
.forkserver_mut() .forkserver_mut()
.write_ctl(last_run_timed_out)?; .write_ctl(last_run_timed_out)?;
self.executor.forkserver_mut().set_last_run_timed_out(0); self.executor.forkserver_mut().set_last_run_timed_out(false);
if send_len != 4 { if send_len != 4 {
return Err(Error::unknown( return Err(Error::unknown(
@ -503,11 +561,10 @@ where
} }
} }
} else { } else {
self.executor.forkserver_mut().set_last_run_timed_out(1); self.executor.forkserver_mut().set_last_run_timed_out(true);
// We need to kill the child in case he has timed out, or we can't get the correct pid in the next call to self.executor.forkserver_mut().read_st()? // We need to kill the child in case he has timed out, or we can't get the correct pid in the next call to self.executor.forkserver_mut().read_st()?
let _: Result<(), nix::errno::Errno> = let _ = kill(self.executor.forkserver().child_pid(), self.signal);
kill(self.executor.forkserver().child_pid(), self.signal);
let (recv_status_len, _) = self.executor.forkserver_mut().read_st()?; let (recv_status_len, _) = self.executor.forkserver_mut().read_st()?;
if recv_status_len != 4 { if recv_status_len != 4 {
return Err(Error::unknown("Could not kill timed-out child".to_string())); return Err(Error::unknown("Could not kill timed-out child".to_string()));
@ -515,9 +572,7 @@ where
exit_kind = ExitKind::Timeout; exit_kind = ExitKind::Timeout;
} }
self.executor self.executor.forkserver_mut().reset_child_pid();
.forkserver_mut()
.set_child_pid(Pid::from_raw(0));
Ok(exit_kind) Ok(exit_kind)
} }
@ -645,6 +700,12 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> {
self.use_stdin self.use_stdin
); );
if self.uses_shmem_testcase && map.is_none() {
return Err(Error::illegal_state(
"Map must always be set for `uses_shmem_testcase`",
));
}
Ok(ForkserverExecutor { Ok(ForkserverExecutor {
target, target,
args: self.arguments.clone(), args: self.arguments.clone(),
@ -690,6 +751,12 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> {
let observers: (MO, OT) = other_observers.prepend(map_observer); let observers: (MO, OT) = other_observers.prepend(map_observer);
if self.uses_shmem_testcase && map.is_none() {
return Err(Error::illegal_state(
"Map must always be set for `uses_shmem_testcase`",
));
}
Ok(ForkserverExecutor { Ok(ForkserverExecutor {
target, target,
args: self.arguments.clone(), args: self.arguments.clone(),
@ -1097,6 +1164,12 @@ where
// Write to testcase // Write to testcase
if self.uses_shmem_testcase { if self.uses_shmem_testcase {
debug_assert!(
self.map.is_some(),
"The uses_shmem_testcase bool can only exist when a map is set"
);
// # SAFETY
// Struct can never be created when uses_shmem_testcase is true and map is none.
let map = unsafe { self.map.as_mut().unwrap_unchecked() }; let map = unsafe { self.map.as_mut().unwrap_unchecked() };
let target_bytes = input.target_bytes(); let target_bytes = input.target_bytes();
let mut size = target_bytes.as_slice().len(); let mut size = target_bytes.as_slice().len();
@ -1120,7 +1193,7 @@ where
let send_len = self let send_len = self
.forkserver .forkserver
.write_ctl(self.forkserver().last_run_timed_out())?; .write_ctl(self.forkserver().last_run_timed_out_raw())?;
if send_len != 4 { if send_len != 4 {
return Err(Error::illegal_state( return Err(Error::illegal_state(
"Unable to request new process from fork server (OOM?)".to_string(), "Unable to request new process from fork server (OOM?)".to_string(),
@ -1170,7 +1243,7 @@ where
} }
} }
self.forkserver.set_child_pid(Pid::from_raw(0)); self.forkserver.reset_child_pid();
// Clear the observer map after the execution is finished // Clear the observer map after the execution is finished
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);