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
|
# Forkserver
|
||||||
- ./fuzzers/forkserver/forkserver_simple
|
- ./fuzzers/forkserver/forkserver_simple
|
||||||
|
- ./fuzzers/forkserver/forkserver_capture_stdout
|
||||||
- ./fuzzers/forkserver/forkserver_libafl_cc
|
- ./fuzzers/forkserver/forkserver_libafl_cc
|
||||||
- ./fuzzers/forkserver/fuzzbench_forkserver
|
- ./fuzzers/forkserver/fuzzbench_forkserver
|
||||||
- ./fuzzers/forkserver/fuzzbench_forkserver_cmplog
|
- ./fuzzers/forkserver/fuzzbench_forkserver_cmplog
|
||||||
|
@ -117,8 +117,11 @@ pub fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
let mut executor =
|
let mut executor = MyExecutor { shmem_id, timeout }.into_executor(
|
||||||
MyExecutor { shmem_id, timeout }.into_executor(tuple_list!(observer, bt_observer));
|
tuple_list!(observer, bt_observer),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
// Generator of printable bytearrays of max size 32
|
// Generator of printable bytearrays of max size 32
|
||||||
let mut generator = RandPrintablesGenerator::new(nonzero!(32));
|
let mut generator = RandPrintablesGenerator::new(nonzero!(32));
|
||||||
|
@ -25,7 +25,7 @@ use libafl_bolts::{
|
|||||||
rands::StdRand,
|
rands::StdRand,
|
||||||
shmem::{ShMem, ShMemProvider},
|
shmem::{ShMem, ShMemProvider},
|
||||||
tuples::tuple_list,
|
tuples::tuple_list,
|
||||||
AsSliceMut, TargetArgs,
|
AsSliceMut, StdTargetArgs,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
|
@ -136,6 +136,8 @@ pub fn main() {
|
|||||||
command_configurator,
|
command_configurator,
|
||||||
tuple_list!(observer),
|
tuple_list!(observer),
|
||||||
tuple_list!(hook),
|
tuple_list!(hook),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Generator of printable bytearrays of max size 32
|
// 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::{
|
use libafl::{
|
||||||
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
|
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
|
||||||
events::SimpleEventManager,
|
events::SimpleEventManager,
|
||||||
executors::{forkserver::ForkserverExecutor, HasObservers},
|
executors::{forkserver::ForkserverExecutor, HasObservers, StdChildArgs},
|
||||||
feedback_and_fast, feedback_or,
|
feedback_and_fast, feedback_or,
|
||||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
||||||
fuzzer::{Fuzzer, StdFuzzer},
|
fuzzer::{Fuzzer, StdFuzzer},
|
||||||
@ -22,7 +22,7 @@ use libafl_bolts::{
|
|||||||
rands::StdRand,
|
rands::StdRand,
|
||||||
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
||||||
tuples::{tuple_list, Handled, Merge},
|
tuples::{tuple_list, Handled, Merge},
|
||||||
AsSliceMut, TargetArgs, Truncate,
|
AsSliceMut, StdTargetArgs, Truncate,
|
||||||
};
|
};
|
||||||
use libafl_targets::EDGES_MAP_DEFAULT_SIZE;
|
use libafl_targets::EDGES_MAP_DEFAULT_SIZE;
|
||||||
use nix::sys::signal::Signal;
|
use nix::sys::signal::Signal;
|
||||||
|
@ -6,7 +6,7 @@ use libafl::{
|
|||||||
HasMetadata,
|
HasMetadata,
|
||||||
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
|
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
|
||||||
events::SimpleEventManager,
|
events::SimpleEventManager,
|
||||||
executors::{HasObservers, forkserver::ForkserverExecutor},
|
executors::{HasObservers, StdChildArgs, forkserver::ForkserverExecutor},
|
||||||
feedback_and_fast, feedback_or,
|
feedback_and_fast, feedback_or,
|
||||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
||||||
fuzzer::{Fuzzer, StdFuzzer},
|
fuzzer::{Fuzzer, StdFuzzer},
|
||||||
@ -19,7 +19,7 @@ use libafl::{
|
|||||||
state::{HasCorpus, StdState},
|
state::{HasCorpus, StdState},
|
||||||
};
|
};
|
||||||
use libafl_bolts::{
|
use libafl_bolts::{
|
||||||
AsSliceMut, TargetArgs, Truncate, current_nanos,
|
AsSliceMut, StdTargetArgs, Truncate, current_nanos,
|
||||||
rands::StdRand,
|
rands::StdRand,
|
||||||
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
||||||
tuples::{Handled, Merge, tuple_list},
|
tuples::{Handled, Merge, tuple_list},
|
||||||
|
@ -11,7 +11,10 @@ use clap::{Arg, ArgAction, Command};
|
|||||||
use libafl::{
|
use libafl::{
|
||||||
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
|
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
|
||||||
events::SimpleEventManager,
|
events::SimpleEventManager,
|
||||||
executors::forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
|
executors::{
|
||||||
|
forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
|
||||||
|
StdChildArgs,
|
||||||
|
},
|
||||||
feedback_or,
|
feedback_or,
|
||||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
||||||
fuzzer::{Fuzzer, StdFuzzer},
|
fuzzer::{Fuzzer, StdFuzzer},
|
||||||
@ -38,7 +41,7 @@ use libafl_bolts::{
|
|||||||
rands::StdRand,
|
rands::StdRand,
|
||||||
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
||||||
tuples::{tuple_list, Merge},
|
tuples::{tuple_list, Merge},
|
||||||
AsSliceMut, TargetArgs,
|
AsSliceMut, StdTargetArgs,
|
||||||
};
|
};
|
||||||
use libafl_targets::cmps::AFLppCmpLogMap;
|
use libafl_targets::cmps::AFLppCmpLogMap;
|
||||||
use nix::sys::signal::Signal;
|
use nix::sys::signal::Signal;
|
||||||
|
@ -11,7 +11,10 @@ use clap::{Arg, ArgAction, Command};
|
|||||||
use libafl::{
|
use libafl::{
|
||||||
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
|
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
|
||||||
events::SimpleEventManager,
|
events::SimpleEventManager,
|
||||||
executors::forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
|
executors::{
|
||||||
|
forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
|
||||||
|
StdChildArgs,
|
||||||
|
},
|
||||||
feedback_or,
|
feedback_or,
|
||||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
||||||
fuzzer::{Fuzzer, StdFuzzer},
|
fuzzer::{Fuzzer, StdFuzzer},
|
||||||
@ -37,7 +40,7 @@ use libafl_bolts::{
|
|||||||
rands::StdRand,
|
rands::StdRand,
|
||||||
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
||||||
tuples::{tuple_list, Handled, Merge},
|
tuples::{tuple_list, Handled, Merge},
|
||||||
AsSliceMut, TargetArgs,
|
AsSliceMut, StdTargetArgs,
|
||||||
};
|
};
|
||||||
use libafl_targets::{
|
use libafl_targets::{
|
||||||
cmps::{observers::AFLppCmpLogObserver, stages::AFLppCmplogTracingStage},
|
cmps::{observers::AFLppCmpLogObserver, stages::AFLppCmplogTracingStage},
|
||||||
|
@ -14,6 +14,7 @@ use libafl::{
|
|||||||
executors::{
|
executors::{
|
||||||
forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
|
forkserver::{ForkserverExecutor, SHM_CMPLOG_ENV_VAR},
|
||||||
sand::SANDExecutor,
|
sand::SANDExecutor,
|
||||||
|
StdChildArgs,
|
||||||
},
|
},
|
||||||
feedback_or,
|
feedback_or,
|
||||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
||||||
@ -41,7 +42,7 @@ use libafl_bolts::{
|
|||||||
rands::StdRand,
|
rands::StdRand,
|
||||||
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
||||||
tuples::{tuple_list, Handled, Merge},
|
tuples::{tuple_list, Handled, Merge},
|
||||||
AsSliceMut, TargetArgs,
|
AsSliceMut, StdTargetArgs,
|
||||||
};
|
};
|
||||||
use libafl_targets::cmps::AFLppCmpLogMap;
|
use libafl_targets::cmps::AFLppCmpLogMap;
|
||||||
use nix::sys::signal::Signal;
|
use nix::sys::signal::Signal;
|
||||||
|
@ -16,7 +16,10 @@ use libafl::monitors::SimpleMonitor;
|
|||||||
use libafl::{
|
use libafl::{
|
||||||
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
|
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
|
||||||
events::ProgressReporter,
|
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,
|
feedback_and, feedback_or, feedback_or_fast,
|
||||||
feedbacks::{
|
feedbacks::{
|
||||||
CaptureTimeoutFeedback, ConstFeedback, CrashFeedback, MaxMapFeedback, TimeFeedback,
|
CaptureTimeoutFeedback, ConstFeedback, CrashFeedback, MaxMapFeedback, TimeFeedback,
|
||||||
@ -51,7 +54,7 @@ use libafl_bolts::{
|
|||||||
rands::StdRand,
|
rands::StdRand,
|
||||||
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
||||||
tuples::{tuple_list, Handled, Merge},
|
tuples::{tuple_list, Handled, Merge},
|
||||||
AsSliceMut, TargetArgs,
|
AsSliceMut, StdTargetArgs,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "nyx")]
|
#[cfg(feature = "nyx")]
|
||||||
use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings};
|
use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings};
|
||||||
|
@ -5,7 +5,7 @@ use clap::Parser;
|
|||||||
use libafl::{
|
use libafl::{
|
||||||
corpus::{InMemoryCorpus, OnDiskCorpus},
|
corpus::{InMemoryCorpus, OnDiskCorpus},
|
||||||
events::SimpleEventManager,
|
events::SimpleEventManager,
|
||||||
executors::{forkserver::ForkserverExecutor, HasObservers},
|
executors::{forkserver::ForkserverExecutor, HasObservers, StdChildArgs},
|
||||||
feedback_and_fast, feedback_or,
|
feedback_and_fast, feedback_or,
|
||||||
feedbacks::{
|
feedbacks::{
|
||||||
CrashFeedback, MaxMapFeedback, NautilusChunksMetadata, NautilusFeedback, TimeFeedback,
|
CrashFeedback, MaxMapFeedback, NautilusChunksMetadata, NautilusFeedback, TimeFeedback,
|
||||||
@ -29,7 +29,7 @@ use libafl_bolts::{
|
|||||||
rands::StdRand,
|
rands::StdRand,
|
||||||
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
||||||
tuples::{tuple_list, Handled},
|
tuples::{tuple_list, Handled},
|
||||||
AsSliceMut, TargetArgs, Truncate,
|
AsSliceMut, StdTargetArgs, Truncate,
|
||||||
};
|
};
|
||||||
use nix::sys::signal::Signal;
|
use nix::sys::signal::Signal;
|
||||||
|
|
||||||
|
@ -217,9 +217,11 @@ fn fuzz(
|
|||||||
let mut stages = tuple_list!(
|
let mut stages = tuple_list!(
|
||||||
// Create a concolic trace
|
// Create a concolic trace
|
||||||
ConcolicTracingStage::new(
|
ConcolicTracingStage::new(
|
||||||
TracingStage::new(
|
TracingStage::new(MyCommandConfigurator.into_executor(
|
||||||
MyCommandConfigurator.into_executor(tuple_list!(concolic_observer)),
|
tuple_list!(concolic_observer),
|
||||||
),
|
None,
|
||||||
|
None
|
||||||
|
),),
|
||||||
concolic_ref,
|
concolic_ref,
|
||||||
),
|
),
|
||||||
// Use the concolic trace for z3-based solving
|
// Use the concolic trace for z3-based solving
|
||||||
|
@ -533,7 +533,10 @@ mod tests {
|
|||||||
|
|
||||||
match try_create_new(&path) {
|
match try_create_new(&path) {
|
||||||
Ok(None) => (),
|
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}"),
|
Err(e) => panic!("An unexpected error occurred: {e}"),
|
||||||
}
|
}
|
||||||
drop(f);
|
drop(f);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! The command executor executes a sub program for each run
|
//! The command executor executes a sub program for each run
|
||||||
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
||||||
use alloc::ffi::CString;
|
use alloc::ffi::CString;
|
||||||
|
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
||||||
use core::ffi::CStr;
|
use core::ffi::CStr;
|
||||||
@ -13,18 +14,17 @@ use core::{
|
|||||||
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
||||||
use std::os::fd::AsRawFd;
|
use std::os::fd::AsRawFd;
|
||||||
use std::{
|
use std::{
|
||||||
ffi::{OsStr, OsString},
|
ffi::OsStr,
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
os::unix::ffi::OsStrExt,
|
os::{fd::RawFd, unix::ffi::OsStrExt},
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::{Child, Command, Stdio},
|
process::{Child, Command, Stdio},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
||||||
use libafl_bolts::core_affinity::CoreId;
|
use libafl_bolts::core_affinity::CoreId;
|
||||||
use libafl_bolts::{
|
use libafl_bolts::{
|
||||||
AsSlice, InputLocation, TargetArgs,
|
AsSlice, InputLocation, StdTargetArgs, StdTargetArgsInner,
|
||||||
tuples::{Handle, MatchName, RefIndexable},
|
tuples::{Handle, MatchName, MatchNameRef, RefIndexable},
|
||||||
};
|
};
|
||||||
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
||||||
use libc::STDIN_FILENO;
|
use libc::STDIN_FILENO;
|
||||||
@ -46,7 +46,9 @@ use nix::{
|
|||||||
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
#[cfg(all(feature = "intel_pt", target_os = "linux"))]
|
||||||
use typed_builder::TypedBuilder;
|
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")]
|
#[cfg(target_os = "linux")]
|
||||||
use crate::executors::hooks::ExecutorHooksTuple;
|
use crate::executors::hooks::ExecutorHooksTuple;
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -55,9 +57,47 @@ use crate::{
|
|||||||
inputs::HasTargetBytes,
|
inputs::HasTargetBytes,
|
||||||
observers::{ObserversTuple, StdErrObserver, StdOutObserver},
|
observers::{ObserversTuple, StdErrObserver, StdOutObserver},
|
||||||
state::HasExecutions,
|
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
|
/// A simple Configurator that takes the most common parameters
|
||||||
/// Writes the input either to stdio or to a file
|
/// Writes the input either to stdio or to a file
|
||||||
/// Use [`CommandExecutor::builder()`] to use this configurator.
|
/// Use [`CommandExecutor::builder()`] to use this configurator.
|
||||||
@ -66,8 +106,8 @@ pub struct StdCommandConfigurator {
|
|||||||
/// If set to true, the child output will remain visible
|
/// If set to true, the child output will remain visible
|
||||||
/// By default, the child output is hidden to increase execution speed
|
/// By default, the child output is hidden to increase execution speed
|
||||||
debug_child: bool,
|
debug_child: bool,
|
||||||
stdout_observer: Option<Handle<StdOutObserver>>,
|
stdout_cap: Option<StdCommandCaptureMethod>,
|
||||||
stderr_observer: Option<Handle<StdErrObserver>>,
|
stderr_cap: Option<StdCommandCaptureMethod>,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
/// true: input gets delivered via stdink
|
/// true: input gets delivered via stdink
|
||||||
input_location: InputLocation,
|
input_location: InputLocation,
|
||||||
@ -79,30 +119,26 @@ impl<I> CommandConfigurator<I> for StdCommandConfigurator
|
|||||||
where
|
where
|
||||||
I: HasTargetBytes,
|
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> {
|
fn spawn_child(&mut self, input: &I) -> Result<Child, Error> {
|
||||||
|
let mut cmd = Command::new(self.command.get_program());
|
||||||
match &mut self.input_location {
|
match &mut self.input_location {
|
||||||
InputLocation::Arg { argnum } => {
|
InputLocation::Arg { argnum } => {
|
||||||
let args = self.command.get_args();
|
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.stdout(Stdio::null());
|
||||||
cmd.stderr(Stdio::null());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.stdout_observer.is_some() {
|
if self.debug_child {
|
||||||
cmd.stdout(Stdio::piped());
|
cmd.stderr(Stdio::inherit());
|
||||||
}
|
} else if let Some(cap) = &self.stderr_cap {
|
||||||
if self.stderr_observer.is_some() {
|
cap.pre_capture(&mut cmd, false);
|
||||||
cmd.stderr(Stdio::piped());
|
} else {
|
||||||
|
cmd.stderr(Stdio::null());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, arg) in args.enumerate() {
|
for (i, arg) in args.enumerate() {
|
||||||
@ -269,6 +305,8 @@ pub struct CommandExecutor<I, OT, S, T, HT = (), C = Child> {
|
|||||||
configurer: T,
|
configurer: T,
|
||||||
/// The observers used by this executor
|
/// The observers used by this executor
|
||||||
observers: OT,
|
observers: OT,
|
||||||
|
stdout_observer: Option<Handle<StdOutObserver>>,
|
||||||
|
stderr_observer: Option<Handle<StdErrObserver>>,
|
||||||
hooks: HT,
|
hooks: HT,
|
||||||
phantom: PhantomData<(C, I, S)>,
|
phantom: PhantomData<(C, I, S)>,
|
||||||
}
|
}
|
||||||
@ -302,6 +340,8 @@ where
|
|||||||
.field("inner", &self.configurer)
|
.field("inner", &self.configurer)
|
||||||
.field("observers", &self.observers)
|
.field("observers", &self.observers)
|
||||||
.field("hooks", &self.hooks)
|
.field("hooks", &self.hooks)
|
||||||
|
.field("stdout_observer", &self.stdout_observer)
|
||||||
|
.field("stderr_observer", &self.stderr_observer)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,9 +363,8 @@ where
|
|||||||
fn execute_input_with_command(&mut self, state: &mut S, input: &I) -> Result<ExitKind, Error> {
|
fn execute_input_with_command(&mut self, state: &mut S, input: &I) -> Result<ExitKind, Error> {
|
||||||
use wait_timeout::ChildExt;
|
use wait_timeout::ChildExt;
|
||||||
|
|
||||||
|
self.observers_mut().pre_exec_all(state, input)?;
|
||||||
*state.executions_mut() += 1;
|
*state.executions_mut() += 1;
|
||||||
self.observers.pre_exec_child_all(state, input)?;
|
|
||||||
|
|
||||||
let mut child = self.configurer.spawn_child(input)?;
|
let mut child = self.configurer.spawn_child(input)?;
|
||||||
|
|
||||||
let exit_kind = child
|
let exit_kind = child
|
||||||
@ -341,31 +380,28 @@ where
|
|||||||
ExitKind::Timeout
|
ExitKind::Timeout
|
||||||
});
|
});
|
||||||
|
|
||||||
self.observers
|
// Manualy update stdout/stderr here if we use piped implementation.
|
||||||
.post_exec_child_all(state, input, &exit_kind)?;
|
// 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() {
|
if let Some(mut stdout) = child.stdout {
|
||||||
let mut stdout = Vec::new();
|
if let Some(stdout_handle) = self.stdout_observer.clone() {
|
||||||
child.stdout.as_mut().ok_or_else(|| {
|
let mut buf = vec![];
|
||||||
Error::illegal_state(
|
stdout.read_to_end(&mut buf)?;
|
||||||
"Observer tries to read stderr, but stderr was not `Stdio::pipe` in CommandExecutor",
|
self.observers_mut().index_mut(&stdout_handle).observe(buf);
|
||||||
)
|
|
||||||
})?.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.observers_mut()
|
||||||
|
.post_exec_child_all(state, input, &exit_kind)?;
|
||||||
Ok(exit_kind)
|
Ok(exit_kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -497,48 +533,27 @@ where
|
|||||||
/// The builder for a default [`CommandExecutor`] that should fit most use-cases.
|
/// The builder for a default [`CommandExecutor`] that should fit most use-cases.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CommandExecutorBuilder {
|
pub struct CommandExecutorBuilder {
|
||||||
stdout: Option<Handle<StdOutObserver>>,
|
target_inner: StdTargetArgsInner,
|
||||||
stderr: Option<Handle<StdErrObserver>>,
|
child_env_inner: StdChildArgsInner,
|
||||||
debug_child: bool,
|
|
||||||
program: Option<OsString>,
|
|
||||||
args: Vec<OsString>,
|
|
||||||
input_location: InputLocation,
|
|
||||||
cwd: Option<PathBuf>,
|
|
||||||
envs: Vec<(OsString, OsString)>,
|
|
||||||
timeout: Duration,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TargetArgs for CommandExecutorBuilder {
|
impl StdTargetArgs for CommandExecutorBuilder {
|
||||||
fn arguments_ref(&self) -> &Vec<OsString> {
|
fn inner(&self) -> &StdTargetArgsInner {
|
||||||
&self.args
|
&self.target_inner
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arguments_mut(&mut self) -> &mut Vec<OsString> {
|
fn inner_mut(&mut self) -> &mut StdTargetArgsInner {
|
||||||
&mut self.args
|
&mut self.target_inner
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn envs_ref(&self) -> &Vec<(OsString, OsString)> {
|
impl StdChildArgs for CommandExecutorBuilder {
|
||||||
&self.envs
|
fn inner(&self) -> &StdChildArgsInner {
|
||||||
|
&self.child_env_inner
|
||||||
}
|
}
|
||||||
|
|
||||||
fn envs_mut(&mut self) -> &mut Vec<(OsString, OsString)> {
|
fn inner_mut(&mut self) -> &mut StdChildArgsInner {
|
||||||
&mut self.envs
|
&mut self.child_env_inner
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,49 +568,11 @@ impl CommandExecutorBuilder {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
fn new() -> CommandExecutorBuilder {
|
fn new() -> CommandExecutorBuilder {
|
||||||
CommandExecutorBuilder {
|
CommandExecutorBuilder {
|
||||||
stdout: None,
|
target_inner: StdTargetArgsInner::default(),
|
||||||
stderr: None,
|
child_env_inner: StdChildArgsInner::default(),
|
||||||
program: None,
|
|
||||||
args: vec![],
|
|
||||||
input_location: InputLocation::StdIn,
|
|
||||||
cwd: None,
|
|
||||||
envs: vec![],
|
|
||||||
timeout: Duration::from_secs(5),
|
|
||||||
debug_child: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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`
|
/// Builds the `CommandExecutor`
|
||||||
pub fn build<I, OT, S>(
|
pub fn build<I, OT, S>(
|
||||||
&self,
|
&self,
|
||||||
@ -605,14 +582,14 @@ impl CommandExecutorBuilder {
|
|||||||
I: HasTargetBytes,
|
I: HasTargetBytes,
|
||||||
OT: MatchName + ObserversTuple<I, S>,
|
OT: MatchName + ObserversTuple<I, S>,
|
||||||
{
|
{
|
||||||
let Some(program) = &self.program else {
|
let Some(program) = &self.target_inner.program else {
|
||||||
return Err(Error::illegal_argument(
|
return Err(Error::illegal_argument(
|
||||||
"CommandExecutor::builder: no program set!",
|
"CommandExecutor::builder: no program set!",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut command = Command::new(program);
|
let mut command = Command::new(program);
|
||||||
match &self.input_location {
|
match &self.target_inner.input_location {
|
||||||
InputLocation::StdIn => {
|
InputLocation::StdIn => {
|
||||||
command.stdin(Stdio::piped());
|
command.stdin(Stdio::piped());
|
||||||
}
|
}
|
||||||
@ -620,40 +597,77 @@ impl CommandExecutorBuilder {
|
|||||||
command.stdin(Stdio::null());
|
command.stdin(Stdio::null());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
command.args(&self.args);
|
command.args(&self.target_inner.arguments);
|
||||||
command.envs(
|
command.envs(
|
||||||
self.envs
|
self.target_inner
|
||||||
|
.envs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (k.as_os_str(), v.as_os_str())),
|
.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);
|
command.current_dir(cwd);
|
||||||
}
|
}
|
||||||
if !self.debug_child {
|
|
||||||
|
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());
|
command.stdout(Stdio::null());
|
||||||
command.stderr(Stdio::null());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.stdout.is_some() {
|
if let Some(cap) = &stderr_cap {
|
||||||
|
cap.pre_capture(&mut command, false);
|
||||||
|
} else {
|
||||||
|
command.stderr(Stdio::null());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.child_env_inner.stdout_observer.is_some() {
|
||||||
command.stdout(Stdio::piped());
|
command.stdout(Stdio::piped());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.stderr.is_some() {
|
if self.child_env_inner.stderr_observer.is_some() {
|
||||||
command.stderr(Stdio::piped());
|
command.stderr(Stdio::piped());
|
||||||
}
|
}
|
||||||
|
|
||||||
let configurator = StdCommandConfigurator {
|
let configurator = StdCommandConfigurator {
|
||||||
debug_child: self.debug_child,
|
debug_child: self.child_env_inner.debug_child,
|
||||||
stdout_observer: self.stdout.clone(),
|
stdout_cap,
|
||||||
stderr_observer: self.stderr.clone(),
|
stderr_cap,
|
||||||
input_location: self.input_location.clone(),
|
input_location: self.target_inner.input_location.clone(),
|
||||||
timeout: self.timeout,
|
timeout: self.child_env_inner.timeout,
|
||||||
command,
|
command,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
<StdCommandConfigurator as CommandConfigurator<I>>::into_executor::<OT, S>(
|
<StdCommandConfigurator as CommandConfigurator<I>>::into_executor::<OT, S>(
|
||||||
configurator,
|
configurator,
|
||||||
observers,
|
observers,
|
||||||
|
self.child_env_inner.stdout_observer.clone(),
|
||||||
|
self.child_env_inner.stderr_observer.clone(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -705,7 +719,7 @@ impl CommandExecutorBuilder {
|
|||||||
/// where
|
/// where
|
||||||
/// S: HasExecutions,
|
/// S: HasExecutions,
|
||||||
/// {
|
/// {
|
||||||
/// MyExecutor.into_executor(())
|
/// MyExecutor.into_executor((), None, None)
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait CommandConfigurator<I, C = Child>: Sized {
|
pub trait CommandConfigurator<I, C = Child>: Sized {
|
||||||
@ -739,11 +753,18 @@ pub trait CommandConfigurator<I, C = Child>: Sized {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create an `Executor` from this `CommandConfigurator`.
|
/// 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 {
|
CommandExecutor {
|
||||||
configurer: self,
|
configurer: self,
|
||||||
observers,
|
observers,
|
||||||
hooks: (),
|
hooks: (),
|
||||||
|
stderr_observer,
|
||||||
|
stdout_observer,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -753,11 +774,15 @@ pub trait CommandConfigurator<I, C = Child>: Sized {
|
|||||||
self,
|
self,
|
||||||
observers: OT,
|
observers: OT,
|
||||||
hooks: HT,
|
hooks: HT,
|
||||||
|
stdout_observer: Option<Handle<StdOutObserver>>,
|
||||||
|
stderr_observer: Option<Handle<StdErrObserver>>,
|
||||||
) -> CommandExecutor<I, OT, S, Self, HT, C> {
|
) -> CommandExecutor<I, OT, S, Self, HT, C> {
|
||||||
CommandExecutor {
|
CommandExecutor {
|
||||||
configurer: self,
|
configurer: self,
|
||||||
observers,
|
observers,
|
||||||
hooks,
|
hooks,
|
||||||
|
stderr_observer,
|
||||||
|
stdout_observer,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -781,17 +806,19 @@ fn waitpid_filtered(pid: Pid, options: Option<WaitPidFlag>) -> Result<WaitStatus
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use libafl_bolts::TargetArgs;
|
use libafl_bolts::{StdTargetArgs, tuples::Handled};
|
||||||
|
use tuple_list::tuple_list;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
events::SimpleEventManager,
|
events::SimpleEventManager,
|
||||||
executors::{
|
executors::{
|
||||||
Executor,
|
Executor, StdChildArgs,
|
||||||
command::{CommandExecutor, InputLocation},
|
command::{CommandExecutor, InputLocation},
|
||||||
},
|
},
|
||||||
fuzzer::NopFuzzer,
|
fuzzer::NopFuzzer,
|
||||||
inputs::{BytesInput, NopInput},
|
inputs::{BytesInput, NopInput},
|
||||||
monitors::SimpleMonitor,
|
monitors::SimpleMonitor,
|
||||||
|
observers::StdOutObserver,
|
||||||
state::NopState,
|
state::NopState,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -818,4 +845,33 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.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},
|
fd::{AsRawFd, BorrowedFd},
|
||||||
unix::{io::RawFd, process::CommandExt},
|
unix::{io::RawFd, process::CommandExt},
|
||||||
},
|
},
|
||||||
|
path::PathBuf,
|
||||||
process::{Child, Command, Stdio},
|
process::{Child, Command, Stdio},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "regex")]
|
#[cfg(feature = "regex")]
|
||||||
use libafl_bolts::tuples::{Handle, Handled, MatchNameRef};
|
use libafl_bolts::tuples::{Handle, Handled};
|
||||||
use libafl_bolts::{
|
use libafl_bolts::{
|
||||||
AsSlice, AsSliceMut, InputLocation, TargetArgs, Truncate,
|
AsSlice, AsSliceMut, InputLocation, StdTargetArgs, StdTargetArgsInner, Truncate,
|
||||||
fs::{InputFile, get_unique_std_input_file},
|
fs::{InputFile, get_unique_std_input_file},
|
||||||
os::{dup2, pipes::Pipe},
|
os::{dup2, pipes::Pipe},
|
||||||
shmem::{ShMem, ShMemProvider, UnixShMem, UnixShMemProvider},
|
shmem::{ShMem, ShMemProvider, UnixShMem, UnixShMemProvider},
|
||||||
tuples::{Prepend, RefIndexable},
|
tuples::{MatchNameRef, Prepend, RefIndexable},
|
||||||
};
|
};
|
||||||
use libc::RLIM_INFINITY;
|
use libc::RLIM_INFINITY;
|
||||||
use nix::{
|
use nix::{
|
||||||
@ -37,7 +38,7 @@ use nix::{
|
|||||||
unistd::Pid,
|
unistd::Pid,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::HasTimeout;
|
use super::{HasTimeout, StdChildArgs, StdChildArgsInner};
|
||||||
#[cfg(feature = "regex")]
|
#[cfg(feature = "regex")]
|
||||||
use crate::observers::{
|
use crate::observers::{
|
||||||
AsanBacktraceObserver, get_asan_runtime_flags, get_asan_runtime_flags_with_log_path,
|
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;
|
fn setlimit(&mut self, memlimit: u64) -> &mut Self;
|
||||||
/// enables core dumps (rlimit = infinity)
|
/// enables core dumps (rlimit = infinity)
|
||||||
fn set_coredump(&mut self, enable: bool) -> &mut Self;
|
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
|
/// Sets the AFL forkserver pipes
|
||||||
fn setpipe(
|
fn setpipe(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -171,6 +170,8 @@ pub trait ConfigTarget {
|
|||||||
ctl_read: RawFd,
|
ctl_read: RawFd,
|
||||||
ctl_write: RawFd,
|
ctl_write: RawFd,
|
||||||
) -> &mut Self;
|
) -> &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 {
|
impl ConfigTarget for Command {
|
||||||
@ -216,23 +217,6 @@ impl ConfigTarget for Command {
|
|||||||
unsafe { self.pre_exec(func) }
|
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)]
|
#[expect(trivial_numeric_casts)]
|
||||||
fn setlimit(&mut self, memlimit: u64) -> &mut Self {
|
fn setlimit(&mut self, memlimit: u64) -> &mut Self {
|
||||||
if memlimit == 0 {
|
if memlimit == 0 {
|
||||||
@ -277,6 +261,19 @@ impl ConfigTarget for Command {
|
|||||||
// This calls our non-shady function from above.
|
// This calls our non-shady function from above.
|
||||||
unsafe { self.pre_exec(func) }
|
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.
|
/// 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>,
|
coverage_map_size: Option<usize>,
|
||||||
debug_output: bool,
|
debug_output: bool,
|
||||||
kill_signal: Signal,
|
kill_signal: Signal,
|
||||||
|
stdout_memfd: Option<RawFd>,
|
||||||
|
stderr_memfd: Option<RawFd>,
|
||||||
|
cwd: Option<PathBuf>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let Some(coverage_map_size) = coverage_map_size else {
|
let Some(coverage_map_size) = coverage_map_size else {
|
||||||
return Err(Error::unknown(
|
return Err(Error::unknown(
|
||||||
@ -385,19 +385,32 @@ impl Forkserver {
|
|||||||
let mut st_pipe = Pipe::new().unwrap();
|
let mut st_pipe = Pipe::new().unwrap();
|
||||||
let mut ctl_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);
|
let mut command = Command::new(target);
|
||||||
// Setup args, stdio
|
// Setup args, stdio
|
||||||
command
|
command.args(args);
|
||||||
.args(args)
|
if use_stdin {
|
||||||
.stdin(Stdio::null())
|
command.setdup2(input_filefd, libc::STDIN_FILENO);
|
||||||
.stdout(stdout)
|
} else {
|
||||||
.stderr(stderr);
|
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}"));
|
command.env("AFL_MAP_SIZE", format!("{coverage_map_size}"));
|
||||||
|
|
||||||
@ -422,13 +435,16 @@ impl Forkserver {
|
|||||||
#[cfg(not(feature = "regex"))]
|
#[cfg(not(feature = "regex"))]
|
||||||
let _ = dump_asan_logs;
|
let _ = dump_asan_logs;
|
||||||
|
|
||||||
|
if let Some(cwd) = cwd {
|
||||||
|
command.current_dir(cwd);
|
||||||
|
}
|
||||||
|
|
||||||
let fsrv_handle = match command
|
let fsrv_handle = match command
|
||||||
.env("LD_BIND_NOW", "1")
|
.env("LD_BIND_NOW", "1")
|
||||||
.envs(envs)
|
.envs(envs)
|
||||||
.setlimit(memlimit)
|
.setlimit(memlimit)
|
||||||
.set_coredump(afl_debug)
|
.set_coredump(afl_debug)
|
||||||
.setsid()
|
.setsid()
|
||||||
.setstdin(input_filefd, use_stdin)
|
|
||||||
.setpipe(
|
.setpipe(
|
||||||
st_pipe.read_end().unwrap(),
|
st_pipe.read_end().unwrap(),
|
||||||
st_pipe.write_end().unwrap(),
|
st_pipe.write_end().unwrap(),
|
||||||
@ -798,58 +814,40 @@ where
|
|||||||
|
|
||||||
/// The builder for `ForkserverExecutor`
|
/// The builder for `ForkserverExecutor`
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[expect(clippy::struct_excessive_bools)]
|
|
||||||
pub struct ForkserverExecutorBuilder<'a, SP> {
|
pub struct ForkserverExecutorBuilder<'a, SP> {
|
||||||
program: Option<OsString>,
|
target_inner: StdTargetArgsInner,
|
||||||
arguments: Vec<OsString>,
|
child_env_inner: StdChildArgsInner,
|
||||||
envs: Vec<(OsString, OsString)>,
|
|
||||||
debug_child: bool,
|
|
||||||
uses_shmem_testcase: bool,
|
uses_shmem_testcase: bool,
|
||||||
is_persistent: bool,
|
is_persistent: bool,
|
||||||
is_deferred_frksrv: bool,
|
is_deferred_frksrv: bool,
|
||||||
autotokens: Option<&'a mut Tokens>,
|
autotokens: Option<&'a mut Tokens>,
|
||||||
input_location: InputLocation,
|
|
||||||
shmem_provider: Option<&'a mut SP>,
|
shmem_provider: Option<&'a mut SP>,
|
||||||
max_input_size: usize,
|
max_input_size: usize,
|
||||||
min_input_size: usize,
|
min_input_size: usize,
|
||||||
map_size: Option<usize>,
|
map_size: Option<usize>,
|
||||||
kill_signal: Option<Signal>,
|
kill_signal: Option<Signal>,
|
||||||
timeout: Option<Duration>,
|
|
||||||
#[cfg(feature = "regex")]
|
#[cfg(feature = "regex")]
|
||||||
asan_obs: Option<Handle<AsanBacktraceObserver>>,
|
asan_obs: Option<Handle<AsanBacktraceObserver>>,
|
||||||
crash_exitcode: Option<i8>,
|
crash_exitcode: Option<i8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SP> TargetArgs for ForkserverExecutorBuilder<'_, SP> {
|
impl<SP> StdChildArgs for ForkserverExecutorBuilder<'_, SP> {
|
||||||
fn arguments_ref(&self) -> &Vec<OsString> {
|
fn inner(&self) -> &StdChildArgsInner {
|
||||||
&self.arguments
|
&self.child_env_inner
|
||||||
}
|
|
||||||
fn arguments_mut(&mut self) -> &mut Vec<OsString> {
|
|
||||||
&mut self.arguments
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn envs_ref(&self) -> &Vec<(OsString, OsString)> {
|
fn inner_mut(&mut self) -> &mut StdChildArgsInner {
|
||||||
&self.envs
|
&mut self.child_env_inner
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn envs_mut(&mut self) -> &mut Vec<(OsString, OsString)> {
|
impl<SP> StdTargetArgs for ForkserverExecutorBuilder<'_, SP> {
|
||||||
&mut self.envs
|
fn inner(&self) -> &StdTargetArgsInner {
|
||||||
|
&self.target_inner
|
||||||
}
|
}
|
||||||
|
|
||||||
fn program_ref(&self) -> &Option<OsString> {
|
fn inner_mut(&mut self) -> &mut StdTargetArgsInner {
|
||||||
&self.program
|
&mut self.target_inner
|
||||||
}
|
|
||||||
|
|
||||||
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 arg_input_arg(self) -> Self {
|
fn arg_input_arg(self) -> Self {
|
||||||
@ -875,13 +873,13 @@ where
|
|||||||
where
|
where
|
||||||
OT: ObserversTuple<I, S>,
|
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!(
|
log::info!(
|
||||||
"ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}",
|
"ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}",
|
||||||
target,
|
target,
|
||||||
self.arguments.clone(),
|
self.target_inner.arguments.clone(),
|
||||||
self.use_stdin()
|
self.use_stdin()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -891,10 +889,7 @@ where
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeout: TimeSpec = match self.timeout {
|
let timeout: TimeSpec = self.child_env_inner.timeout.into();
|
||||||
Some(t) => t.into(),
|
|
||||||
None => Duration::from_millis(5000).into(),
|
|
||||||
};
|
|
||||||
if self.min_input_size > self.max_input_size {
|
if self.min_input_size > self.max_input_size {
|
||||||
return Err(Error::illegal_argument(
|
return Err(Error::illegal_argument(
|
||||||
format!(
|
format!(
|
||||||
@ -907,7 +902,7 @@ where
|
|||||||
|
|
||||||
Ok(ForkserverExecutor {
|
Ok(ForkserverExecutor {
|
||||||
target,
|
target,
|
||||||
args: self.arguments.clone(),
|
args: self.target_inner.arguments.clone(),
|
||||||
input_file,
|
input_file,
|
||||||
uses_shmem_testcase: self.uses_shmem_testcase,
|
uses_shmem_testcase: self.uses_shmem_testcase,
|
||||||
forkserver,
|
forkserver,
|
||||||
@ -940,13 +935,13 @@ where
|
|||||||
MO: MapObserver + Truncate, // TODO maybe enforce Entry = u8 for the cov map
|
MO: MapObserver + Truncate, // TODO maybe enforce Entry = u8 for the cov map
|
||||||
OT: ObserversTuple<I, S> + Prepend<MO>,
|
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!(
|
log::info!(
|
||||||
"ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}, map_size: {:?}",
|
"ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}, map_size: {:?}",
|
||||||
target,
|
target,
|
||||||
self.arguments.clone(),
|
self.target_inner.arguments.clone(),
|
||||||
self.use_stdin(),
|
self.use_stdin(),
|
||||||
self.map_size
|
self.map_size
|
||||||
);
|
);
|
||||||
@ -963,14 +958,11 @@ where
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeout: TimeSpec = match self.timeout {
|
let timeout: TimeSpec = self.child_env_inner.timeout.into();
|
||||||
Some(t) => t.into(),
|
|
||||||
None => Duration::from_millis(5000).into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ForkserverExecutor {
|
Ok(ForkserverExecutor {
|
||||||
target,
|
target,
|
||||||
args: self.arguments.clone(),
|
args: self.target_inner.arguments.clone(),
|
||||||
input_file,
|
input_file,
|
||||||
uses_shmem_testcase: self.uses_shmem_testcase,
|
uses_shmem_testcase: self.uses_shmem_testcase,
|
||||||
forkserver,
|
forkserver,
|
||||||
@ -991,8 +983,14 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::pedantic)]
|
#[expect(clippy::pedantic)]
|
||||||
fn build_helper(&mut self) -> Result<(Forkserver, InputFile, Option<SHM>), Error> {
|
fn build_helper<I, OT, S>(
|
||||||
let input_file = match &self.input_location {
|
&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::StdIn => InputFile::create(OsString::from(get_unique_std_input_file()))?,
|
||||||
InputLocation::Arg { argnum: _ } => {
|
InputLocation::Arg { argnum: _ } => {
|
||||||
return Err(Error::illegal_argument(
|
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(
|
Some(t) => Forkserver::new(
|
||||||
t.clone(),
|
t.clone(),
|
||||||
self.arguments.clone(),
|
self.target_inner.arguments.clone(),
|
||||||
self.envs.clone(),
|
self.target_inner.envs.clone(),
|
||||||
input_file.as_raw_fd(),
|
input_file.as_raw_fd(),
|
||||||
self.use_stdin(),
|
self.use_stdin(),
|
||||||
0,
|
0,
|
||||||
@ -1031,8 +1029,23 @@ where
|
|||||||
self.is_deferred_frksrv,
|
self.is_deferred_frksrv,
|
||||||
self.has_asan_obs(),
|
self.has_asan_obs(),
|
||||||
self.map_size,
|
self.map_size,
|
||||||
self.debug_child,
|
self.child_env_inner.debug_child,
|
||||||
self.kill_signal.unwrap_or(KILL_SIGNAL_DEFAULT),
|
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 => {
|
None => {
|
||||||
return Err(Error::illegal_argument(
|
return Err(Error::illegal_argument(
|
||||||
@ -1278,13 +1291,6 @@ where
|
|||||||
self
|
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
|
/// Set the max input size
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn max_input_size(mut self, size: usize) -> Self {
|
pub fn max_input_size(mut self, size: usize) -> Self {
|
||||||
@ -1299,13 +1305,6 @@ where
|
|||||||
self
|
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
|
/// Call this if you want to run it under persistent mode; default is false
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_persistent(mut self, is_persistent: bool) -> Self {
|
pub fn is_persistent(mut self, is_persistent: bool) -> Self {
|
||||||
@ -1366,21 +1365,17 @@ impl<'a> ForkserverExecutorBuilder<'a, UnixShMemProvider> {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> ForkserverExecutorBuilder<'a, UnixShMemProvider> {
|
pub fn new() -> ForkserverExecutorBuilder<'a, UnixShMemProvider> {
|
||||||
ForkserverExecutorBuilder {
|
ForkserverExecutorBuilder {
|
||||||
program: None,
|
target_inner: StdTargetArgsInner::default(),
|
||||||
arguments: vec![],
|
child_env_inner: StdChildArgsInner::default(),
|
||||||
envs: vec![],
|
|
||||||
debug_child: false,
|
|
||||||
uses_shmem_testcase: false,
|
uses_shmem_testcase: false,
|
||||||
is_persistent: false,
|
is_persistent: false,
|
||||||
is_deferred_frksrv: false,
|
is_deferred_frksrv: false,
|
||||||
autotokens: None,
|
autotokens: None,
|
||||||
input_location: InputLocation::StdIn,
|
|
||||||
shmem_provider: None,
|
shmem_provider: None,
|
||||||
map_size: None,
|
map_size: None,
|
||||||
max_input_size: MAX_INPUT_SIZE_DEFAULT,
|
max_input_size: MAX_INPUT_SIZE_DEFAULT,
|
||||||
min_input_size: MIN_INPUT_SIZE_DEFAULT,
|
min_input_size: MIN_INPUT_SIZE_DEFAULT,
|
||||||
kill_signal: None,
|
kill_signal: None,
|
||||||
timeout: None,
|
|
||||||
#[cfg(feature = "regex")]
|
#[cfg(feature = "regex")]
|
||||||
asan_obs: None,
|
asan_obs: None,
|
||||||
crash_exitcode: None,
|
crash_exitcode: None,
|
||||||
@ -1398,20 +1393,16 @@ impl<'a> ForkserverExecutorBuilder<'a, UnixShMemProvider> {
|
|||||||
// Set the new provider
|
// Set the new provider
|
||||||
shmem_provider: Some(shmem_provider),
|
shmem_provider: Some(shmem_provider),
|
||||||
// Copy all other values from the old Builder
|
// Copy all other values from the old Builder
|
||||||
program: self.program,
|
target_inner: self.target_inner,
|
||||||
arguments: self.arguments,
|
child_env_inner: self.child_env_inner,
|
||||||
envs: self.envs,
|
|
||||||
debug_child: self.debug_child,
|
|
||||||
uses_shmem_testcase: self.uses_shmem_testcase,
|
uses_shmem_testcase: self.uses_shmem_testcase,
|
||||||
is_persistent: self.is_persistent,
|
is_persistent: self.is_persistent,
|
||||||
is_deferred_frksrv: self.is_deferred_frksrv,
|
is_deferred_frksrv: self.is_deferred_frksrv,
|
||||||
autotokens: self.autotokens,
|
autotokens: self.autotokens,
|
||||||
input_location: InputLocation::StdIn,
|
|
||||||
map_size: self.map_size,
|
map_size: self.map_size,
|
||||||
max_input_size: self.max_input_size,
|
max_input_size: self.max_input_size,
|
||||||
min_input_size: self.min_input_size,
|
min_input_size: self.min_input_size,
|
||||||
kill_signal: self.kill_signal,
|
kill_signal: self.kill_signal,
|
||||||
timeout: self.timeout,
|
|
||||||
#[cfg(feature = "regex")]
|
#[cfg(feature = "regex")]
|
||||||
asan_obs: self.asan_obs,
|
asan_obs: self.asan_obs,
|
||||||
crash_exitcode: self.crash_exitcode,
|
crash_exitcode: self.crash_exitcode,
|
||||||
@ -1443,7 +1434,11 @@ where
|
|||||||
) -> Result<ExitKind, Error> {
|
) -> Result<ExitKind, Error> {
|
||||||
let converter = fuzzer.converter_mut();
|
let converter = fuzzer.converter_mut();
|
||||||
let bytes = converter.to_bytes(input);
|
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 std::ffi::OsString;
|
||||||
|
|
||||||
use libafl_bolts::{
|
use libafl_bolts::{
|
||||||
AsSliceMut, TargetArgs,
|
AsSliceMut, StdTargetArgs,
|
||||||
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
||||||
tuples::tuple_list,
|
tuples::tuple_list,
|
||||||
};
|
};
|
||||||
@ -1490,7 +1485,10 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
Error,
|
Error,
|
||||||
corpus::NopCorpus,
|
corpus::NopCorpus,
|
||||||
executors::forkserver::{FAILED_TO_START_FORKSERVER_MSG, ForkserverExecutor},
|
executors::{
|
||||||
|
StdChildArgs,
|
||||||
|
forkserver::{FAILED_TO_START_FORKSERVER_MSG, ForkserverExecutor},
|
||||||
|
},
|
||||||
inputs::BytesInput,
|
inputs::BytesInput,
|
||||||
observers::{ConstMapObserver, HitcountsMapObserver},
|
observers::{ConstMapObserver, HitcountsMapObserver},
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::{fmt::Debug, time::Duration};
|
use core::{fmt::Debug, time::Duration};
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub use combined::CombinedExecutor;
|
pub use combined::CombinedExecutor;
|
||||||
#[cfg(all(feature = "std", unix))]
|
#[cfg(all(feature = "std", unix))]
|
||||||
@ -14,12 +16,16 @@ pub use inprocess::InProcessExecutor;
|
|||||||
pub use inprocess_fork::InProcessForkExecutor;
|
pub use inprocess_fork::InProcessForkExecutor;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use libafl_bolts::os::unix_signals::Signal;
|
use libafl_bolts::os::unix_signals::Signal;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use libafl_bolts::tuples::Handle;
|
||||||
use libafl_bolts::tuples::RefIndexable;
|
use libafl_bolts::tuples::RefIndexable;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
pub use shadow::ShadowExecutor;
|
pub use shadow::ShadowExecutor;
|
||||||
pub use with_observers::WithObservers;
|
pub use with_observers::WithObservers;
|
||||||
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use crate::observers::{StdErrObserver, StdOutObserver};
|
||||||
|
|
||||||
pub mod combined;
|
pub mod combined;
|
||||||
#[cfg(all(feature = "std", unix))]
|
#[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)]
|
#[cfg(test)]
|
||||||
/// Tester for executor
|
/// Tester for executor
|
||||||
pub mod test {
|
pub mod test {
|
||||||
|
@ -4,14 +4,18 @@
|
|||||||
//! The executor must explicitly support these observers.
|
//! The executor must explicitly support these observers.
|
||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
unix,
|
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 core::marker::PhantomData;
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{Read, Seek, SeekFrom},
|
||||||
|
};
|
||||||
|
|
||||||
use libafl_bolts::Named;
|
use libafl_bolts::Named;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
|
||||||
use crate::{Error, observers::Observer};
|
use crate::{Error, observers::Observer};
|
||||||
|
|
||||||
@ -26,7 +30,7 @@ use crate::{Error, observers::Observer};
|
|||||||
/// Error, Fuzzer, StdFuzzer,
|
/// Error, Fuzzer, StdFuzzer,
|
||||||
/// corpus::{Corpus, InMemoryCorpus, Testcase},
|
/// corpus::{Corpus, InMemoryCorpus, Testcase},
|
||||||
/// events::{EventFirer, NopEventManager},
|
/// events::{EventFirer, NopEventManager},
|
||||||
/// executors::{CommandExecutor, ExitKind},
|
/// executors::{StdChildArgs, CommandExecutor, ExitKind},
|
||||||
/// feedbacks::{Feedback, StateInitializer},
|
/// feedbacks::{Feedback, StateInitializer},
|
||||||
/// inputs::BytesInput,
|
/// inputs::BytesInput,
|
||||||
/// mutators::{MutationResult, NopMutator},
|
/// mutators::{MutationResult, NopMutator},
|
||||||
@ -36,7 +40,7 @@ use crate::{Error, observers::Observer};
|
|||||||
/// };
|
/// };
|
||||||
/// use libafl_bolts::{
|
/// use libafl_bolts::{
|
||||||
/// Named, current_nanos,
|
/// Named, current_nanos,
|
||||||
/// TargetArgs,
|
/// StdTargetArgs,
|
||||||
/// rands::StdRand,
|
/// rands::StdRand,
|
||||||
/// tuples::{Handle, Handled, MatchNameRef, tuple_list},
|
/// tuples::{Handle, Handled, MatchNameRef, tuple_list},
|
||||||
/// };
|
/// };
|
||||||
@ -87,8 +91,8 @@ use crate::{Error, observers::Observer};
|
|||||||
/// let input_text = "Hello, World!";
|
/// let input_text = "Hello, World!";
|
||||||
/// let encoded_input_text = "SGVsbG8sIFdvcmxkIQo=";
|
/// let encoded_input_text = "SGVsbG8sIFdvcmxkIQo=";
|
||||||
///
|
///
|
||||||
/// let stdout_observer = StdOutObserver::new("stdout-observer");
|
/// let stdout_observer = StdOutObserver::new("stdout-observer".into()).unwrap();
|
||||||
/// let stderr_observer = StdErrObserver::new("stderr-observer");
|
/// let stderr_observer = StdErrObserver::new("stderr-observer".into()).unwrap();
|
||||||
///
|
///
|
||||||
/// let mut feedback = ExportStdXObserver {
|
/// let mut feedback = ExportStdXObserver {
|
||||||
/// stdout_observer: stdout_observer.handle(),
|
/// 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> {
|
pub struct OutputObserver<T> {
|
||||||
/// The name of the observer.
|
/// The name of the observer.
|
||||||
pub name: Cow<'static, str>,
|
pub name: Cow<'static, str>,
|
||||||
/// The captured stdout/stderr data during last execution.
|
/// The captured stdout/stderr data during last execution.
|
||||||
pub output: Option<Vec<u8>>,
|
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 data to hold the stream type
|
||||||
phantom: PhantomData<T>,
|
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`
|
/// Marker traits to mark stdout for the `OutputObserver`
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StdOutMarker;
|
pub struct StdOutMarker;
|
||||||
@ -189,19 +207,80 @@ pub struct StdOutMarker;
|
|||||||
pub struct StdErrMarker;
|
pub struct StdErrMarker;
|
||||||
|
|
||||||
impl<T> OutputObserver<T> {
|
impl<T> OutputObserver<T> {
|
||||||
/// Create a new `OutputObserver` with the given name.
|
// This is the best we can do on macOS because
|
||||||
#[must_use]
|
// - macos doesn't have memfd_create
|
||||||
pub fn new(name: &'static str) -> Self {
|
// - fd returned from shm_open can't be written (https://stackoverflow.com/questions/73752631/cant-write-to-fd-from-shm-open-on-macos)
|
||||||
Self {
|
// - there is even no native tmpfs implementation!
|
||||||
name: Cow::from(name),
|
// 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,
|
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,
|
phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// React to new stream data
|
/// React to new stream data
|
||||||
pub fn observe(&mut self, data: &[u8]) {
|
pub fn observe(&mut self, data: Vec<u8>) {
|
||||||
self.output = Some(data.into());
|
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> {
|
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;
|
self.output = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
|
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(())
|
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.
|
/// An observer that captures stdout of a target.
|
||||||
|
@ -4,50 +4,23 @@ use core::{
|
|||||||
ffi::{c_char, c_int},
|
ffi::{c_char, c_int},
|
||||||
pin::Pin,
|
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
|
/// For creating an C-compatible argument
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CMainArgsBuilder {
|
pub struct CMainArgsBuilder {
|
||||||
program: Option<OsString>,
|
inner: StdTargetArgsInner,
|
||||||
input_location: InputLocation,
|
|
||||||
envs: Vec<(OsString, OsString)>,
|
|
||||||
args: Vec<OsString>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TargetArgs for CMainArgsBuilder {
|
impl StdTargetArgs for CMainArgsBuilder {
|
||||||
fn arguments_ref(&self) -> &Vec<OsString> {
|
fn inner(&self) -> &StdTargetArgsInner {
|
||||||
&self.args
|
&self.inner
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arguments_mut(&mut self) -> &mut Vec<OsString> {
|
fn inner_mut(&mut self) -> &mut StdTargetArgsInner {
|
||||||
&mut self.args
|
&mut self.inner
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,10 +35,7 @@ impl CMainArgsBuilder {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
program: None,
|
inner: StdTargetArgsInner::default(),
|
||||||
input_location: InputLocation::StdIn,
|
|
||||||
envs: Vec::new(),
|
|
||||||
args: Vec::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,13 +43,13 @@ impl CMainArgsBuilder {
|
|||||||
pub fn build(&self) -> Result<CMainArgs, Error> {
|
pub fn build(&self) -> Result<CMainArgs, Error> {
|
||||||
let mut argv: Vec<Pin<Box<CString>>> = Vec::new();
|
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()));
|
argv.push(Box::pin(CString::new(program.as_bytes()).unwrap()));
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::illegal_argument("Program not specified"));
|
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()));
|
argv.push(Box::pin(CString::new(args.as_bytes()).unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1390,7 +1390,7 @@ pub mod unix_shmem {
|
|||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
ptr, slice,
|
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 libc::{MAP_SHARED, PROT_READ, PROT_WRITE, close, fstat, ftruncate, mmap, munmap};
|
||||||
use nix::sys::memfd::{MemFdCreateFlag, memfd_create};
|
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`]
|
/// Implement [`ShMemProvider`] for [`MemfdShMemProvider`]
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
impl ShMemProvider for MemfdShMemProvider {
|
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
|
/// The main implementation trait of afl style arguments handling
|
||||||
pub trait TargetArgs: Sized {
|
pub trait StdTargetArgs: Sized {
|
||||||
/// Gets the arguments
|
/// Get inner common arguments
|
||||||
fn arguments_ref(&self) -> &Vec<OsString>;
|
fn inner(&self) -> &StdTargetArgsInner;
|
||||||
/// Gets the mutable arguments
|
|
||||||
fn arguments_mut(&mut self) -> &mut Vec<OsString>;
|
|
||||||
|
|
||||||
/// Gets the main program
|
/// Get mutable inner common arguments
|
||||||
fn program_ref(&self) -> &Option<OsString>;
|
fn inner_mut(&mut self) -> &mut StdTargetArgsInner;
|
||||||
/// 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)>;
|
|
||||||
|
|
||||||
/// Adds an environmental var to the harness's commandline
|
/// Adds an environmental var to the harness's commandline
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -58,7 +57,8 @@ pub trait TargetArgs: Sized {
|
|||||||
K: AsRef<OsStr>,
|
K: AsRef<OsStr>,
|
||||||
V: AsRef<OsStr>,
|
V: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
self.envs_mut()
|
self.inner_mut()
|
||||||
|
.envs
|
||||||
.push((key.as_ref().to_owned(), val.as_ref().to_owned()));
|
.push((key.as_ref().to_owned(), val.as_ref().to_owned()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -75,20 +75,20 @@ pub trait TargetArgs: Sized {
|
|||||||
for (ref key, ref val) in vars {
|
for (ref key, ref val) in vars {
|
||||||
res.push((key.as_ref().to_owned(), val.as_ref().to_owned()));
|
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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If use stdin
|
/// If use stdin
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn use_stdin(&self) -> bool {
|
fn use_stdin(&self) -> bool {
|
||||||
matches!(self.input_location_ref(), InputLocation::StdIn)
|
matches!(self.inner().input_location, InputLocation::StdIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set input
|
/// Set input
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn input(mut self, input: InputLocation) -> Self {
|
fn input(mut self, input: InputLocation) -> Self {
|
||||||
*self.input_location_mut() = input;
|
self.inner_mut().input_location = input;
|
||||||
self
|
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.
|
/// Use [`Self::arg_input_file_std`] if you want to provide the input as a file instead.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn arg_input_arg(mut self) -> Self {
|
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 });
|
self = self.input(InputLocation::Arg { argnum });
|
||||||
// Placeholder arg that gets replaced with the input name later.
|
// Placeholder arg that gets replaced with the input name later.
|
||||||
self = self.arg("PLACEHOLDER");
|
self = self.arg("PLACEHOLDER");
|
||||||
@ -112,7 +112,7 @@ pub trait TargetArgs: Sized {
|
|||||||
fn arg_input_file<P: AsRef<Path>>(self, path: P) -> Self {
|
fn arg_input_file<P: AsRef<Path>>(self, path: P) -> Self {
|
||||||
let mut moved = self.arg(path.as_ref());
|
let mut moved = self.arg(path.as_ref());
|
||||||
assert!(
|
assert!(
|
||||||
match moved.input_location_ref() {
|
match &moved.inner().input_location {
|
||||||
InputLocation::File { out_file } => out_file.path.as_path() == path.as_ref(),
|
InputLocation::File { out_file } => out_file.path.as_path() == path.as_ref(),
|
||||||
InputLocation::StdIn => true,
|
InputLocation::StdIn => true,
|
||||||
InputLocation::Arg { argnum: _ } => false,
|
InputLocation::Arg { argnum: _ } => false,
|
||||||
@ -137,7 +137,7 @@ pub trait TargetArgs: Sized {
|
|||||||
where
|
where
|
||||||
O: AsRef<OsStr>,
|
O: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
*self.program_mut() = Some(program.as_ref().to_owned());
|
self.inner_mut().program = Some(program.as_ref().to_owned());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ pub trait TargetArgs: Sized {
|
|||||||
where
|
where
|
||||||
O: AsRef<OsStr>,
|
O: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
self.arguments_mut().push(arg.as_ref().to_owned());
|
self.inner_mut().arguments.push(arg.as_ref().to_owned());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ pub trait TargetArgs: Sized {
|
|||||||
for arg in args {
|
for arg in args {
|
||||||
res.push(arg.as_ref().to_owned());
|
res.push(arg.as_ref().to_owned());
|
||||||
}
|
}
|
||||||
self.arguments_mut().append(&mut res);
|
self.inner_mut().arguments.append(&mut res);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ pub trait TargetArgs: Sized {
|
|||||||
let mut moved = self;
|
let mut moved = self;
|
||||||
|
|
||||||
let mut use_arg_0_as_program = false;
|
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;
|
use_arg_0_as_program = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ pub trait TargetArgs: Sized {
|
|||||||
// subsequent arguments as regular arguments
|
// subsequent arguments as regular arguments
|
||||||
use_arg_0_as_program = false;
|
use_arg_0_as_program = false;
|
||||||
} else if item.as_ref() == "@@" {
|
} else if item.as_ref() == "@@" {
|
||||||
match moved.input_location_ref().clone() {
|
match moved.inner().input_location.clone() {
|
||||||
InputLocation::File { out_file } => {
|
InputLocation::File { out_file } => {
|
||||||
// If the input file name has been modified, use this one
|
// If the input file name has been modified, use this one
|
||||||
moved = moved.arg_input_file(&out_file.path);
|
moved = moved.arg_input_file(&out_file.path);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use std::{
|
use std::{
|
||||||
io::{Read, Seek},
|
io::{Read, Seek},
|
||||||
|
ops::IndexMut,
|
||||||
os::fd::AsRawFd,
|
os::fd::AsRawFd,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -11,7 +12,10 @@ use libafl::{
|
|||||||
observers::{ObserversTuple, StdOutObserver},
|
observers::{ObserversTuple, StdOutObserver},
|
||||||
state::HasExecutions,
|
state::HasExecutions,
|
||||||
};
|
};
|
||||||
use libafl_bolts::{AsSlice, tuples::RefIndexable};
|
use libafl_bolts::{
|
||||||
|
AsSlice,
|
||||||
|
tuples::{Handle, RefIndexable},
|
||||||
|
};
|
||||||
use libnyx::NyxReturnValue;
|
use libnyx::NyxReturnValue;
|
||||||
|
|
||||||
use crate::{cmplog::CMPLOG_ENABLED, helper::NyxHelper};
|
use crate::{cmplog::CMPLOG_ENABLED, helper::NyxHelper};
|
||||||
@ -21,7 +25,7 @@ pub struct NyxExecutor<S, OT> {
|
|||||||
/// implement nyx function
|
/// implement nyx function
|
||||||
pub helper: NyxHelper,
|
pub helper: NyxHelper,
|
||||||
/// stdout
|
/// stdout
|
||||||
stdout: Option<StdOutObserver>,
|
stdout: Option<Handle<StdOutObserver>>,
|
||||||
/// stderr
|
/// stderr
|
||||||
// stderr: Option<StdErrObserver>,
|
// stderr: Option<StdErrObserver>,
|
||||||
/// observers
|
/// 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();
|
let mut stdout = Vec::new();
|
||||||
self.helper.nyx_stdout.rewind()?;
|
self.helper.nyx_stdout.rewind()?;
|
||||||
self.helper
|
self.helper
|
||||||
@ -120,7 +124,7 @@ where
|
|||||||
.read_to_end(&mut stdout)
|
.read_to_end(&mut stdout)
|
||||||
.map_err(|e| Error::illegal_state(format!("Failed to read Nyx stdout: {e}")))?;
|
.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 {
|
unsafe {
|
||||||
@ -169,7 +173,7 @@ impl<S, OT> NyxExecutor<S, OT> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct NyxExecutorBuilder {
|
pub struct NyxExecutorBuilder {
|
||||||
stdout: Option<StdOutObserver>,
|
stdout: Option<Handle<StdOutObserver>>,
|
||||||
// stderr: Option<StdErrObserver>,
|
// 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.stdout = Some(stdout);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,10 @@ use libafl::{
|
|||||||
Error, HasMetadata,
|
Error, HasMetadata,
|
||||||
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
|
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
|
||||||
events::{EventConfig, LlmpRestartingEventManager, launcher::Launcher},
|
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,
|
feedback_and_fast, feedback_or, feedback_or_fast,
|
||||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
||||||
fuzzer::{Fuzzer, StdFuzzer},
|
fuzzer::{Fuzzer, StdFuzzer},
|
||||||
@ -28,7 +31,7 @@ use libafl::{
|
|||||||
state::{HasCorpus, StdState},
|
state::{HasCorpus, StdState},
|
||||||
};
|
};
|
||||||
use libafl_bolts::{
|
use libafl_bolts::{
|
||||||
AsSliceMut, TargetArgs,
|
AsSliceMut, StdTargetArgs,
|
||||||
core_affinity::Cores,
|
core_affinity::Cores,
|
||||||
nonzero,
|
nonzero,
|
||||||
ownedref::OwnedRefMut,
|
ownedref::OwnedRefMut,
|
||||||
|
@ -131,11 +131,11 @@ where
|
|||||||
|
|
||||||
let mut out_dir = self.output_dir.clone();
|
let mut out_dir = self.output_dir.clone();
|
||||||
if fs::create_dir(&out_dir).is_err() {
|
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!(
|
assert!(
|
||||||
out_dir.is_dir(),
|
out_dir.is_dir(),
|
||||||
"Out dir at {:?} is not a valid directory!",
|
"Out dir at {} is not a valid directory!",
|
||||||
&out_dir
|
&out_dir.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let mut crashes = out_dir.clone();
|
let mut crashes = out_dir.clone();
|
||||||
|
@ -6,7 +6,7 @@ use std::{
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use nix::unistd::{dup2, execvp};
|
use nix::unistd::{dup2, execvp};
|
||||||
|
|
||||||
use crate::{args::ChildArgs, exit::Exit};
|
use crate::{args::StdChildArgs, exit::Exit};
|
||||||
|
|
||||||
pub struct Child {
|
pub struct Child {
|
||||||
argv: Vec<String>,
|
argv: Vec<String>,
|
||||||
@ -47,7 +47,7 @@ impl Child {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(args: &impl ChildArgs, fd1: RawFd, fd2: RawFd) -> Child {
|
pub fn new(args: &impl StdChildArgs, fd1: RawFd, fd2: RawFd) -> Child {
|
||||||
Child {
|
Child {
|
||||||
argv: args.argv().to_vec(),
|
argv: args.argv().to_vec(),
|
||||||
fd1,
|
fd1,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user