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:
parent
d0d378c174
commit
134fe6a992
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user