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:
parent
2dbf636201
commit
f901c2085d
1
.github/workflows/build_and_test.yml
vendored
1
.github/workflows/build_and_test.yml
vendored
@ -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
|
||||
|
@ -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));
|
||||
|
@ -25,7 +25,7 @@ use libafl_bolts::{
|
||||
rands::StdRand,
|
||||
shmem::{ShMem, ShMemProvider},
|
||||
tuples::tuple_list,
|
||||
AsSliceMut, TargetArgs,
|
||||
AsSliceMut, StdTargetArgs,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
|
@ -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
|
||||
|
1
fuzzers/forkserver/forkserver_capture_stdout/.gitignore
vendored
Normal file
1
fuzzers/forkserver/forkserver_capture_stdout/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
forkserver_capture_stdout
|
1290
fuzzers/forkserver/forkserver_capture_stdout/Cargo.lock
generated
Normal file
1290
fuzzers/forkserver/forkserver_capture_stdout/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
fuzzers/forkserver/forkserver_capture_stdout/Cargo.toml
Normal file
24
fuzzers/forkserver/forkserver_capture_stdout/Cargo.toml
Normal 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"
|
14
fuzzers/forkserver/forkserver_capture_stdout/README.md
Normal file
14
fuzzers/forkserver/forkserver_capture_stdout/README.md
Normal 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.
|
61
fuzzers/forkserver/forkserver_capture_stdout/build.rs
Normal file
61
fuzzers/forkserver/forkserver_capture_stdout/build.rs
Normal 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/");
|
||||
}
|
261
fuzzers/forkserver/forkserver_capture_stdout/src/main.rs
Normal file
261
fuzzers/forkserver/forkserver_capture_stdout/src/main.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
24
fuzzers/forkserver/forkserver_capture_stdout/src/program.c
Normal file
24
fuzzers/forkserver/forkserver_capture_stdout/src/program.c
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
@ -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},
|
||||
|
@ -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;
|
||||
|
@ -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},
|
||||
|
@ -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;
|
||||
|
@ -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};
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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},
|
||||
};
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user