Support capturing stdout/stderr for ForkserverExecutor and code clean (#3216)

* Support capture stdout/stderr for ForkserverExecutor

Reduce code duplication for ForkserverExecutor and CommandExecutor

* use memfd_create from nix for macos and remove debug print

* resolve macos issue

* clippy

* fix macos again

* fix docs

* fix imports

* format code

* fix docs again

* fix sample

* fix another wrong import

* restore cargo.lock

* add an inner for target args

* fix and docs

* fix

* rename to ChildArgs and ChildArgsInner

* revert forkserver_simple

* allow debug child with observers

* fmt

* std marker

* fix

* move implementation to observers

* implement serde

* Add a forkserver_capture_stdout

* renaming

* fix

* fmt

* fix CommandExecutor

* add a test to check capture

* fix imports

* clippy

* fix sample

* update sample to make it closer to real usecase

* also CommandExecutor for sample

* format

* add forkserver_capture_stdout to CI

* fix doc

* accidentally remove

* fix non_std

* fix for windows

* remove useless lint

* remove spurious fuzzer

* fix for windows again

* fix imports

* fix doc sample

* fix docs

* fix sample

* fmt

* clippy

* clippy again

* fix msrv

* have cargo.lock for sample fuzzer

* avoid double read

* fix fsrv and cmd

* fix sample

* fix docs for windows

* fix typo

* clippy again

* fix exec

* typo

* clippy

* update

* fix nyx executor

* cliipy

* fmt again

* last clippy

* clippy
This commit is contained in:
lazymio 2025-05-13 22:08:27 +08:00 committed by GitHub
parent 2dbf636201
commit f901c2085d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 2331 additions and 386 deletions

View File

@ -280,6 +280,7 @@ jobs:
# Forkserver
- ./fuzzers/forkserver/forkserver_simple
- ./fuzzers/forkserver/forkserver_capture_stdout
- ./fuzzers/forkserver/forkserver_libafl_cc
- ./fuzzers/forkserver/fuzzbench_forkserver
- ./fuzzers/forkserver/fuzzbench_forkserver_cmplog

View File

@ -117,8 +117,11 @@ pub fn main() {
}
let timeout = Duration::from_secs(5);
let mut executor =
MyExecutor { shmem_id, timeout }.into_executor(tuple_list!(observer, bt_observer));
let mut executor = MyExecutor { shmem_id, timeout }.into_executor(
tuple_list!(observer, bt_observer),
None,
None,
);
// Generator of printable bytearrays of max size 32
let mut generator = RandPrintablesGenerator::new(nonzero!(32));

View File

@ -25,7 +25,7 @@ use libafl_bolts::{
rands::StdRand,
shmem::{ShMem, ShMemProvider},
tuples::tuple_list,
AsSliceMut, TargetArgs,
AsSliceMut, StdTargetArgs,
};
pub fn main() {

View File

@ -136,6 +136,8 @@ pub fn main() {
command_configurator,
tuple_list!(observer),
tuple_list!(hook),
None,
None,
);
// Generator of printable bytearrays of max size 32

View File

@ -0,0 +1 @@
forkserver_capture_stdout

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
[package]
name = "forkserver_capture_stdout"
version = "0.14.1"
authors = ["tokatoka <tokazerkje@outlook.com>", "Ziqiao Kong <mio@lazym.io>"]
edition = "2024"
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
lto = true
codegen-units = 1
opt-level = 3
[dependencies]
clap = { version = "4.5.18", features = ["derive"] }
env_logger = "0.11.5"
libafl = { path = "../../../libafl", features = ["std", "derive"] }
libafl_bolts = { path = "../../../libafl_bolts" }
log = { version = "0.4.22", features = ["release_max_level_info"] }
nix = { version = "0.29.0", features = ["signal"] }
serde = "1.0.219"
serde_json = "1.0.140"

View File

@ -0,0 +1,14 @@
# Simple Forkserver Fuzzer to Capture Stdout
This is a simple example fuzzer to showcase how to capture stdout(stderr) from `ForkserverExecutor`.
## Usage
You can build this example by `cargo build --release`.
This downloads AFLplusplus/AFLplusplus and compiles the example harness program in src/program.c with afl-cc
## Run
After you build it you can run
`cp ./target/release/forkserver_capture_stdout .` to copy the fuzzer into this directory,
and you can run
`taskset -c 1 ./forkserver_capture_stdout ./target/release/program ./corpus/ -t 1000` to run the fuzzer.
`taskset` binds this process to a specific core to improve the throughput.

View File

@ -0,0 +1,61 @@
use std::{
env,
path::Path,
process::{Command, exit},
};
const AFL_URL: &str = "https://github.com/AFLplusplus/AFLplusplus";
fn main() {
if cfg!(windows) {
println!("cargo:warning=No support for windows yet.");
exit(0);
}
unsafe {
env::remove_var("DEBUG");
}
let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
let afl = format!("{}/AFLplusplus", &cwd);
let afl_cc = format!("{}/AFLplusplus/afl-cc", &cwd);
let afl_path = Path::new(&afl);
let afl_cc_path = Path::new(&afl_cc);
if !afl_path.is_dir() {
println!("cargo:warning=AFL++ not found, downloading...");
Command::new("git")
.arg("clone")
.arg(AFL_URL)
.status()
.unwrap();
}
if !afl_cc_path.is_file() {
let mut afl_cc_make = Command::new("make");
afl_cc_make.arg("all").current_dir(afl_path);
if let Ok(llvm_config) = env::var("LLVM_CONFIG") {
if !llvm_config.is_empty() {
afl_cc_make.env("LLVM_CONFIG", llvm_config);
}
}
afl_cc_make.status().unwrap();
}
let mut compile_command = Command::new(afl_cc_path);
compile_command
.args(["src/program.c", "-o"])
.arg(format!("{cwd}/target/release/program"));
if let Ok(llvm_config) = env::var("LLVM_CONFIG") {
if !llvm_config.is_empty() {
compile_command.env("LLVM_CONFIG", llvm_config);
}
}
compile_command.status().unwrap();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=src/");
}

View File

@ -0,0 +1,261 @@
use core::time::Duration;
use std::{ops::Index, path::PathBuf};
use clap::Parser;
use libafl::{
HasMetadata,
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
events::SimpleEventManager,
executors::{
CommandExecutor, DiffExecutor, HasObservers, StdChildArgs, forkserver::ForkserverExecutor,
},
feedback_and_fast, feedback_or,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
fuzzer::{Fuzzer, StdFuzzer},
inputs::BytesInput,
monitors::SimpleMonitor,
mutators::{HavocScheduledMutator, Tokens, havoc_mutations, tokens_mutations},
observers::{CanTrack, HitcountsMapObserver, StdMapObserver, StdOutObserver, TimeObserver},
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
stages::mutational::StdMutationalStage,
state::{HasCorpus, StdState},
};
use libafl_bolts::{
AsSliceMut, StdTargetArgs, Truncate, current_nanos,
rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{Handled, Merge, tuple_list},
};
use nix::sys::signal::Signal;
use serde::Deserialize;
/// The commandline args this fuzzer accepts
#[derive(Debug, Parser)]
#[command(
name = "forkserver_simple",
about = "This is a simple example fuzzer to fuzz a executable instrumented by afl-cc.",
author = "tokatoka <tokazerkje@outlook.com>"
)]
struct Opt {
#[arg(
help = "The instrumented binary we want to fuzz",
name = "EXEC",
required = true
)]
executable: String,
#[arg(
help = "The directory to read initial inputs from ('seeds')",
name = "INPUT_DIR",
required = true
)]
in_dir: PathBuf,
#[arg(
help = "Timeout for each individual execution, in milliseconds",
short = 't',
long = "timeout",
default_value = "1200"
)]
timeout: u64,
#[arg(
help = "If not set, the child's stdout and stderror will be redirected to /dev/null",
short = 'd',
long = "debug-child",
default_value = "false"
)]
debug_child: bool,
#[arg(
help = "Arguments passed to the target",
name = "arguments",
num_args(1..),
allow_hyphen_values = true,
)]
arguments: Vec<String>,
#[arg(
help = "Signal used to stop child",
short = 's',
long = "signal",
value_parser = str::parse::<Signal>,
default_value = "SIGKILL"
)]
signal: Signal,
}
#[derive(Debug, Clone, Deserialize)]
struct ProgramOutput {
len: i32,
}
pub fn main() {
env_logger::init();
const MAP_SIZE: usize = 65536;
let opt = Opt::parse();
let corpus_dirs: Vec<PathBuf> = [opt.in_dir].to_vec();
// The unix shmem provider supported by AFL++ for shared memory
let mut shmem_provider = UnixShMemProvider::new().unwrap();
// The coverage map shared between observer and executor
let mut shmem = shmem_provider.new_shmem(MAP_SIZE).unwrap();
// let the forkserver know the shmid
unsafe {
shmem.write_to_env("__AFL_SHM_ID").unwrap();
}
let shmem_buf = shmem.as_slice_mut();
// Create an observation channel using the signals map
let edges_observer = unsafe {
HitcountsMapObserver::new(StdMapObserver::new("shared_mem", shmem_buf)).track_indices()
};
// Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time");
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
let mut feedback = feedback_or!(
// New maximization map feedback linked to the edges observer and the feedback state
MaxMapFeedback::new(&edges_observer),
// Time feedback, this one does not need a feedback state
TimeFeedback::new(&time_observer)
);
// A feedback to choose if an input is a solution or not
// We want to do the same crash deduplication that AFL does
let mut objective = feedback_and_fast!(
// Must be a crash
CrashFeedback::new(),
// Take it only if trigger new coverage over crashes
// Uses `with_name` to create a different history from the `MaxMapFeedback` in `feedback` above
MaxMapFeedback::with_name("mapfeedback_metadata_objective", &edges_observer)
);
// create a State from scratch
let mut state = StdState::new(
// RNG
StdRand::with_seed(current_nanos()),
// Corpus that will be evolved, we keep it in memory for performance
InMemoryCorpus::<BytesInput>::new(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(),
// States of the feedbacks.
// The feedbacks can report the data that should persist in the State.
&mut feedback,
// Same for objective feedbacks
&mut objective,
)
.unwrap();
// The Monitor trait define how the fuzzer stats are reported to the user
let monitor = SimpleMonitor::new(|s| println!("{s}"));
// The event manager handle the various events generated during the fuzzing loop
// such as the notification of the addition of a new item to the corpus
let mut mgr = SimpleEventManager::new(monitor);
// A minimization+queue policy to get testcasess from the corpus
let scheduler = IndexesLenTimeMinimizerScheduler::new(&edges_observer, QueueScheduler::new());
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// If we should debug the child
let debug_child = opt.debug_child;
// Create the executor for the forkserver
let args = opt.arguments;
let observer_ref = edges_observer.handle();
let stdout = StdOutObserver::new("stdout".into()).expect("observer");
let stdout_handle = stdout.handle();
let cmd_stdout = StdOutObserver::new("cmd_stdout".into()).expect("observer");
let cmd_stdout_handle = cmd_stdout.handle();
let mut tokens = Tokens::new();
let mut executor = ForkserverExecutor::builder()
.program(opt.executable.clone())
.debug_child(debug_child)
.shmem_provider(&mut shmem_provider)
.autotokens(&mut tokens)
.parse_afl_cmdline(args.clone())
.coverage_map_size(MAP_SIZE)
.timeout(Duration::from_millis(opt.timeout))
.kill_signal(opt.signal)
.stdout_observer(stdout_handle.clone())
.build(tuple_list!(time_observer, edges_observer, stdout))
.unwrap();
let cmd_executor = CommandExecutor::builder()
.program(opt.executable)
.debug_child(debug_child)
.parse_afl_cmdline(args)
.timeout(Duration::from_millis(opt.timeout))
.stdout_observer(cmd_stdout_handle.clone())
.build(tuple_list!(cmd_stdout))
.unwrap();
if let Some(dynamic_map_size) = executor.coverage_map_size() {
executor.observers_mut()[&observer_ref]
.as_mut()
.truncate(dynamic_map_size);
}
let mut executor = DiffExecutor::new(executor, cmd_executor, ());
// In case the corpus is empty (on first run), reset
if state.must_load_initial_inputs() {
state
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &corpus_dirs)
.unwrap_or_else(|err| {
panic!(
"Failed to load initial corpus at {:?}: {:?}",
&corpus_dirs, err
)
});
println!("We imported {} inputs from disk.", state.corpus().count());
}
state.add_metadata(tokens);
// Setup a mutational stage with a basic bytes mutator
let mutator =
HavocScheduledMutator::with_max_stack_pow(havoc_mutations().merge(tokens_mutations()), 6);
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
for _ in 0..5 {
fuzzer
.fuzz_one(&mut stages, &mut executor, &mut state, &mut mgr)
.unwrap();
let stdout = executor
.observers()
.index(&stdout_handle)
.output
.clone()
.expect("no stdout");
let out: ProgramOutput = serde_json::from_slice(&stdout).unwrap();
println!(
"Program output from Forkserver after serde_json::from_slice is {:?}",
&out
);
let cmd_stdout = executor
.observers()
.index(&cmd_stdout_handle)
.output
.clone()
.expect("no stdout");
let out: ProgramOutput = serde_json::from_slice(&stdout).unwrap();
println!(
"Program output from CommandExecutor after serde_json::from_slice is {:?}",
&out
);
}
}

