Skip to content

Commit 134fe6a

Browse files
domenukkmvanotti
andauthored
ForkserverExecutor: stop forked children on exit (#1493)
* wip * Fix forkserver exit * undo change in forkserver_simple * less map_err --------- Co-authored-by: Marco Vanotti <[email protected]>
1 parent d0d378c commit 134fe6a

File tree

1 file changed

+94
-21
lines changed

1 file changed

+94
-21
lines changed

libafl/src/executors/forkserver.rs

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::{
1212
io::{self, prelude::*, ErrorKind},
1313
os::unix::{io::RawFd, process::CommandExt},
1414
path::Path,
15-
process::{Command, Stdio},
15+
process::{Child, Command, Stdio},
1616
};
1717

1818
use libafl_bolts::{
@@ -27,6 +27,7 @@ use nix::{
2727
select::{pselect, FdSet},
2828
signal::{kill, SigSet, Signal},
2929
time::{TimeSpec, TimeValLike},
30+
wait::waitpid,
3031
},
3132
unistd::Pid,
3233
};
@@ -150,6 +151,9 @@ impl ConfigTarget for Command {
150151
if memlimit == 0 {
151152
return self;
152153
}
154+
// SAFETY
155+
// This method does not do shady pointer foo.
156+
// It merely call libc functions.
153157
let func = move || {
154158
let memlimit: libc::rlim_t = (memlimit as libc::rlim_t) << 20;
155159
let r = libc::rlimit {
@@ -174,6 +178,8 @@ impl ConfigTarget for Command {
174178
}
175179
Ok(())
176180
};
181+
// # SAFETY
182+
// This calls our non-shady function from above.
177183
unsafe { self.pre_exec(func) }
178184
}
179185
}
@@ -182,13 +188,40 @@ impl ConfigTarget for Command {
182188
/// The communication happens via pipe.
183189
#[derive(Debug)]
184190
pub struct Forkserver {
191+
/// The "actual" forkserver we spawned in the target
192+
fsrv_handle: Child,
193+
/// Status pipe
185194
st_pipe: Pipe,
195+
/// Control pipe
186196
ctl_pipe: Pipe,
187-
child_pid: Pid,
197+
/// Pid of the current forked child (child of the forkserver) during execution
198+
child_pid: Option<Pid>,
199+
/// The last status reported to us by the in-target forkserver
188200
status: i32,
201+
/// If the last run timed out (in in-target i32)
189202
last_run_timed_out: i32,
190203
}
191204

205+
impl Drop for Forkserver {
206+
fn drop(&mut self) {
207+
if let Err(err) = self.fsrv_handle.kill() {
208+
log::warn!("Failed kill forkserver: {err}",);
209+
}
210+
if let Some(pid) = self.child_pid {
211+
if let Err(err) = kill(pid, Signal::SIGKILL) {
212+
log::warn!(
213+
"Failed to deliver kill signal to child process {}: {err} ({})",
214+
pid,
215+
io::Error::last_os_error()
216+
);
217+
}
218+
if let Err(err) = waitpid(pid, None) {
219+
log::warn!("Failed to wait for child pid ({pid}): {err}",);
220+
}
221+
}
222+
}
223+
}
224+
192225
#[allow(clippy::fn_params_excessive_bools)]
193226
impl Forkserver {
194227
/// Create a new [`Forkserver`]
@@ -234,7 +267,7 @@ impl Forkserver {
234267
#[cfg(feature = "regex")]
235268
command.env("ASAN_OPTIONS", get_asan_runtime_flags_with_log_path());
236269

237-
match command
270+
let fsrv_handle = match command
238271
.env("LD_BIND_NOW", "1")
239272
.envs(envs)
240273
.setlimit(memlimit)
@@ -248,7 +281,7 @@ impl Forkserver {
248281
)
249282
.spawn()
250283
{
251-
Ok(_) => (),
284+
Ok(fsrv_handle) => fsrv_handle,
252285
Err(err) => {
253286
return Err(Error::illegal_state(format!(
254287
"Could not spawn the forkserver: {err:#?}"
@@ -261,25 +294,39 @@ impl Forkserver {
261294
st_pipe.close_write_end();
262295

263296
Ok(Self {
297+
fsrv_handle,
264298
st_pipe,
265299
ctl_pipe,
266-
child_pid: Pid::from_raw(0),
300+
child_pid: None,
267301
status: 0,
268302
last_run_timed_out: 0,
269303
})
270304
}
271305

272-
/// If the last run timed out
306+
/// If the last run timed out (as in-target i32)
273307
#[must_use]
274-
pub fn last_run_timed_out(&self) -> i32 {
308+
pub fn last_run_timed_out_raw(&self) -> i32 {
275309
self.last_run_timed_out
276310
}
277311

278-
/// Sets if the last run timed out
279-
pub fn set_last_run_timed_out(&mut self, last_run_timed_out: i32) {
312+
/// If the last run timed out
313+
#[must_use]
314+
pub fn last_run_timed_out(&self) -> bool {
315+
self.last_run_timed_out_raw() != 0
316+
}
317+
318+
/// Sets if the last run timed out (as in-target i32)
319+
#[inline]
320+
pub fn set_last_run_timed_out_raw(&mut self, last_run_timed_out: i32) {
280321
self.last_run_timed_out = last_run_timed_out;
281322
}
282323

324+
/// Sets if the last run timed out
325+
#[inline]
326+
pub fn set_last_run_timed_out(&mut self, last_run_timed_out: bool) {
327+
self.last_run_timed_out = i32::from(last_run_timed_out);
328+
}
329+
283330
/// The status
284331
#[must_use]
285332
pub fn status(&self) -> i32 {
@@ -294,12 +341,17 @@ impl Forkserver {
294341
/// The child pid
295342
#[must_use]
296343
pub fn child_pid(&self) -> Pid {
297-
self.child_pid
344+
self.child_pid.unwrap()
298345
}
299346

300347
/// Set the child pid
301348
pub fn set_child_pid(&mut self, child_pid: Pid) {
302-
self.child_pid = child_pid;
349+
self.child_pid = Some(child_pid);
350+
}
351+
352+
/// Remove the child pid.
353+
pub fn reset_child_pid(&mut self) {
354+
self.child_pid = None;
303355
}
304356

305357
/// Read from the st pipe
@@ -433,9 +485,15 @@ where
433485
) -> Result<ExitKind, Error> {
434486
let mut exit_kind = ExitKind::Ok;
435487

436-
let last_run_timed_out = self.executor.forkserver().last_run_timed_out();
488+
let last_run_timed_out = self.executor.forkserver().last_run_timed_out_raw();
437489

438490
if self.executor.uses_shmem_testcase() {
491+
debug_assert!(
492+
self.executor.shmem_mut().is_some(),
493+
"The uses_shmem_testcase() bool can only exist when a map is set"
494+
);
495+
// # SAFETY
496+
// Struct can never be created when uses_shmem_testcase is true and map is none.
439497
let map = unsafe { self.executor.shmem_mut().as_mut().unwrap_unchecked() };
440498
let target_bytes = input.target_bytes();
441499
let mut size = target_bytes.as_slice().len();
@@ -461,7 +519,7 @@ where
461519
.forkserver_mut()
462520
.write_ctl(last_run_timed_out)?;
463521

464-
self.executor.forkserver_mut().set_last_run_timed_out(0);
522+
self.executor.forkserver_mut().set_last_run_timed_out(false);
465523

466524
if send_len != 4 {
467525
return Err(Error::unknown(
@@ -503,21 +561,18 @@ where
503561
}
504562
}
505563
} else {
506-
self.executor.forkserver_mut().set_last_run_timed_out(1);
564+
self.executor.forkserver_mut().set_last_run_timed_out(true);
507565

508566
// 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()?
509-
let _: Result<(), nix::errno::Errno> =
510-
kill(self.executor.forkserver().child_pid(), self.signal);
567+
let _ = kill(self.executor.forkserver().child_pid(), self.signal);
511568
let (recv_status_len, _) = self.executor.forkserver_mut().read_st()?;
512569
if recv_status_len != 4 {
513570
return Err(Error::unknown("Could not kill timed-out child".to_string()));
514571
}
515572
exit_kind = ExitKind::Timeout;
516573
}
517574

518-
self.executor
519-
.forkserver_mut()
520-
.set_child_pid(Pid::from_raw(0));
575+
self.executor.forkserver_mut().reset_child_pid();
521576

522577
Ok(exit_kind)
523578
}
@@ -645,6 +700,12 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> {
645700
self.use_stdin
646701
);
647702

703+
if self.uses_shmem_testcase && map.is_none() {
704+
return Err(Error::illegal_state(
705+
"Map must always be set for `uses_shmem_testcase`",
706+
));
707+
}
708+
648709
Ok(ForkserverExecutor {
649710
target,
650711
args: self.arguments.clone(),
@@ -690,6 +751,12 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> {
690751

691752
let observers: (MO, OT) = other_observers.prepend(map_observer);
692753

754+
if self.uses_shmem_testcase && map.is_none() {
755+
return Err(Error::illegal_state(
756+
"Map must always be set for `uses_shmem_testcase`",
757+
));
758+
}
759+
693760
Ok(ForkserverExecutor {
694761
target,
695762
args: self.arguments.clone(),
@@ -1097,6 +1164,12 @@ where
10971164

10981165
// Write to testcase
10991166
if self.uses_shmem_testcase {
1167+
debug_assert!(
1168+
self.map.is_some(),
1169+
"The uses_shmem_testcase bool can only exist when a map is set"
1170+
);
1171+
// # SAFETY
1172+
// Struct can never be created when uses_shmem_testcase is true and map is none.
11001173
let map = unsafe { self.map.as_mut().unwrap_unchecked() };
11011174
let target_bytes = input.target_bytes();
11021175
let mut size = target_bytes.as_slice().len();
@@ -1120,7 +1193,7 @@ where
11201193

11211194
let send_len = self
11221195
.forkserver
1123-
.write_ctl(self.forkserver().last_run_timed_out())?;
1196+
.write_ctl(self.forkserver().last_run_timed_out_raw())?;
11241197
if send_len != 4 {
11251198
return Err(Error::illegal_state(
11261199
"Unable to request new process from fork server (OOM?)".to_string(),
@@ -1170,7 +1243,7 @@ where
11701243
}
11711244
}
11721245

1173-
self.forkserver.set_child_pid(Pid::from_raw(0));
1246+
self.forkserver.reset_child_pid();
11741247

11751248
// Clear the observer map after the execution is finished
11761249
compiler_fence(Ordering::SeqCst);

0 commit comments

Comments
 (0)