Forkserver builder (#523)

* Builder for ForkserverExecutor

* add

* clippy warnings

* comment

* stash

* tmp

* change

* revert

* use_shmem_feature field

* change the harness back

* wip

* wip

* revert

* works

* clippy

* Makefile fix

* doc

* clippy

* rename to program

* rename, fix, envs

* lifetime

* arg_input_file

* bug fix

* arg_input_file

* builder()

* doc

* clippy & fmt

* clippy & fmt
This commit is contained in:
Dongjia Zhang 2022-02-10 06:07:15 +09:00 committed by GitHub
parent 63d89463a3
commit 9482433e54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 232 additions and 115 deletions

View File

@ -1,5 +1,6 @@
FUZZER_NAME="forkserver_simple"
PROJECT_DIR=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
CORPUS_DIR="corpus"
PHONY: all
@ -17,7 +18,7 @@ run: all
short_test: all
rm -rf libafl_unix_shmem_server || true
timeout 10s taskset -c 0 ./$(FUZZER_NAME) 2>/dev/null &
timeout 10s taskset -c 0 ./$(FUZZER_NAME) $(PROJECT_DIR)/target/release/program ./$(CORPUS_DIR) @@ 2>/dev/null &
test: all
timeout 60s taskset -c 0 ./$(FUZZER_NAME) 2>/dev/null &
timeout 60s taskset -c 0 ./$(FUZZER_NAME) $(PROJECT_DIR)/target/release/program ./$(CORPUS_DIR) @@ 2>/dev/null &

View File

@ -145,15 +145,16 @@ pub fn main() {
None => [].to_vec(),
};
let forkserver = ForkserverExecutor::builder()
.program(res.value_of("executable").unwrap().to_string())
.args(&args)
.debug_child(debug_child)
.shmem_provider(&mut shmem_provider)
.build(tuple_list!(time_observer, edges_observer))
.unwrap();
let mut executor = TimeoutForkserverExecutor::new(
ForkserverExecutor::with_shmem_inputs(
res.value_of("executable").unwrap().to_string(),
&args,
tuple_list!(edges_observer, time_observer),
debug_child,
&mut shmem_provider,
)
.unwrap(),
forkserver,
Duration::from_millis(
res.value_of("timeout")
.unwrap()

View File

@ -6,8 +6,10 @@ use core::{
time::Duration,
};
use std::{
ffi::{OsStr, OsString},
io::{self, prelude::*, ErrorKind},
os::unix::{io::RawFd, process::CommandExt},
path::Path,
process::{Command, Stdio},
};
@ -166,8 +168,9 @@ pub struct Forkserver {
impl Forkserver {
/// Create a new [`Forkserver`]
pub fn new(
target: String,
args: Vec<String>,
target: OsString,
args: Vec<OsString>,
envs: Vec<(OsString, OsString)>,
out_filefd: RawFd,
use_stdin: bool,
memlimit: u64,
@ -189,6 +192,7 @@ impl Forkserver {
.stderr(stderr)
.env("LD_BIND_LAZY", "1")
.env("ASAN_OPTIONS", get_asan_runtime_flags_with_log_path())
.envs(envs)
.setlimit(memlimit)
.setsid()
.setstdin(out_filefd, use_stdin)
@ -460,12 +464,11 @@ where
/// Please refer to AFL++'s docs. <https://github.com/AFLplusplus/AFLplusplus/blob/stable/instrumentation/README.persistent_mode.md>
pub struct ForkserverExecutor<I, OT, S, SP>
where
I: Input + HasTargetBytes,
OT: ObserversTuple<I, S>,
OT: Debug,
SP: ShMemProvider,
{
target: String,
args: Vec<String>,
target: OsString,
args: Vec<OsString>,
out_file: OutFile,
forkserver: Forkserver,
observers: OT,
@ -477,8 +480,7 @@ where
impl<I, OT, S, SP> Debug for ForkserverExecutor<I, OT, S, SP>
where
I: Input + HasTargetBytes,
OT: ObserversTuple<I, S>,
OT: Debug,
SP: ShMemProvider,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
@ -493,21 +495,11 @@ where
}
}
impl<I, OT, S> ForkserverExecutor<I, OT, S, StdShMemProvider>
where
I: Input + HasTargetBytes,
OT: ObserversTuple<I, S>,
{
/// Creates a new `AFL`-style [`ForkserverExecutor`] with the given target, arguments and observers.
/// This Forserver won't attempt to provide inputs over shared mem but write them to an iput file
/// If `debug_child` is set, the child will print to `stdout`/`stderr`.
pub fn new(
target: String,
arguments: &[String],
observers: OT,
debug_child: bool,
) -> Result<Self, Error> {
Self::new_internal(target, arguments, observers, debug_child, None)
impl ForkserverExecutor<(), (), (), StdShMemProvider> {
/// Builder for `ForkserverExecutor`
#[must_use]
pub fn builder() -> ForkserverExecutorBuilder<'static, StdShMemProvider> {
ForkserverExecutorBuilder::new()
}
}
@ -517,47 +509,68 @@ where
OT: ObserversTuple<I, S>,
SP: ShMemProvider,
{
/// Creates a new [`ForkserverExecutor`] with the given target, arguments and observers.
pub fn with_shmem_inputs(
target: String,
arguments: &[String],
observers: OT,
debug_child: bool,
shmem_provider: &mut SP,
) -> Result<Self, Error> {
Self::new_internal(
target,
arguments,
observers,
debug_child,
Some(shmem_provider),
)
/// The `target` binary that's going to run.
pub fn target(&self) -> &OsString {
&self.target
}
/// Creates a new [`ForkserverExecutor`] with the given target, arguments and observers, with debug mode
fn new_internal(
target: String,
arguments: &[String],
observers: OT,
debug_child: bool,
shmem_provider: Option<&mut SP>,
) -> Result<Self, Error> {
let mut args = Vec::<String>::new();
let mut use_stdin = true;
let out_filename = ".cur_input".to_string();
/// The `args` used for the binary.
pub fn args(&self) -> &[OsString] {
&self.args
}
for item in arguments {
/// The [`Forkserver`] instance.
pub fn forkserver(&self) -> &Forkserver {
&self.forkserver
}
/// The [`OutFile`] used by this [`Executor`].
pub fn out_file(&self) -> &OutFile {
&self.out_file
}
}
/// The builder for `ForkserverExecutor`
#[derive(Debug)]
pub struct ForkserverExecutorBuilder<'a, SP> {
program: Option<OsString>,
arguments: Vec<OsString>,
envs: Vec<(OsString, OsString)>,
out_filename: Option<OsString>,
debug_child: bool,
shmem_provider: Option<&'a mut SP>,
}
impl<'a, SP> ForkserverExecutorBuilder<'a, SP> {
/// Builds `ForkserverExecutor`.
pub fn build<I, OT, S>(
&mut self,
observers: OT,
) -> Result<ForkserverExecutor<I, OT, S, SP>, Error>
where
I: Input + HasTargetBytes,
OT: ObserversTuple<I, S>,
SP: ShMemProvider,
{
let mut args = Vec::<OsString>::new();
let mut use_stdin = true;
let out_filename = match &self.out_filename {
Some(name) => name.clone(),
None => OsString::from(".cur_input"),
};
for item in &self.arguments {
if item == "@@" && use_stdin {
use_stdin = false;
args.push(out_filename.clone());
} else {
args.push(item.to_string());
args.push(item.clone());
}
}
let out_file = OutFile::create(&out_filename)?;
let map = match shmem_provider {
let map = match &mut self.shmem_provider {
None => None,
Some(provider) => {
// setup shared memory
@ -570,15 +583,27 @@ where
}
};
let mut forkserver = Forkserver::new(
target.clone(),
let (target, mut forkserver) = match &self.program {
Some(t) => {
let forkserver = Forkserver::new(
t.clone(),
args.clone(),
self.envs.clone(),
out_file.as_raw_fd(),
use_stdin,
0,
debug_child,
self.debug_child,
)?;
(t.clone(), forkserver)
}
None => {
return Err(Error::IllegalArgument(
"ForkserverExecutorBuilder::build: target file not found".to_string(),
))
}
};
let (rlen, status) = forkserver.read_st()?; // Initial handshake, read 4-bytes hello message from the forkserver.
if rlen != 4 {
@ -604,8 +629,7 @@ where
println!("Forkserver Options are not available.");
}
Ok(Self {
has_asan_observer: None, // initialized on first use
Ok(ForkserverExecutor {
target,
args,
out_file,
@ -613,27 +637,120 @@ where
observers,
map,
phantom: PhantomData,
has_asan_observer: None, // initialized on first use
})
}
}
/// The `target` binary that's going to run.
pub fn target(&self) -> &String {
&self.target
impl<'a> ForkserverExecutorBuilder<'a, StdShMemProvider> {
/// Creates a new `AFL`-style [`ForkserverExecutor`] with the given target, arguments and observers.
/// This is the builder for `ForkserverExecutor`
/// This Forkserver will attempt to provide inputs over shared mem when `shmem_provider` is given.
/// Else this forkserver will try to write the input to `.cur_input` file.
/// If `debug_child` is set, the child will print to `stdout`/`stderr`.
#[must_use]
pub fn new() -> ForkserverExecutorBuilder<'a, StdShMemProvider> {
ForkserverExecutorBuilder {
program: None,
arguments: vec![],
envs: vec![],
out_filename: None,
debug_child: false,
shmem_provider: None,
}
}
/// The `args` used for the binary.
pub fn args(&self) -> &[String] {
&self.args
/// The harness
#[must_use]
pub fn program<O>(mut self, target: O) -> Self
where
O: AsRef<OsStr>,
{
self.program = Some(target.as_ref().to_owned());
self
}
/// The [`Forkserver`] instance.
pub fn forkserver(&self) -> &Forkserver {
&self.forkserver
/// Adds an argument to the harness's commandline
#[must_use]
pub fn arg<O>(mut self, arg: O) -> Self
where
O: AsRef<OsStr>,
{
self.arguments.push(arg.as_ref().to_owned());
self
}
/// The [`OutFile`] used by this [`Executor`].
pub fn out_file(&self) -> &OutFile {
&self.out_file
/// Adds arguments to the harness's commandline
#[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
}
/// Adds an environmental var to the harness's commandline
#[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
}
/// Adds environmental vars to the harness's commandline
#[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]
/// Place the input at this position and set the filename for the input.
pub fn arg_input_file<P: AsRef<Path>>(self, path: P) -> Self {
let mut moved = self.arg(path.as_ref());
moved.out_filename = Some(path.as_ref().as_os_str().to_os_string());
moved
}
#[must_use]
/// If `debug_child` is set, the child will print to `stdout`/`stderr`.
pub fn debug_child(mut self, debug_child: bool) -> Self {
self.debug_child = debug_child;
self
}
/// Shmem provider for forkserver's shared memory testcase feature.
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,
out_filename: self.out_filename,
debug_child: self.debug_child,
shmem_provider: Some(shmem_provider),
}
}
}
@ -799,25 +916,25 @@ where
#[cfg(test)]
mod tests {
use serial_test::serial;
use crate::{
bolts::{
shmem::{ShMem, ShMemProvider, StdShMemProvider},
tuples::tuple_list,
AsMutSlice,
},
executors::ForkserverExecutor,
executors::forkserver::ForkserverExecutorBuilder,
inputs::NopInput,
observers::{ConstMapObserver, HitcountsMapObserver},
Error,
};
use serial_test::serial;
use std::ffi::OsString;
#[test]
#[serial]
fn test_forkserver() {
const MAP_SIZE: usize = 65536;
let bin = "echo";
let args = vec![String::from("@@")];
let bin = OsString::from("echo");
let args = vec![OsString::from("@@")];
let mut shmem_provider = StdShMemProvider::new().unwrap();
@ -830,15 +947,14 @@ mod tests {
shmem_buf,
));
let executor = ForkserverExecutor::<NopInput, _, (), _>::with_shmem_inputs(
bin.to_string(),
&args,
tuple_list!(edges_observer),
false,
&mut shmem_provider,
);
// Since /usr/bin/echo is not a instrumented binary file, the test will just check if the forkserver has failed at the initial handshake
let executor = ForkserverExecutorBuilder::new()
.program(bin)
.args(&args)
.debug_child(false)
.shmem_provider(&mut shmem_provider)
.build::<NopInput, _, ()>(tuple_list!(edges_observer));
// Since /usr/bin/echo is not a instrumented binary file, the test will just check if the forkserver has failed at the initial handshake
let result = match executor {
Ok(_) => true,
Err(e) => match e {

View File

@ -18,7 +18,7 @@ use libafl::{
QueueCorpusScheduler,
},
events::{EventConfig, EventRestarter, LlmpRestartingEventManager},
executors::{ForkserverExecutor, TimeoutForkserverExecutor},
executors::{forkserver::ForkserverExecutorBuilder, TimeoutForkserverExecutor},
feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer},
@ -177,25 +177,24 @@ impl<'a, const MAP_SIZE: usize> ForkserverBytesCoverageSugar<'a, MAP_SIZE> {
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
let forkserver = if self.shmem_testcase {
ForkserverExecutorBuilder::new()
.program(self.program.clone())
.args(self.arguments)
.debug_child(self.debug_output)
.shmem_provider(&mut shmem_provider_client)
.build(tuple_list!(edges_observer, time_observer))
} else {
ForkserverExecutorBuilder::new()
.program(self.program.clone())
.args(self.arguments)
.debug_child(self.debug_output)
.build(tuple_list!(edges_observer, time_observer))
};
// Create the executor for an in-process function with one observer for edge coverage and one for the execution time
let mut executor = TimeoutForkserverExecutor::new(
if self.shmem_testcase {
ForkserverExecutor::with_shmem_inputs(
self.program.clone(),
self.arguments,
tuple_list!(edges_observer, time_observer),
self.debug_output,
&mut shmem_provider_client,
)
} else {
ForkserverExecutor::new(
self.program.clone(),
self.arguments,
tuple_list!(edges_observer, time_observer),
self.debug_output,
)
}
.expect("Failed to create the executor."),
forkserver.expect("Failed to create the executor."),
timeout,
)
.expect("Failed to create the executor.");