View File

@ -0,0 +1,24 @@
#include <stdio.h>
// The following line is needed for shared memeory testcase fuzzing
__AFL_FUZZ_INIT();
int main(int argc, char **argv) {
FILE *file = stdin;
if (argc > 1) { file = fopen(argv[1], "rb"); }
// The following three lines are for normal fuzzing.
/*
char buf[16];
char* p = fgets(buf, 16, file);
buf[15] = 0;
*/
// The following line is also needed for shared memory testcase fuzzing
unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
int len = __AFL_FUZZ_TESTCASE_LEN;
printf("{\"len\": %d}", len);
return 0;
}

View File

@ -5,7 +5,7 @@ use clap::Parser;
use libafl::{
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
events::SimpleEventManager,
executors::{forkserver::ForkserverExecutor, HasObservers},
executors::{forkserver::ForkserverExecutor, HasObservers, StdChildArgs},
feedback_and_fast, feedback_or,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
fuzzer::{Fuzzer, StdFuzzer},
@ -22,7 +22,7 @@ use libafl_bolts::{
rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Handled, Merge},
AsSliceMut, TargetArgs, Truncate,
AsSliceMut, StdTargetArgs, Truncate,
};
use libafl_targets::EDGES_MAP_DEFAULT_SIZE;
use nix::sys::signal::Signal;

View File

@ -6,7 +6,7 @@ use libafl::{
HasMetadata,
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
events::SimpleEventManager,
executors::{HasObservers, forkserver::ForkserverExecutor},
executors::{HasObservers, StdChildArgs, forkserver::ForkserverExecutor},
feedback_and_fast, feedback_or,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
fuzzer::{Fuzzer, StdFuzzer},
@ -19,7 +19,7 @@ use libafl::{
state::{HasCorpus, StdState},
};
use libafl_bolts::{
AsSliceMut, TargetArgs, Truncate, current_nanos,
AsSliceMut, StdTargetArgs, Truncate, current_nanos,
rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{Handled, Merge, tuple_list},

View File

@ -11,7 +11,10 @@ use clap::{Arg, ArgAction, Command};
use libafl::{
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
events::SimpleEventManager,
executors::forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
executors::{
forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
StdChildArgs,
},
feedback_or,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
fuzzer::{Fuzzer, StdFuzzer},
@ -38,7 +41,7 @@ use libafl_bolts::{
rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Merge},
AsSliceMut, TargetArgs,
AsSliceMut, StdTargetArgs,
};
use libafl_targets::cmps::AFLppCmpLogMap;
use nix::sys::signal::Signal;

View File

@ -11,7 +11,10 @@ use clap::{Arg, ArgAction, Command};
use libafl::{
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
events::SimpleEventManager,
executors::forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
executors::{
forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
StdChildArgs,
},
feedback_or,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
fuzzer::{Fuzzer, StdFuzzer},
@ -37,7 +40,7 @@ use libafl_bolts::{
rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Handled, Merge},
AsSliceMut, TargetArgs,
AsSliceMut, StdTargetArgs,
};
use libafl_targets::{
cmps::{observers::AFLppCmpLogObserver, stages::AFLppCmplogTracingStage},

View File

@ -14,6 +14,7 @@ use libafl::{
executors::{
forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
sand::SANDExecutor,
StdChildArgs,
},
feedback_or,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
@ -41,7 +42,7 @@ use libafl_bolts::{
rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Handled, Merge},
AsSliceMut, TargetArgs,
AsSliceMut, StdTargetArgs,
};
use libafl_targets::cmps::AFLppCmpLogMap;
use nix::sys::signal::Signal;

View File

@ -16,7 +16,10 @@ use libafl::monitors::SimpleMonitor;
use libafl::{
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
events::ProgressReporter,
executors::forkserver::{ForkserverExecutor, ForkserverExecutorBuilder, SHM_CMPLOG_ENV_VAR},
executors::{
forkserver::{ForkserverExecutor, ForkserverExecutorBuilder, SHM_CMPLOG_ENV_VAR},
StdChildArgs,
},
feedback_and, feedback_or, feedback_or_fast,
feedbacks::{
CaptureTimeoutFeedback, ConstFeedback, CrashFeedback, MaxMapFeedback, TimeFeedback,
@ -51,7 +54,7 @@ use libafl_bolts::{
rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Handled, Merge},
AsSliceMut, TargetArgs,
AsSliceMut, StdTargetArgs,
};
#[cfg(feature = "nyx")]
use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings};

View File

@ -5,7 +5,7 @@ use clap::Parser;
use libafl::{
corpus::{InMemoryCorpus, OnDiskCorpus},
events::SimpleEventManager,
executors::{forkserver::ForkserverExecutor, HasObservers},
executors::{forkserver::ForkserverExecutor, HasObservers, StdChildArgs},
feedback_and_fast, feedback_or,
feedbacks::{
CrashFeedback, MaxMapFeedback, NautilusChunksMetadata, NautilusFeedback, TimeFeedback,
@ -29,7 +29,7 @@ use libafl_bolts::{
rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Handled},
AsSliceMut, TargetArgs, Truncate,
AsSliceMut, StdTargetArgs, Truncate,
};
use nix::sys::signal::Signal;

View File

@ -217,9 +217,11 @@ fn fuzz(
let mut stages = tuple_list!(
// Create a concolic trace
ConcolicTracingStage::new(
TracingStage::new(
MyCommandConfigurator.into_executor(tuple_list!(concolic_observer)),
),
TracingStage::new(MyCommandConfigurator.into_executor(
tuple_list!(concolic_observer),
None,
None
),),
concolic_ref,
),
// Use the concolic trace for z3-based solving

View File

@ -533,7 +533,10 @@ mod tests {
match try_create_new(&path) {
Ok(None) => (),
Ok(_) => panic!("File {path:?} did not exist even though it should have?"),
Ok(_) => panic!(
"File {} did not exist even though it should have?",
&path.display()
),
Err(e) => panic!("An unexpected error occurred: {e}"),
}
drop(f);

View File

@ -1,6 +1,7 @@
//! The command executor executes a sub program for each run
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
use alloc::ffi::CString;
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
use alloc::vec::Vec;
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
use core::ffi::CStr;
@ -13,18 +14,17 @@ use core::{
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
use std::os::fd::AsRawFd;
use std::{
ffi::{OsStr, OsString},
ffi::OsStr,
io::{Read, Write},
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
os::{fd::RawFd, unix::ffi::OsStrExt},
process::{Child, Command, Stdio},
};
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
use libafl_bolts::core_affinity::CoreId;
use libafl_bolts::{
AsSlice, InputLocation, TargetArgs,
tuples::{Handle, MatchName, RefIndexable},
AsSlice, InputLocation, StdTargetArgs, StdTargetArgsInner,
tuples::{Handle, MatchName, MatchNameRef, RefIndexable},
};
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
use libc::STDIN_FILENO;
@ -46,7 +46,9 @@ use nix::{
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
use typed_builder::TypedBuilder;
use super::HasTimeout;
#[cfg(all(target_family = "unix", feature = "fork"))]
use super::forkserver::ConfigTarget;
use super::{HasTimeout, StdChildArgs, StdChildArgsInner};
#[cfg(target_os = "linux")]
use crate::executors::hooks::ExecutorHooksTuple;
use crate::{
@ -55,9 +57,47 @@ use crate::{
inputs::HasTargetBytes,
observers::{ObserversTuple, StdErrObserver, StdOutObserver},
state::HasExecutions,
std::borrow::ToOwned,
};
/// How do we capture stdout/stderr. Not intended for public use.
#[derive(Debug, Default)]
#[allow(dead_code)]
enum StdCommandCaptureMethod {
Fd(RawFd),
#[default]
Pipe,
}
impl StdCommandCaptureMethod {
fn pipe_capture(cmd: &mut Command, stdout: bool) {
if stdout {
cmd.stdout(Stdio::piped());
} else {
cmd.stderr(Stdio::piped());
}
}
fn pre_capture(&self, cmd: &mut Command, stdout: bool) {
#[cfg(feature = "fork")]
{
if let Self::Fd(old) = self {
if stdout {
cmd.setdup2(*old, libc::STDOUT_FILENO);
cmd.stdout(Stdio::null());
} else {
cmd.setdup2(*old, libc::STDERR_FILENO);
cmd.stderr(Stdio::null());
}
} else {
Self::pipe_capture(cmd, stdout);
}
}
#[cfg(not(feature = "fork"))]
Self::pipe_capture(cmd, stdout);
}
}
/// A simple Configurator that takes the most common parameters
/// Writes the input either to stdio or to a file
/// Use [`CommandExecutor::builder()`] to use this configurator.
@ -66,8 +106,8 @@ pub struct StdCommandConfigurator {
/// If set to true, the child output will remain visible
/// By default, the child output is hidden to increase execution speed
debug_child: bool,
stdout_observer: Option<Handle<StdOutObserver>>,
stderr_observer: Option<Handle<StdErrObserver>>,
stdout_cap: Option<StdCommandCaptureMethod>,
stderr_cap: Option<StdCommandCaptureMethod>,
timeout: Duration,
/// true: input gets delivered via stdink
input_location: InputLocation,
@ -79,30 +119,26 @@ impl<I> CommandConfigurator<I> for StdCommandConfigurator
where
I: HasTargetBytes,
{
fn stdout_observer(&self) -> Option<Handle<StdOutObserver>> {
self.stdout_observer.clone()
}
fn stderr_observer(&self) -> Option<Handle<StdErrObserver>> {
self.stderr_observer.clone()
}
fn spawn_child(&mut self, input: &I) -> Result<Child, Error> {
let mut cmd = Command::new(self.command.get_program());
match &mut self.input_location {
InputLocation::Arg { argnum } => {
let args = self.command.get_args();
let mut cmd = Command::new(self.command.get_program());
if !self.debug_child {
if self.debug_child {
cmd.stdout(Stdio::inherit());
} else if let Some(cap) = &self.stdout_cap {
cap.pre_capture(&mut cmd, true);
} else {
cmd.stdout(Stdio::null());
cmd.stderr(Stdio::null());
}
if self.stdout_observer.is_some() {
cmd.stdout(Stdio::piped());
}
if self.stderr_observer.is_some() {
cmd.stderr(Stdio::piped());
if self.debug_child {
cmd.stderr(Stdio::inherit());
} else if let Some(cap) = &self.stderr_cap {
cap.pre_capture(&mut cmd, false);
} else {
cmd.stderr(Stdio::null());
}
for (i, arg) in args.enumerate() {
@ -269,6 +305,8 @@ pub struct CommandExecutor<I, OT, S, T, HT = (), C = Child> {
configurer: T,
/// The observers used by this executor
observers: OT,
stdout_observer: Option<Handle<StdOutObserver>>,
stderr_observer: Option<Handle<StdErrObserver>>,
hooks: HT,
phantom: PhantomData<(C, I, S)>,
}
@ -302,6 +340,8 @@ where
.field("inner", &self.configurer)
.field("observers", &self.observers)
.field("hooks", &self.hooks)
.field("stdout_observer", &self.stdout_observer)
.field("stderr_observer", &self.stderr_observer)
.finish()
}
}
@ -323,9 +363,8 @@ where
fn execute_input_with_command(&mut self, state: &mut S, input: &I) -> Result<ExitKind, Error> {
use wait_timeout::ChildExt;
self.observers_mut().pre_exec_all(state, input)?;
*state.executions_mut() += 1;
self.observers.pre_exec_child_all(state, input)?;
let mut child = self.configurer.spawn_child(input)?;
let exit_kind = child
@ -341,31 +380,28 @@ where
ExitKind::Timeout
});
self.observers
.post_exec_child_all(state, input, &exit_kind)?;
// Manualy update stdout/stderr here if we use piped implementation.
// Reason of not putting into state and pass by post_exec_all is that
// - Save extra at least two hashmap lookups since we already know the handle
// - Doesn't pose HasNamedMetadata bound on S (note we might have many stdout/stderr observers)
if let Some(mut stderr) = child.stderr {
if let Some(stderr_handle) = self.stderr_observer.clone() {
let mut buf = vec![];
stderr.read_to_end(&mut buf)?;
self.observers_mut().index_mut(&stderr_handle).observe(buf);
}
}
if let Some(h) = &mut self.configurer.stdout_observer() {
let mut stdout = Vec::new();
child.stdout.as_mut().ok_or_else(|| {
Error::illegal_state(
"Observer tries to read stderr, but stderr was not `Stdio::pipe` in CommandExecutor",
)
})?.read_to_end(&mut stdout)?;
let mut observers = self.observers_mut();
let obs = observers.index_mut(h);
obs.observe(&stdout);
}
if let Some(h) = &mut self.configurer.stderr_observer() {
let mut stderr = Vec::new();
child.stderr.as_mut().ok_or_else(|| {
Error::illegal_state(
"Observer tries to read stderr, but stderr was not `Stdio::pipe` in CommandExecutor",
)
})?.read_to_end(&mut stderr)?;
let mut observers = self.observers_mut();
let obs = observers.index_mut(h);
obs.observe(&stderr);
if let Some(mut stdout) = child.stdout {
if let Some(stdout_handle) = self.stdout_observer.clone() {
let mut buf = vec![];
stdout.read_to_end(&mut buf)?;
self.observers_mut().index_mut(&stdout_handle).observe(buf);
}
}
self.observers_mut()
.post_exec_child_all(state, input, &exit_kind)?;
Ok(exit_kind)
}
}
@ -497,48 +533,27 @@ where
/// The builder for a default [`CommandExecutor`] that should fit most use-cases.
#[derive(Debug, Clone)]
pub struct CommandExecutorBuilder {
stdout: Option<Handle<StdOutObserver>>,
stderr: Option<Handle<StdErrObserver>>,
debug_child: bool,
program: Option<OsString>,
args: Vec<OsString>,
input_location: InputLocation,
cwd: Option<PathBuf>,
envs: Vec<(OsString, OsString)>,
timeout: Duration,
target_inner: StdTargetArgsInner,
child_env_inner: StdChildArgsInner,
}
impl TargetArgs for CommandExecutorBuilder {
fn arguments_ref(&self) -> &Vec<OsString> {
&self.args
impl StdTargetArgs for CommandExecutorBuilder {
fn inner(&self) -> &StdTargetArgsInner {
&self.target_inner
}
fn arguments_mut(&mut self) -> &mut Vec<OsString> {
&mut self.args
fn inner_mut(&mut self) -> &mut StdTargetArgsInner {
&mut self.target_inner
}
}
impl StdChildArgs for CommandExecutorBuilder {
fn inner(&self) -> &StdChildArgsInner {
&self.child_env_inner
}
fn envs_ref(&self) -> &Vec<(OsString, OsString)> {
&self.envs
}
fn envs_mut(&mut self) -> &mut Vec<(OsString, OsString)> {
&mut self.envs
}
fn program_ref(&self) -> &Option<OsString> {
&self.program
}
fn program_mut(&mut self) -> &mut Option<OsString> {
&mut self.program
}
fn input_location_ref(&self) -> &InputLocation {
&self.input_location
}
fn input_location_mut(&mut self) -> &mut InputLocation {
&mut self.input_location
fn inner_mut(&mut self) -> &mut StdChildArgsInner {
&mut self.child_env_inner
}
}
@ -553,49 +568,11 @@ impl CommandExecutorBuilder {
#[must_use]
fn new() -> CommandExecutorBuilder {
CommandExecutorBuilder {
stdout: None,
stderr: None,
program: None,
args: vec![],
input_location: InputLocation::StdIn,
cwd: None,
envs: vec![],
timeout: Duration::from_secs(5),
debug_child: false,
target_inner: StdTargetArgsInner::default(),
child_env_inner: StdChildArgsInner::default(),
}
}
/// Sets the stdout observer
pub fn stdout_observer(&mut self, stdout: Handle<StdOutObserver>) -> &mut Self {
self.stdout = Some(stdout);
self
}
/// Sets the stderr observer
pub fn stderr_observer(&mut self, stderr: Handle<StdErrObserver>) -> &mut Self {
self.stderr = Some(stderr);
self
}
/// Sets the working directory for the child process.
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut CommandExecutorBuilder {
self.cwd = Some(dir.as_ref().to_owned());
self
}
/// If set to true, the child's output won't be redirecited to `/dev/null`.
/// Defaults to `false`.
pub fn debug_child(&mut self, debug_child: bool) -> &mut CommandExecutorBuilder {
self.debug_child = debug_child;
self
}
/// Sets the execution timeout duration.
pub fn timeout(&mut self, timeout: Duration) -> &mut CommandExecutorBuilder {
self.timeout = timeout;
self
}
/// Builds the `CommandExecutor`
pub fn build<I, OT, S>(
&self,
@ -605,14 +582,14 @@ impl CommandExecutorBuilder {
I: HasTargetBytes,
OT: MatchName + ObserversTuple<I, S>,
{
let Some(program) = &self.program else {
let Some(program) = &self.target_inner.program else {
return Err(Error::illegal_argument(
"CommandExecutor::builder: no program set!",
));
};
let mut command = Command::new(program);
match &self.input_location {
match &self.target_inner.input_location {
InputLocation::StdIn => {
command.stdin(Stdio::piped());
}
@ -620,40 +597,77 @@ impl CommandExecutorBuilder {
command.stdin(Stdio::null());
}
}
command.args(&self.args);
command.args(&self.target_inner.arguments);
command.envs(
self.envs
self.target_inner
.envs
.iter()
.map(|(k, v)| (k.as_os_str(), v.as_os_str())),
);
if let Some(cwd) = &self.cwd {
if let Some(cwd) = &self.child_env_inner.current_directory {
command.current_dir(cwd);
}
if !self.debug_child {
command.stdout(Stdio::null());
command.stderr(Stdio::null());
let stdout_cap = self.child_env_inner.stdout_observer.as_ref().map(|hdl| {
observers
.get(hdl)
.as_ref()
.expect("stdout observer not in observers tuple")
.as_raw_fd()
.map(StdCommandCaptureMethod::Fd)
.unwrap_or_default()
});
let stderr_cap = self.child_env_inner.stderr_observer.as_ref().map(|hdl| {
observers
.get(hdl)
.as_ref()
.expect("stderr observer not in observers tuple")
.as_raw_fd()
.map(StdCommandCaptureMethod::Fd)
.unwrap_or_default()
});
if self.child_env_inner.debug_child {
command.stdout(Stdio::piped());
command.stderr(Stdio::piped());
} else {
if let Some(cap) = &stdout_cap {
cap.pre_capture(&mut command, true);
} else {
command.stdout(Stdio::null());
}
if let Some(cap) = &stderr_cap {
cap.pre_capture(&mut command, false);
} else {
command.stderr(Stdio::null());
}
}
if self.stdout.is_some() {
if self.child_env_inner.stdout_observer.is_some() {
command.stdout(Stdio::piped());
}
if self.stderr.is_some() {
if self.child_env_inner.stderr_observer.is_some() {
command.stderr(Stdio::piped());
}
let configurator = StdCommandConfigurator {
debug_child: self.debug_child,
stdout_observer: self.stdout.clone(),
stderr_observer: self.stderr.clone(),
input_location: self.input_location.clone(),
timeout: self.timeout,
debug_child: self.child_env_inner.debug_child,
stdout_cap,
stderr_cap,
input_location: self.target_inner.input_location.clone(),
timeout: self.child_env_inner.timeout,
command,
};
Ok(
<StdCommandConfigurator as CommandConfigurator<I>>::into_executor::<OT, S>(
configurator,
observers,
self.child_env_inner.stdout_observer.clone(),
self.child_env_inner.stderr_observer.clone(),
),
)
}
@ -705,7 +719,7 @@ impl CommandExecutorBuilder {
/// where
/// S: HasExecutions,
/// {
/// MyExecutor.into_executor(())
/// MyExecutor.into_executor((), None, None)
/// }
/// ```
pub trait CommandConfigurator<I, C = Child>: Sized {
@ -739,11 +753,18 @@ pub trait CommandConfigurator<I, C = Child>: Sized {
}
/// Create an `Executor` from this `CommandConfigurator`.
fn into_executor<OT, S>(self, observers: OT) -> CommandExecutor<I, OT, S, Self, (), C> {
fn into_executor<OT, S>(
self,
observers: OT,
stdout_observer: Option<Handle<StdOutObserver>>,
stderr_observer: Option<Handle<StdErrObserver>>,
) -> CommandExecutor<I, OT, S, Self, (), C> {
CommandExecutor {
configurer: self,
observers,
hooks: (),
stderr_observer,
stdout_observer,
phantom: PhantomData,
}
}
@ -753,11 +774,15 @@ pub trait CommandConfigurator<I, C = Child>: Sized {
self,
observers: OT,
hooks: HT,
stdout_observer: Option<Handle<StdOutObserver>>,
stderr_observer: Option<Handle<StdErrObserver>>,
) -> CommandExecutor<I, OT, S, Self, HT, C> {
CommandExecutor {
configurer: self,
observers,
hooks,
stderr_observer,
stdout_observer,
phantom: PhantomData,
}
}
@ -781,17 +806,19 @@ fn waitpid_filtered(pid: Pid, options: Option<WaitPidFlag>) -> Result<WaitStatus
#[cfg(test)]
mod tests {
use libafl_bolts::TargetArgs;
use libafl_bolts::{StdTargetArgs, tuples::Handled};
use tuple_list::tuple_list;
use crate::{
events::SimpleEventManager,
executors::{
Executor,
Executor, StdChildArgs,
command::{CommandExecutor, InputLocation},
},
fuzzer::NopFuzzer,
inputs::{BytesInput, NopInput},
monitors::SimpleMonitor,
observers::StdOutObserver,
state::NopState,
};
@ -818,4 +845,33 @@ mod tests {
)
.unwrap();
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_capture() {
let mut mgr: SimpleEventManager<NopInput, _, NopState<NopInput>> =
SimpleEventManager::new(SimpleMonitor::new(|status| {
log::info!("{status}");
}));
let stdout = StdOutObserver::new("stdout".into()).unwrap();
let handle = stdout.handle();
let executor = CommandExecutor::builder()
.program("ls")
.stdout_observer(handle.clone())
.input(InputLocation::Arg { argnum: 0 });
let executor = executor.build(tuple_list!(stdout));
let mut executor = executor.unwrap();
executor
.run_target(
&mut NopFuzzer::new(),
&mut NopState::<NopInput>::new(),
&mut mgr,
&BytesInput::new(b".".to_vec()),
)
.unwrap();
assert!(executor.observers.0.output.is_some());
}
}

View File

@ -14,17 +14,18 @@ use std::{
fd::{AsRawFd, BorrowedFd},
unix::{io::RawFd, process::CommandExt},
},
path::PathBuf,
process::{Child, Command, Stdio},
};
#[cfg(feature = "regex")]
use libafl_bolts::tuples::{Handle, Handled, MatchNameRef};
use libafl_bolts::tuples::{Handle, Handled};
use libafl_bolts::{
AsSlice, AsSliceMut, InputLocation, TargetArgs, Truncate,
AsSlice, AsSliceMut, InputLocation, StdTargetArgs, StdTargetArgsInner, Truncate,
fs::{InputFile, get_unique_std_input_file},
os::{dup2, pipes::Pipe},
shmem::{ShMem, ShMemProvider, UnixShMem, UnixShMemProvider},
tuples::{Prepend, RefIndexable},
tuples::{MatchNameRef, Prepend, RefIndexable},
};
use libc::RLIM_INFINITY;
use nix::{
@ -37,7 +38,7 @@ use nix::{
unistd::Pid,
};
use super::HasTimeout;
use super::{HasTimeout, StdChildArgs, StdChildArgsInner};
#[cfg(feature = "regex")]
use crate::observers::{
AsanBacktraceObserver, get_asan_runtime_flags, get_asan_runtime_flags_with_log_path,
@ -161,8 +162,6 @@ pub trait ConfigTarget {
fn setlimit(&mut self, memlimit: u64) -> &mut Self;
/// enables core dumps (rlimit = infinity)
fn set_coredump(&mut self, enable: bool) -> &mut Self;
/// Sets the stdin
fn setstdin(&mut self, fd: RawFd, use_stdin: bool) -> &mut Self;
/// Sets the AFL forkserver pipes
fn setpipe(
&mut self,
@ -171,6 +170,8 @@ pub trait ConfigTarget {
ctl_read: RawFd,
ctl_write: RawFd,
) -> &mut Self;
/// dup2 the specific fd, used for stdio
fn setdup2(&mut self, old_fd: RawFd, new_fd: RawFd) -> &mut Self;
}
impl ConfigTarget for Command {
@ -216,23 +217,6 @@ impl ConfigTarget for Command {
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
}
}
#[expect(trivial_numeric_casts)]
fn setlimit(&mut self, memlimit: u64) -> &mut Self {
if memlimit == 0 {
@ -277,6 +261,19 @@ impl ConfigTarget for Command {
// This calls our non-shady function from above.
unsafe { self.pre_exec(func) }
}
fn setdup2(&mut self, old_fd: RawFd, new_fd: RawFd) -> &mut Self {
let func = move || {
let ret = unsafe { libc::dup2(old_fd, new_fd) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
};
// # Safety
// This calls our non-shady function from above.
unsafe { self.pre_exec(func) }
}
}
/// The [`Forkserver`] is communication channel with a child process that forks on request of the fuzzer.
@ -358,6 +355,9 @@ impl Forkserver {
coverage_map_size: Option<usize>,
debug_output: bool,
kill_signal: Signal,
stdout_memfd: Option<RawFd>,
stderr_memfd: Option<RawFd>,
cwd: Option<PathBuf>,
) -> Result<Self, Error> {
let Some(coverage_map_size) = coverage_map_size else {
return Err(Error::unknown(
@ -385,19 +385,32 @@ impl Forkserver {
let mut st_pipe = Pipe::new().unwrap();
let mut ctl_pipe = Pipe::new().unwrap();
let (stdout, stderr) = if debug_output {
(Stdio::inherit(), Stdio::inherit())
} else {
(Stdio::null(), Stdio::null())
};
let mut command = Command::new(target);
// Setup args, stdio
command
.args(args)
.stdin(Stdio::null())
.stdout(stdout)
.stderr(stderr);
command.args(args);
if use_stdin {
command.setdup2(input_filefd, libc::STDIN_FILENO);
} else {
command.stdin(Stdio::null());
}
if debug_output {
command.stdout(Stdio::inherit());
} else if let Some(fd) = &stdout_memfd {
command.setdup2(*fd, libc::STDOUT_FILENO);
command.stdout(Stdio::null());
} else {
command.stdout(Stdio::null());
}
if debug_output {
command.stderr(Stdio::inherit());
} else if let Some(fd) = &stderr_memfd {
command.setdup2(*fd, libc::STDERR_FILENO);
command.stderr(Stdio::null());
} else {
command.stderr(Stdio::null());
}
command.env("AFL_MAP_SIZE", format!("{coverage_map_size}"));
@ -422,13 +435,16 @@ impl Forkserver {
#[cfg(not(feature = "regex"))]
let _ = dump_asan_logs;
if let Some(cwd) = cwd {
command.current_dir(cwd);
}
let fsrv_handle = match command
.env("LD_BIND_NOW", "1")
.envs(envs)
.setlimit(memlimit)
.set_coredump(afl_debug)
.setsid()
.setstdin(input_filefd, use_stdin)
.setpipe(
st_pipe.read_end().unwrap(),
st_pipe.write_end().unwrap(),
@ -798,58 +814,40 @@ where
/// The builder for `ForkserverExecutor`
#[derive(Debug)]
#[expect(clippy::struct_excessive_bools)]
pub struct ForkserverExecutorBuilder<'a, SP> {
program: Option<OsString>,
arguments: Vec<OsString>,
envs: Vec<(OsString, OsString)>,
debug_child: bool,
target_inner: StdTargetArgsInner,
child_env_inner: StdChildArgsInner,
uses_shmem_testcase: bool,
is_persistent: bool,
is_deferred_frksrv: bool,
autotokens: Option<&'a mut Tokens>,
input_location: InputLocation,
shmem_provider: Option<&'a mut SP>,
max_input_size: usize,
min_input_size: usize,
map_size: Option<usize>,
kill_signal: Option<Signal>,
timeout: Option<Duration>,
#[cfg(feature = "regex")]
asan_obs: Option<Handle<AsanBacktraceObserver>>,
crash_exitcode: Option<i8>,
}
impl<SP> TargetArgs for ForkserverExecutorBuilder<'_, SP> {
fn arguments_ref(&self) -> &Vec<OsString> {
&self.arguments
}
fn arguments_mut(&mut self) -> &mut Vec<OsString> {
&mut self.arguments
impl<SP> StdChildArgs for ForkserverExecutorBuilder<'_, SP> {
fn inner(&self) -> &StdChildArgsInner {
&self.child_env_inner
}
fn envs_ref(&self) -> &Vec<(OsString, OsString)> {
&self.envs
fn inner_mut(&mut self) -> &mut StdChildArgsInner {
&mut self.child_env_inner
}
}
impl<SP> StdTargetArgs for ForkserverExecutorBuilder<'_, SP> {
fn inner(&self) -> &StdTargetArgsInner {
&self.target_inner
}
fn envs_mut(&mut self) -> &mut Vec<(OsString, OsString)> {
&mut self.envs
}
fn program_ref(&self) -> &Option<OsString> {
&self.program
}
fn program_mut(&mut self) -> &mut Option<OsString> {
&mut self.program
}
fn input_location_ref(&self) -> &InputLocation {
&self.input_location
}
fn input_location_mut(&mut self) -> &mut InputLocation {
&mut self.input_location
fn inner_mut(&mut self) -> &mut StdTargetArgsInner {
&mut self.target_inner
}
fn arg_input_arg(self) -> Self {
@ -875,13 +873,13 @@ where
where
OT: ObserversTuple<I, S>,
{
let (forkserver, input_file, map) = self.build_helper()?;
let (forkserver, input_file, map) = self.build_helper(&observers)?;
let target = self.program.take().unwrap();
let target = self.target_inner.program.take().unwrap();
log::info!(
"ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}",
target,
self.arguments.clone(),
self.target_inner.arguments.clone(),
self.use_stdin()
);
@ -891,10 +889,7 @@ where
));
}
let timeout: TimeSpec = match self.timeout {
Some(t) => t.into(),
None => Duration::from_millis(5000).into(),
};
let timeout: TimeSpec = self.child_env_inner.timeout.into();
if self.min_input_size > self.max_input_size {
return Err(Error::illegal_argument(
format!(
@ -907,7 +902,7 @@ where
Ok(ForkserverExecutor {
target,
args: self.arguments.clone(),
args: self.target_inner.arguments.clone(),
input_file,
uses_shmem_testcase: self.uses_shmem_testcase,
forkserver,
@ -940,13 +935,13 @@ where
MO: MapObserver + Truncate, // TODO maybe enforce Entry = u8 for the cov map
OT: ObserversTuple<I, S> + Prepend<MO>,
{
let (forkserver, input_file, map) = self.build_helper()?;
let (forkserver, input_file, map) = self.build_helper(&other_observers)?;
let target = self.program.take().unwrap();
let target = self.target_inner.program.take().unwrap();
log::info!(
"ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}, map_size: {:?}",
target,
self.arguments.clone(),
self.target_inner.arguments.clone(),
self.use_stdin(),
self.map_size
);
@ -963,14 +958,11 @@ where
));
}
let timeout: TimeSpec = match self.timeout {
Some(t) => t.into(),
None => Duration::from_millis(5000).into(),
};
let timeout: TimeSpec = self.child_env_inner.timeout.into();
Ok(ForkserverExecutor {
target,
args: self.arguments.clone(),
args: self.target_inner.arguments.clone(),
input_file,
uses_shmem_testcase: self.uses_shmem_testcase,
forkserver,
@ -991,8 +983,14 @@ where
}
#[expect(clippy::pedantic)]
fn build_helper(&mut self) -> Result<(Forkserver, InputFile, Option<SHM>), Error> {
let input_file = match &self.input_location {
fn build_helper<I, OT, S>(
&mut self,
obs: &OT,
) -> Result<(Forkserver, InputFile, Option<SHM>), Error>
where
OT: ObserversTuple<I, S>,
{
let input_file = match &self.target_inner.input_location {
InputLocation::StdIn => InputFile::create(OsString::from(get_unique_std_input_file()))?,
InputLocation::Arg { argnum: _ } => {
return Err(Error::illegal_argument(
@ -1019,11 +1017,11 @@ where
}
};
let mut forkserver = match &self.program {
let mut forkserver = match &self.target_inner.program {
Some(t) => Forkserver::new(
t.clone(),
self.arguments.clone(),
self.envs.clone(),
self.target_inner.arguments.clone(),
self.target_inner.envs.clone(),
input_file.as_raw_fd(),
self.use_stdin(),
0,
@ -1031,8 +1029,23 @@ where
self.is_deferred_frksrv,
self.has_asan_obs(),
self.map_size,
self.debug_child,
self.child_env_inner.debug_child,
self.kill_signal.unwrap_or(KILL_SIGNAL_DEFAULT),
self.child_env_inner.stdout_observer.as_ref().map(|t| {
obs.get(t)
.as_ref()
.expect("stdout observer not passed in the builder")
.as_raw_fd()
.expect("only memory fd backend is allowed for forkserver executor")
}),
self.child_env_inner.stderr_observer.as_ref().map(|t| {
obs.get(t)
.as_ref()
.expect("stderr observer not passed in the builder")
.as_raw_fd()
.expect("only memory fd backend is allowed for forkserver executor")
}),
self.child_env_inner.current_directory.clone(),
)?,
None => {
return Err(Error::illegal_argument(
@ -1278,13 +1291,6 @@ where
self
}
#[must_use]
/// set the timeout for the executor
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
/// Set the max input size
#[must_use]
pub fn max_input_size(mut self, size: usize) -> Self {
@ -1299,13 +1305,6 @@ where
self
}
/// If `debug_child` is set, the child will print to `stdout`/`stderr`.
#[must_use]
pub fn debug_child(mut self, debug_child: bool) -> Self {
self.debug_child = debug_child;
self
}
/// Call this if you want to run it under persistent mode; default is false
#[must_use]
pub fn is_persistent(mut self, is_persistent: bool) -> Self {
@ -1366,21 +1365,17 @@ impl<'a> ForkserverExecutorBuilder<'a, UnixShMemProvider> {
#[must_use]
pub fn new() -> ForkserverExecutorBuilder<'a, UnixShMemProvider> {
ForkserverExecutorBuilder {
program: None,
arguments: vec![],
envs: vec![],
debug_child: false,
target_inner: StdTargetArgsInner::default(),
child_env_inner: StdChildArgsInner::default(),
uses_shmem_testcase: false,
is_persistent: false,
is_deferred_frksrv: false,
autotokens: None,
input_location: InputLocation::StdIn,
shmem_provider: None,
map_size: None,
max_input_size: MAX_INPUT_SIZE_DEFAULT,
min_input_size: MIN_INPUT_SIZE_DEFAULT,
kill_signal: None,
timeout: None,
#[cfg(feature = "regex")]
asan_obs: None,
crash_exitcode: None,
@ -1398,20 +1393,16 @@ impl<'a> ForkserverExecutorBuilder<'a, UnixShMemProvider> {
// Set the new provider
shmem_provider: Some(shmem_provider),
// Copy all other values from the old Builder
program: self.program,
arguments: self.arguments,
envs: self.envs,
debug_child: self.debug_child,
target_inner: self.target_inner,
child_env_inner: self.child_env_inner,
uses_shmem_testcase: self.uses_shmem_testcase,
is_persistent: self.is_persistent,
is_deferred_frksrv: self.is_deferred_frksrv,
autotokens: self.autotokens,
input_location: InputLocation::StdIn,
map_size: self.map_size,
max_input_size: self.max_input_size,
min_input_size: self.min_input_size,
kill_signal: self.kill_signal,
timeout: self.timeout,
#[cfg(feature = "regex")]
asan_obs: self.asan_obs,
crash_exitcode: self.crash_exitcode,
@ -1443,7 +1434,11 @@ where
) -> Result<ExitKind, Error> {
let converter = fuzzer.converter_mut();
let bytes = converter.to_bytes(input);
self.execute_input(state, bytes.as_slice())
self.observers_mut().pre_exec_child_all(state, input)?;
let exit = self.execute_input(state, bytes.as_slice())?;
self.observers_mut()
.post_exec_child_all(state, input, &exit)?;
Ok(exit)
}
}
@ -1481,7 +1476,7 @@ mod tests {
use std::ffi::OsString;
use libafl_bolts::{
AsSliceMut, TargetArgs,
AsSliceMut, StdTargetArgs,
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::tuple_list,
};
@ -1490,7 +1485,10 @@ mod tests {
use crate::{
Error,
corpus::NopCorpus,
executors::forkserver::{FAILED_TO_START_FORKSERVER_MSG, ForkserverExecutor},
executors::{
StdChildArgs,
forkserver::{FAILED_TO_START_FORKSERVER_MSG, ForkserverExecutor},
},
inputs::BytesInput,
observers::{ConstMapObserver, HitcountsMapObserver},
};

View File

@ -2,6 +2,8 @@
use alloc::vec::Vec;
use core::{fmt::Debug, time::Duration};
#[cfg(feature = "std")]
use std::path::PathBuf;
pub use combined::CombinedExecutor;
#[cfg(all(feature = "std", unix))]
@ -14,12 +16,16 @@ pub use inprocess::InProcessExecutor;
pub use inprocess_fork::InProcessForkExecutor;
#[cfg(unix)]
use libafl_bolts::os::unix_signals::Signal;
#[cfg(feature = "std")]
use libafl_bolts::tuples::Handle;
use libafl_bolts::tuples::RefIndexable;
use serde::{Deserialize, Serialize};
pub use shadow::ShadowExecutor;
pub use with_observers::WithObservers;
use crate::Error;
#[cfg(feature = "std")]
use crate::observers::{StdErrObserver, StdOutObserver};
pub mod combined;
#[cfg(all(feature = "std", unix))]
@ -227,6 +233,87 @@ pub fn common_signals() -> Vec<Signal> {
]
}
#[cfg(feature = "std")]
/// The inner shared members of [`StdChildArgs`]
#[derive(Debug, Clone)]
pub struct StdChildArgsInner {
/// The timeout of the children
pub timeout: Duration,
/// The stderr handle of the children
pub stderr_observer: Option<Handle<StdErrObserver>>,
/// The stdout handle of the children
pub stdout_observer: Option<Handle<StdOutObserver>>,
/// The current directory of the spawned children
pub current_directory: Option<PathBuf>,
/// Whether debug child by inheriting stdout/stderr
pub debug_child: bool,
}
#[cfg(feature = "std")]
impl Default for StdChildArgsInner {
fn default() -> Self {
Self {
timeout: Duration::from_millis(5000),
stderr_observer: None,
stdout_observer: None,
current_directory: None,
debug_child: false,
}
}
}
#[cfg(feature = "std")]
/// The shared implementation for children with stdout/stderr/timeouts.
pub trait StdChildArgs: Sized {
/// The inner struct of child environment.
fn inner(&self) -> &StdChildArgsInner;
/// The mutable inner struct of child environment.
fn inner_mut(&mut self) -> &mut StdChildArgsInner;
#[must_use]
/// Sets the execution timeout duration.
fn timeout(mut self, timeout: Duration) -> Self {
self.inner_mut().timeout = timeout;
self
}
#[must_use]
/// Sets the stdout observer
fn stdout_observer(mut self, stdout: Handle<StdOutObserver>) -> Self {
self.inner_mut().stdout_observer = Some(stdout);
self
}
#[must_use]
/// Sets the stderr observer
fn stderr_observer(mut self, stderr: Handle<StdErrObserver>) -> Self {
self.inner_mut().stderr_observer = Some(stderr);
self
}
#[must_use]
/// Sets the working directory for the child process.
fn current_dir(mut self, current_dir: PathBuf) -> Self {
self.inner_mut().current_directory = Some(current_dir);
self
}
#[must_use]
/// If set to true, the child's output won't be redirecited to `/dev/null` and will go to parent's stdout/stderr
/// Defaults to `false`.
fn debug_child(mut self, debug_child: bool) -> Self {
if debug_child {
assert!(
self.inner().stderr_observer.is_none() && self.inner().stdout_observer.is_none(),
"you can not set debug_child when you have stderr_observer or stdout_observer"
);
}
self.inner_mut().debug_child = debug_child;
self
}
}
#[cfg(test)]
/// Tester for executor
pub mod test {

View File

@ -4,14 +4,18 @@
//! The executor must explicitly support these observers.
#![cfg_attr(
unix,
doc = r"For example, they are supported on the [`crate::executors::CommandExecutor`]."
doc = r"For example, they are supported on the [`crate::executors::CommandExecutor`] and [`crate::executors::ForkserverExecutor`]."
)]
use alloc::{borrow::Cow, vec::Vec};
use alloc::{borrow::Cow, string::ToString, vec::Vec};
use core::marker::PhantomData;
use std::{
fs::File,
io::{Read, Seek, SeekFrom},
};
use libafl_bolts::Named;
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};
use crate::{Error, observers::Observer};
@ -26,7 +30,7 @@ use crate::{Error, observers::Observer};
/// Error, Fuzzer, StdFuzzer,
/// corpus::{Corpus, InMemoryCorpus, Testcase},
/// events::{EventFirer, NopEventManager},
/// executors::{CommandExecutor, ExitKind},
/// executors::{StdChildArgs, CommandExecutor, ExitKind},
/// feedbacks::{Feedback, StateInitializer},
/// inputs::BytesInput,
/// mutators::{MutationResult, NopMutator},
@ -36,7 +40,7 @@ use crate::{Error, observers::Observer};
/// };
/// use libafl_bolts::{
/// Named, current_nanos,
/// TargetArgs,
/// StdTargetArgs,
/// rands::StdRand,
/// tuples::{Handle, Handled, MatchNameRef, tuple_list},
/// };
@ -87,8 +91,8 @@ use crate::{Error, observers::Observer};
/// let input_text = "Hello, World!";
/// let encoded_input_text = "SGVsbG8sIFdvcmxkIQo=";
///
/// let stdout_observer = StdOutObserver::new("stdout-observer");
/// let stderr_observer = StdErrObserver::new("stderr-observer");
/// let stdout_observer = StdOutObserver::new("stdout-observer".into()).unwrap();
/// let stderr_observer = StdErrObserver::new("stderr-observer".into()).unwrap();
///
/// let mut feedback = ExportStdXObserver {
/// stdout_observer: stdout_observer.handle(),
@ -170,16 +174,30 @@ use crate::{Error, observers::Observer};
/// }
/// ```
///
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
pub struct OutputObserver<T> {
/// The name of the observer.
pub name: Cow<'static, str>,
/// The captured stdout/stderr data during last execution.
pub output: Option<Vec<u8>>,
#[serde(skip_serializing, deserialize_with = "new_file::<_, T>")]
/// File backend of the memory to capture output, if [`None`] we use portable piped output
pub file: Option<File>,
#[serde(skip)]
/// Phantom data to hold the stream type
phantom: PhantomData<T>,
}
/// Blanket implementation for a [`std::fs::File`]. Fortunately the contents of the file
/// is transient and thus we can safely create a new one on deserialization (and skip it)
/// when doing serialization
fn new_file<'de, D, T>(_d: D) -> Result<Option<File>, D::Error>
where
D: Deserializer<'de>,
{
OutputObserver::<T>::file().map_err(|e| serde::de::Error::custom(e.to_string()))
}
/// Marker traits to mark stdout for the `OutputObserver`
#[derive(Debug, Clone)]
pub struct StdOutMarker;
@ -189,19 +207,80 @@ pub struct StdOutMarker;
pub struct StdErrMarker;
impl<T> OutputObserver<T> {
/// Create a new `OutputObserver` with the given name.
#[must_use]
pub fn new(name: &'static str) -> Self {
Self {
name: Cow::from(name),
// This is the best we can do on macOS because
// - macos doesn't have memfd_create
// - fd returned from shm_open can't be written (https://stackoverflow.com/questions/73752631/cant-write-to-fd-from-shm-open-on-macos)
// - there is even no native tmpfs implementation!
// therefore we create a file and immediately remove it to get a writtable fd.
//
// In most cases, capturing stdout/stderr every loop is very slow and mostly for debugging purpose and thus this should be acceptable.
#[cfg(target_os = "macos")]
fn file() -> Result<Option<File>, Error> {
let fp = File::create_new("fsrvmemfd")?;
nix::unistd::unlink("fsrvmemfd")?;
Ok(Some(fp))
}
/// Cool, we can have [`MemfdShMemProvider`] to create a memfd.
#[cfg(target_os = "linux")]
fn file() -> Result<Option<File>, Error> {
Ok(Some(
libafl_bolts::shmem::unix_shmem::memfd::MemfdShMemProvider::new_file()?,
))
}
/// This will use standard but portable pipe mechanism to capture outputs
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
pub fn file() -> Result<Option<File>, Error> {
Ok(None)
}
/// Create a new [`OutputObserver`] with the given name. This will use the memory fd backend
/// on Linux and macOS, which is compatible with forkserver.
pub fn new(name: Cow<'static, str>) -> Result<Self, Error> {
Ok(Self {
name,
output: None,
file: Self::file()?,
phantom: PhantomData,
})
}
/// Create a new `OutputObserver` with the given name. This use portable piped backend, which
/// only works with [`std::process::Command`].
pub fn new_piped(name: Cow<'static, str>) -> Result<Self, Error> {
Ok(Self {
name,
output: None,
file: None,
phantom: PhantomData,
})
}
/// Create a new `OutputObserver` with given name and file.
/// Useful for targets like nyx which writes to the same file again and again.
#[must_use]
pub fn new_file(name: Cow<'static, str>, file: File) -> Self {
Self {
name,
output: None,
file: Some(file),
phantom: PhantomData,
}
}
/// React to new stream data
pub fn observe(&mut self, data: &[u8]) {
self.output = Some(data.into());
pub fn observe(&mut self, data: Vec<u8>) {
self.output = Some(data);
}
#[must_use]
/// Return the raw fd, if any
pub fn as_raw_fd(&self) -> Option<i32> {
#[cfg(target_family = "unix")]
return self.file.as_ref().map(std::os::fd::AsRawFd::as_raw_fd);
#[cfg(not(target_family = "unix"))]
return None;
}
}
@ -211,16 +290,53 @@ impl<T> Named for OutputObserver<T> {
}
}
impl<I, S, T> Observer<I, S> for OutputObserver<T> {
impl<I, S, T> Observer<I, S> for OutputObserver<T>
where
T: 'static,
{
fn pre_exec_child(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
if let Some(file) = self.file.as_mut() {
file.seek(SeekFrom::Start(0))?;
}
self.output = None;
Ok(())
}
fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
self.output = None;
self.pre_exec_child(_state, _input)
}
fn post_exec_child(
&mut self,
_state: &mut S,
_input: &I,
_exit_kind: &crate::executors::ExitKind,
) -> Result<(), Error> {
if let Some(file) = self.file.as_mut() {
if self.output.is_none() {
let pos = file.stream_position()?;
if pos != 0 {
file.seek(SeekFrom::Start(0))?;
let mut buf = vec![0; pos as usize];
file.read_exact(&mut buf)?;
self.observe(buf);
}
}
}
Ok(())
}
fn post_exec(
&mut self,
_state: &mut S,
_input: &I,
_exit_kind: &crate::executors::ExitKind,
) -> Result<(), Error> {
self.post_exec_child(_state, _input, _exit_kind)
}
}
/// An observer that captures stdout of a target.

View File

@ -4,50 +4,23 @@ use core::{
ffi::{c_char, c_int},
pin::Pin,
};
use std::{ffi::OsString, os::unix::ffi::OsStrExt};
use std::os::unix::ffi::OsStrExt;
use crate::{Error, InputLocation, TargetArgs};
use crate::{Error, StdTargetArgs, StdTargetArgsInner};
/// For creating an C-compatible argument
#[derive(Debug)]
pub struct CMainArgsBuilder {
program: Option<OsString>,
input_location: InputLocation,
envs: Vec<(OsString, OsString)>,
args: Vec<OsString>,
inner: StdTargetArgsInner,
}
impl TargetArgs for CMainArgsBuilder {
fn arguments_ref(&self) -> &Vec<OsString> {
&self.args
impl StdTargetArgs for CMainArgsBuilder {
fn inner(&self) -> &StdTargetArgsInner {
&self.inner
}
fn arguments_mut(&mut self) -> &mut Vec<OsString> {
&mut self.args
}
fn input_location_ref(&self) -> &InputLocation {
&self.input_location
}
fn input_location_mut(&mut self) -> &mut InputLocation {
&mut self.input_location
}
fn envs_ref(&self) -> &Vec<(OsString, OsString)> {
&self.envs
}
fn envs_mut(&mut self) -> &mut Vec<(OsString, OsString)> {
&mut self.envs
}
fn program_ref(&self) -> &Option<OsString> {
&self.program
}
fn program_mut(&mut self) -> &mut Option<OsString> {
&mut self.program
fn inner_mut(&mut self) -> &mut StdTargetArgsInner {
&mut self.inner
}
}
@ -62,10 +35,7 @@ impl CMainArgsBuilder {
#[must_use]
pub fn new() -> Self {
Self {
program: None,
input_location: InputLocation::StdIn,
envs: Vec::new(),
args: Vec::new(),
inner: StdTargetArgsInner::default(),
}
}
@ -73,13 +43,13 @@ impl CMainArgsBuilder {
pub fn build(&self) -> Result<CMainArgs, Error> {
let mut argv: Vec<Pin<Box<CString>>> = Vec::new();
if let Some(program) = &self.program {
if let Some(program) = &self.inner().program {
argv.push(Box::pin(CString::new(program.as_bytes()).unwrap()));
} else {
return Err(Error::illegal_argument("Program not specified"));
}
for args in &self.args {
for args in &self.inner().arguments {
argv.push(Box::pin(CString::new(args.as_bytes()).unwrap()));
}

View File

@ -1390,7 +1390,7 @@ pub mod unix_shmem {
ops::{Deref, DerefMut},
ptr, slice,
};
use std::os::fd::IntoRawFd;
use std::{fs::File, os::fd::IntoRawFd};
use libc::{MAP_SHARED, PROT_READ, PROT_WRITE, close, fstat, ftruncate, mmap, munmap};
use nix::sys::memfd::{MemFdCreateFlag, memfd_create};
@ -1533,6 +1533,21 @@ pub mod unix_shmem {
}
}
/// Dedicated Implementation to yield a [`std::fs::File`]
#[cfg(unix)]
impl MemfdShMemProvider {
/// Unlike [`MemfdShMemProvider::new`], this returns a file instead, without any mmap and truncate.
/// By default, the file size is capped by the tmpfs installed by the operating system, which is big
/// enough to hold all output and avoid spurious read/write errors from children. However, you are free
/// to set the size via [`std::fs::File::set_len`]
pub fn new_file() -> Result<File, Error> {
Ok(File::from(memfd_create(
c"libafl_file",
MemFdCreateFlag::empty(),
)?))
}
}
/// Implement [`ShMemProvider`] for [`MemfdShMemProvider`]
#[cfg(unix)]
impl ShMemProvider for MemfdShMemProvider {

View File

@ -29,27 +29,26 @@ pub enum InputLocation {
},
}
/// The shared inner structs of trait [`StdTargetArgs`]
#[derive(Debug, Clone, Default)]
pub struct StdTargetArgsInner {
/// Program arguments
pub arguments: Vec<OsString>,
/// Program main program
pub program: Option<OsString>,
/// Input location, might be stdin or file or cli arg
pub input_location: InputLocation,
/// Program environments
pub envs: Vec<(OsString, OsString)>,
}
/// The main implementation trait of afl style arguments handling
pub trait TargetArgs: Sized {
/// Gets the arguments
fn arguments_ref(&self) -> &Vec<OsString>;
/// Gets the mutable arguments
fn arguments_mut(&mut self) -> &mut Vec<OsString>;
pub trait StdTargetArgs: Sized {
/// Get inner common arguments
fn inner(&self) -> &StdTargetArgsInner;
/// Gets the main program
fn program_ref(&self) -> &Option<OsString>;
/// Gets the mutable main program
fn program_mut(&mut self) -> &mut Option<OsString>;
/// Gets the input file
fn input_location_ref(&self) -> &InputLocation;
/// Gets the mutable input file
fn input_location_mut(&mut self) -> &mut InputLocation;
/// Get the environments
fn envs_ref(&self) -> &Vec<(OsString, OsString)>;
/// Get the mutable environments
fn envs_mut(&mut self) -> &mut Vec<(OsString, OsString)>;
/// Get mutable inner common arguments
fn inner_mut(&mut self) -> &mut StdTargetArgsInner;
/// Adds an environmental var to the harness's commandline
#[must_use]
@ -58,7 +57,8 @@ pub trait TargetArgs: Sized {
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.envs_mut()
self.inner_mut()
.envs
.push((key.as_ref().to_owned(), val.as_ref().to_owned()));
self
}
@ -75,20 +75,20 @@ pub trait TargetArgs: Sized {
for (ref key, ref val) in vars {
res.push((key.as_ref().to_owned(), val.as_ref().to_owned()));
}
self.envs_mut().append(&mut res);
self.inner_mut().envs.append(&mut res);
self
}
/// If use stdin
#[must_use]
fn use_stdin(&self) -> bool {
matches!(self.input_location_ref(), InputLocation::StdIn)
matches!(self.inner().input_location, InputLocation::StdIn)
}
/// Set input
#[must_use]
fn input(mut self, input: InputLocation) -> Self {
*self.input_location_mut() = input;
self.inner_mut().input_location = input;
self
}
@ -97,7 +97,7 @@ pub trait TargetArgs: Sized {
/// Use [`Self::arg_input_file_std`] if you want to provide the input as a file instead.
#[must_use]
fn arg_input_arg(mut self) -> Self {
let argnum = self.arguments_ref().len();
let argnum = self.inner().arguments.len();
self = self.input(InputLocation::Arg { argnum });
// Placeholder arg that gets replaced with the input name later.
self = self.arg("PLACEHOLDER");
@ -112,7 +112,7 @@ pub trait TargetArgs: Sized {
fn arg_input_file<P: AsRef<Path>>(self, path: P) -> Self {
let mut moved = self.arg(path.as_ref());
assert!(
match moved.input_location_ref() {
match &moved.inner().input_location {
InputLocation::File { out_file } => out_file.path.as_path() == path.as_ref(),
InputLocation::StdIn => true,
InputLocation::Arg { argnum: _ } => false,
@ -137,7 +137,7 @@ pub trait TargetArgs: Sized {
where
O: AsRef<OsStr>,
{
*self.program_mut() = Some(program.as_ref().to_owned());
self.inner_mut().program = Some(program.as_ref().to_owned());
self
}
@ -150,7 +150,7 @@ pub trait TargetArgs: Sized {
where
O: AsRef<OsStr>,
{
self.arguments_mut().push(arg.as_ref().to_owned());
self.inner_mut().arguments.push(arg.as_ref().to_owned());
self
}
@ -168,7 +168,7 @@ pub trait TargetArgs: Sized {
for arg in args {
res.push(arg.as_ref().to_owned());
}
self.arguments_mut().append(&mut res);
self.inner_mut().arguments.append(&mut res);
self
}
@ -189,7 +189,7 @@ pub trait TargetArgs: Sized {
let mut moved = self;
let mut use_arg_0_as_program = false;
if moved.program_ref().is_none() {
if moved.inner().program.is_none() {
use_arg_0_as_program = true;
}
@ -200,7 +200,7 @@ pub trait TargetArgs: Sized {
// subsequent arguments as regular arguments
use_arg_0_as_program = false;
} else if item.as_ref() == "@@" {
match moved.input_location_ref().clone() {
match moved.inner().input_location.clone() {
InputLocation::File { out_file } => {
// If the input file name has been modified, use this one
moved = moved.arg_input_file(&out_file.path);

View File

@ -1,6 +1,7 @@
use core::marker::PhantomData;
use std::{
io::{Read, Seek},
ops::IndexMut,
os::fd::AsRawFd,
};
@ -11,7 +12,10 @@ use libafl::{
observers::{ObserversTuple, StdOutObserver},
state::HasExecutions,
};
use libafl_bolts::{AsSlice, tuples::RefIndexable};
use libafl_bolts::{
AsSlice,
tuples::{Handle, RefIndexable},
};
use libnyx::NyxReturnValue;
use crate::{cmplog::CMPLOG_ENABLED, helper::NyxHelper};
@ -21,7 +25,7 @@ pub struct NyxExecutor<S, OT> {
/// implement nyx function
pub helper: NyxHelper,
/// stdout
stdout: Option<StdOutObserver>,
stdout: Option<Handle<StdOutObserver>>,
/// stderr
// stderr: Option<StdErrObserver>,
/// observers
@ -112,7 +116,7 @@ where
}
};
if let Some(ob) = self.stdout.as_mut() {
if let Some(ob) = self.stdout.clone() {
let mut stdout = Vec::new();
self.helper.nyx_stdout.rewind()?;
self.helper
@ -120,7 +124,7 @@ where
.read_to_end(&mut stdout)
.map_err(|e| Error::illegal_state(format!("Failed to read Nyx stdout: {e}")))?;
ob.observe(&stdout);
self.observers_mut().index_mut(&ob).observe(stdout);
}
unsafe {
@ -169,7 +173,7 @@ impl<S, OT> NyxExecutor<S, OT> {
}
pub struct NyxExecutorBuilder {
stdout: Option<StdOutObserver>,
stdout: Option<Handle<StdOutObserver>>,
// stderr: Option<StdErrObserver>,
}
@ -188,7 +192,7 @@ impl NyxExecutorBuilder {
}
}
pub fn stdout(&mut self, stdout: StdOutObserver) -> &mut Self {
pub fn stdout(&mut self, stdout: Handle<StdOutObserver>) -> &mut Self {
self.stdout = Some(stdout);
self
}

View File

@ -7,7 +7,10 @@ use libafl::{
Error, HasMetadata,
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
events::{EventConfig, LlmpRestartingEventManager, launcher::Launcher},
executors::forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
executors::{
StdChildArgs,
forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
},
feedback_and_fast, feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer},
@ -28,7 +31,7 @@ use libafl::{
state::{HasCorpus, StdState},
};
use libafl_bolts::{
AsSliceMut, TargetArgs,
AsSliceMut, StdTargetArgs,
core_affinity::Cores,
nonzero,
ownedref::OwnedRefMut,

View File

@ -131,11 +131,11 @@ where
let mut out_dir = self.output_dir.clone();
if fs::create_dir(&out_dir).is_err() {
log::info!("Out dir at {:?} already exists.", &out_dir);
log::info!("Out dir at {} already exists.", &out_dir.display());
assert!(
out_dir.is_dir(),
"Out dir at {:?} is not a valid directory!",
&out_dir
"Out dir at {} is not a valid directory!",
&out_dir.display()
);
}
let mut crashes = out_dir.clone();

View File

@ -6,7 +6,7 @@ use std::{
use anyhow::{anyhow, Result};
use nix::unistd::{dup2, execvp};
use crate::{args::ChildArgs, exit::Exit};
use crate::{args::StdChildArgs, exit::Exit};
pub struct Child {
argv: Vec<String>,
@ -47,7 +47,7 @@ impl Child {
Ok(())
}
pub fn new(args: &impl ChildArgs, fd1: RawFd, fd2: RawFd) -> Child {
pub fn new(args: &impl StdChildArgs, fd1: RawFd, fd2: RawFd) -> Child {
Child {
argv: args.argv().to_vec(),
fd1,