Add NyxCmpObserver and nyx_launcher example fuzzer (#2826)
* Add NyxCmpObserver to libafl_nyx * Add nyx_launcher example fuzzer * Cargo Format/Clippy * Adapt to naming scheme * Taplo fmt * Add hex decode function to remove hex dependency * Add nyx_launcher to CI * Remove UsesState --------- Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
parent
2e26af90db
commit
faeed19c43
1
.github/workflows/build_and_test.yml
vendored
1
.github/workflows/build_and_test.yml
vendored
@ -276,6 +276,7 @@ jobs:
|
|||||||
- ./fuzzers/forkserver/baby_fuzzer_with_forkexecutor
|
- ./fuzzers/forkserver/baby_fuzzer_with_forkexecutor
|
||||||
|
|
||||||
# Full-system
|
# Full-system
|
||||||
|
- ./fuzzers/full_system/nyx_launcher
|
||||||
- ./fuzzers/full_system/nyx_libxml2_standalone
|
- ./fuzzers/full_system/nyx_libxml2_standalone
|
||||||
- ./fuzzers/full_system/nyx_libxml2_parallel
|
- ./fuzzers/full_system/nyx_libxml2_parallel
|
||||||
|
|
||||||
|
41
fuzzers/full_system/nyx_launcher/Cargo.toml
Normal file
41
fuzzers/full_system/nyx_launcher/Cargo.toml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
[package]
|
||||||
|
name = "nyx_launcher"
|
||||||
|
version = "0.14.1"
|
||||||
|
authors = ["Konstantin Bücheler <buecheko@protonmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
std = []
|
||||||
|
|
||||||
|
## Build with a simple event manager instead of Launcher - don't fork, and crash after the first bug.
|
||||||
|
simplemgr = []
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
opt-level = 3
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
vergen = { version = "8.2.1", features = [
|
||||||
|
"build",
|
||||||
|
"cargo",
|
||||||
|
"git",
|
||||||
|
"gitcl",
|
||||||
|
"rustc",
|
||||||
|
"si",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.5.18", features = ["derive", "string"] }
|
||||||
|
libafl = { path = "../../../libafl", features = ["tui_monitor"] }
|
||||||
|
libafl_bolts = { path = "../../../libafl_bolts", features = [
|
||||||
|
"errors_backtrace",
|
||||||
|
] }
|
||||||
|
libafl_nyx = { path = "../../../libafl_nyx/" }
|
||||||
|
log = { version = "0.4.20" }
|
||||||
|
nix = { version = "0.29.0", features = ["fs"] }
|
||||||
|
rangemap = { version = "1.5.1" }
|
||||||
|
readonly = { version = "0.2.12" }
|
||||||
|
typed-builder = { version = "0.20.0" }
|
11
fuzzers/full_system/nyx_launcher/README.md
Normal file
11
fuzzers/full_system/nyx_launcher/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# nyx_launcher
|
||||||
|
|
||||||
|
Example fuzzer based on `qemu_launcher` but for Nyx.
|
||||||
|
|
||||||
|
## Run the fuzzer
|
||||||
|
|
||||||
|
Run with an existing nyx shared dir:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run -- --input input/ --output output/ --share /tmp/shareddir/ --buffer-size 4096 --cores 0-1 -v --cmplog-cores 1
|
||||||
|
```
|
42
fuzzers/full_system/nyx_launcher/src/client.rs
Normal file
42
fuzzers/full_system/nyx_launcher/src/client.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use libafl::{
|
||||||
|
corpus::{InMemoryOnDiskCorpus, OnDiskCorpus},
|
||||||
|
events::ClientDescription,
|
||||||
|
inputs::BytesInput,
|
||||||
|
monitors::Monitor,
|
||||||
|
state::StdState,
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
use libafl_bolts::rands::StdRand;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
instance::{ClientMgr, Instance},
|
||||||
|
options::FuzzerOptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
pub type ClientState =
|
||||||
|
StdState<BytesInput, InMemoryOnDiskCorpus<BytesInput>, StdRand, OnDiskCorpus<BytesInput>>;
|
||||||
|
|
||||||
|
pub struct Client<'a> {
|
||||||
|
options: &'a FuzzerOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client<'_> {
|
||||||
|
pub fn new(options: &FuzzerOptions) -> Client {
|
||||||
|
Client { options }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<M: Monitor>(
|
||||||
|
&self,
|
||||||
|
state: Option<ClientState>,
|
||||||
|
mgr: ClientMgr<M>,
|
||||||
|
client_description: ClientDescription,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let instance = Instance::builder()
|
||||||
|
.options(self.options)
|
||||||
|
.mgr(mgr)
|
||||||
|
.client_description(client_description);
|
||||||
|
|
||||||
|
instance.build().run(state)
|
||||||
|
}
|
||||||
|
}
|
153
fuzzers/full_system/nyx_launcher/src/fuzzer.rs
Normal file
153
fuzzers/full_system/nyx_launcher/src/fuzzer.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
fs::{File, OpenOptions},
|
||||||
|
io::{self, Write},
|
||||||
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use libafl::{
|
||||||
|
events::{
|
||||||
|
ClientDescription, EventConfig, Launcher, LlmpEventManager, LlmpRestartingEventManager,
|
||||||
|
MonitorTypedEventManager,
|
||||||
|
},
|
||||||
|
monitors::{tui::TuiMonitor, Monitor, MultiMonitor},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
use libafl_bolts::{
|
||||||
|
core_affinity::CoreId,
|
||||||
|
current_time,
|
||||||
|
llmp::LlmpBroker,
|
||||||
|
shmem::{ShMemProvider, StdShMemProvider},
|
||||||
|
staterestore::StateRestorer,
|
||||||
|
tuples::tuple_list,
|
||||||
|
};
|
||||||
|
#[cfg(unix)]
|
||||||
|
use {
|
||||||
|
nix::unistd::dup,
|
||||||
|
std::os::unix::io::{AsRawFd, FromRawFd},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{client::Client, options::FuzzerOptions};
|
||||||
|
|
||||||
|
pub struct Fuzzer {
|
||||||
|
options: FuzzerOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fuzzer {
|
||||||
|
pub fn new() -> Fuzzer {
|
||||||
|
let options = FuzzerOptions::parse();
|
||||||
|
options.validate();
|
||||||
|
Fuzzer { options }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fuzz(&self) -> Result<(), Error> {
|
||||||
|
if self.options.tui {
|
||||||
|
let monitor = TuiMonitor::builder()
|
||||||
|
.title("Nyx Launcher")
|
||||||
|
.version("0.14.1")
|
||||||
|
.enhanced_graphics(true)
|
||||||
|
.build();
|
||||||
|
self.launch(monitor)
|
||||||
|
} else {
|
||||||
|
let log = self.options.log.as_ref().and_then(|l| {
|
||||||
|
OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open(l)
|
||||||
|
.ok()
|
||||||
|
.map(RefCell::new)
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let stdout_cpy = RefCell::new(unsafe {
|
||||||
|
let new_fd = dup(io::stdout().as_raw_fd()).unwrap();
|
||||||
|
File::from_raw_fd(new_fd)
|
||||||
|
});
|
||||||
|
|
||||||
|
// The stats reporter for the broker
|
||||||
|
let monitor = MultiMonitor::new(|s| {
|
||||||
|
#[cfg(unix)]
|
||||||
|
writeln!(stdout_cpy.borrow_mut(), "{s}").unwrap();
|
||||||
|
#[cfg(windows)]
|
||||||
|
println!("{s}");
|
||||||
|
|
||||||
|
if let Some(log) = &log {
|
||||||
|
writeln!(log.borrow_mut(), "{:?} {}", current_time(), s).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.launch(monitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn launch<M>(&self, monitor: M) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
M: Monitor + Clone,
|
||||||
|
{
|
||||||
|
// The shared memory allocator
|
||||||
|
let mut shmem_provider = StdShMemProvider::new()?;
|
||||||
|
|
||||||
|
/* If we are running in verbose, don't provide a replacement stdout, otherwise, use /dev/null */
|
||||||
|
let stdout = if self.options.verbose {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some("/dev/null")
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = Client::new(&self.options);
|
||||||
|
|
||||||
|
if self.options.rerun_input.is_some() {
|
||||||
|
// If we want to rerun a single input but we use a restarting mgr, we'll have to create a fake restarting mgr that doesn't actually restart.
|
||||||
|
// It's not pretty but better than recompiling with simplemgr.
|
||||||
|
|
||||||
|
// Just a random number, let's hope it's free :)
|
||||||
|
let broker_port = 13120;
|
||||||
|
let _fake_broker = LlmpBroker::create_attach_to_tcp(
|
||||||
|
shmem_provider.clone(),
|
||||||
|
tuple_list!(),
|
||||||
|
broker_port,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// To rerun an input, instead of using a launcher, we create dummy parameters and run the client directly.
|
||||||
|
return client.run(
|
||||||
|
None,
|
||||||
|
MonitorTypedEventManager::<_, M>::new(LlmpRestartingEventManager::new(
|
||||||
|
LlmpEventManager::builder()
|
||||||
|
.build_on_port(
|
||||||
|
shmem_provider.clone(),
|
||||||
|
broker_port,
|
||||||
|
EventConfig::AlwaysUnique,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
StateRestorer::new(shmem_provider.new_shmem(0x1000).unwrap()),
|
||||||
|
)),
|
||||||
|
ClientDescription::new(0, 0, CoreId(0)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "simplemgr")]
|
||||||
|
return client.run(None, SimpleEventManager::new(monitor), CoreId(0));
|
||||||
|
|
||||||
|
// Build and run a Launcher
|
||||||
|
match Launcher::builder()
|
||||||
|
.shmem_provider(shmem_provider)
|
||||||
|
.broker_port(self.options.port)
|
||||||
|
.configuration(EventConfig::from_build_id())
|
||||||
|
.monitor(monitor)
|
||||||
|
.run_client(|s, m, c| client.run(s, MonitorTypedEventManager::<_, M>::new(m), c))
|
||||||
|
.cores(&self.options.cores)
|
||||||
|
.stdout_file(stdout)
|
||||||
|
.stderr_file(stdout)
|
||||||
|
.build()
|
||||||
|
.launch()
|
||||||
|
{
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(Error::ShuttingDown) => {
|
||||||
|
println!("Fuzzing stopped by user. Good bye.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(err) => Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
258
fuzzers/full_system/nyx_launcher/src/instance.rs
Normal file
258
fuzzers/full_system/nyx_launcher/src/instance.rs
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
use std::{marker::PhantomData, process};
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
|
||||||
|
events::{
|
||||||
|
ClientDescription, EventRestarter, LlmpRestartingEventManager, MonitorTypedEventManager,
|
||||||
|
NopEventManager,
|
||||||
|
},
|
||||||
|
executors::{Executor, ShadowExecutor},
|
||||||
|
feedback_and_fast, feedback_or, feedback_or_fast,
|
||||||
|
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
||||||
|
fuzzer::{Evaluator, Fuzzer, StdFuzzer},
|
||||||
|
inputs::BytesInput,
|
||||||
|
monitors::Monitor,
|
||||||
|
mutators::{
|
||||||
|
havoc_mutations, tokens_mutations, I2SRandReplace, StdMOptMutator, StdScheduledMutator,
|
||||||
|
Tokens,
|
||||||
|
},
|
||||||
|
observers::{CanTrack, HitcountsMapObserver, StdMapObserver, TimeObserver},
|
||||||
|
schedulers::{
|
||||||
|
powersched::PowerSchedule, IndexesLenTimeMinimizerScheduler, PowerQueueScheduler,
|
||||||
|
},
|
||||||
|
stages::{
|
||||||
|
power::StdPowerMutationalStage, CalibrationStage, ShadowTracingStage, StagesTuple,
|
||||||
|
StdMutationalStage,
|
||||||
|
},
|
||||||
|
state::{HasCorpus, HasMaxSize, StdState},
|
||||||
|
Error, HasMetadata, NopFuzzer,
|
||||||
|
};
|
||||||
|
use libafl_bolts::{
|
||||||
|
current_nanos,
|
||||||
|
rands::StdRand,
|
||||||
|
shmem::StdShMemProvider,
|
||||||
|
tuples::{tuple_list, Merge},
|
||||||
|
};
|
||||||
|
use libafl_nyx::{
|
||||||
|
cmplog::NyxCmpObserver, executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings,
|
||||||
|
};
|
||||||
|
use typed_builder::TypedBuilder;
|
||||||
|
|
||||||
|
use crate::options::FuzzerOptions;
|
||||||
|
|
||||||
|
pub type ClientState =
|
||||||
|
StdState<BytesInput, InMemoryOnDiskCorpus<BytesInput>, StdRand, OnDiskCorpus<BytesInput>>;
|
||||||
|
|
||||||
|
pub type ClientMgr<M> =
|
||||||
|
MonitorTypedEventManager<LlmpRestartingEventManager<(), ClientState, StdShMemProvider>, M>;
|
||||||
|
|
||||||
|
#[derive(TypedBuilder)]
|
||||||
|
pub struct Instance<'a, M: Monitor> {
|
||||||
|
options: &'a FuzzerOptions,
|
||||||
|
/// The harness. We create it before forking, then `take()` it inside the client.
|
||||||
|
mgr: ClientMgr<M>,
|
||||||
|
client_description: ClientDescription,
|
||||||
|
#[builder(default=PhantomData)]
|
||||||
|
phantom: PhantomData<M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Monitor> Instance<'_, M> {
|
||||||
|
pub fn run(mut self, state: Option<ClientState>) -> Result<(), Error> {
|
||||||
|
let parent_cpu_id = self
|
||||||
|
.options
|
||||||
|
.cores
|
||||||
|
.ids
|
||||||
|
.first()
|
||||||
|
.expect("unable to get first core id");
|
||||||
|
|
||||||
|
let settings = NyxSettings::builder()
|
||||||
|
.cpu_id(self.client_description.core_id().0)
|
||||||
|
.parent_cpu_id(Some(parent_cpu_id.0))
|
||||||
|
.input_buffer_size(self.options.buffer_size)
|
||||||
|
.timeout_secs(0)
|
||||||
|
.timeout_micro_secs(self.options.timeout)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let helper = NyxHelper::new(self.options.shared_dir(), settings)?;
|
||||||
|
|
||||||
|
let trace_observer = HitcountsMapObserver::new(unsafe {
|
||||||
|
StdMapObserver::from_mut_ptr("trace", helper.bitmap_buffer, helper.bitmap_size)
|
||||||
|
})
|
||||||
|
.track_indices();
|
||||||
|
|
||||||
|
// Create an observation channel to keep track of the execution time
|
||||||
|
let time_observer = TimeObserver::new("time");
|
||||||
|
|
||||||
|
let map_feedback = MaxMapFeedback::new(&trace_observer);
|
||||||
|
|
||||||
|
// let stdout_observer = StdOutObserver::new("hprintf_output");
|
||||||
|
|
||||||
|
let calibration = CalibrationStage::new(&map_feedback);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
map_feedback,
|
||||||
|
// Time feedback, this one does not need a feedback state
|
||||||
|
TimeFeedback::new(&time_observer),
|
||||||
|
// Append stdout to metadata
|
||||||
|
// StdOutToMetadataFeedback::new(&stdout_observer)
|
||||||
|
);
|
||||||
|
|
||||||
|
// A feedback to choose if an input is a solution or not
|
||||||
|
let mut objective = feedback_and_fast!(
|
||||||
|
// CrashFeedback::new(),
|
||||||
|
feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()),
|
||||||
|
// Take it only if trigger new coverage over crashes
|
||||||
|
// For deduplication
|
||||||
|
MaxMapFeedback::with_name("mapfeedback_metadata_objective", &trace_observer)
|
||||||
|
);
|
||||||
|
|
||||||
|
// If not restarting, create a State from scratch
|
||||||
|
let mut state = match state {
|
||||||
|
Some(x) => x,
|
||||||
|
None => {
|
||||||
|
StdState::new(
|
||||||
|
// RNG
|
||||||
|
StdRand::with_seed(current_nanos()),
|
||||||
|
// Corpus that will be evolved, we keep it in memory for performance
|
||||||
|
InMemoryOnDiskCorpus::no_meta(
|
||||||
|
self.options.queue_dir(self.client_description.core_id()),
|
||||||
|
)?,
|
||||||
|
// Corpus in which we store solutions (crashes in this example),
|
||||||
|
// on disk so the user can get them after stopping the fuzzer
|
||||||
|
OnDiskCorpus::new(self.options.crashes_dir(self.client_description.core_id()))?,
|
||||||
|
// States of the feedbacks.
|
||||||
|
// The feedbacks can report the data that should persist in the State.
|
||||||
|
&mut feedback,
|
||||||
|
// Same for objective feedbacks
|
||||||
|
&mut objective,
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// A minimization+queue policy to get testcasess from the corpus
|
||||||
|
let scheduler = IndexesLenTimeMinimizerScheduler::new(
|
||||||
|
&trace_observer,
|
||||||
|
PowerQueueScheduler::new(&mut state, &trace_observer, PowerSchedule::fast()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let observers = tuple_list!(trace_observer, time_observer); // stdout_observer);
|
||||||
|
|
||||||
|
let mut tokens = Tokens::new();
|
||||||
|
|
||||||
|
if let Some(tokenfile) = &self.options.tokens {
|
||||||
|
tokens.add_from_file(tokenfile)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.add_metadata(tokens);
|
||||||
|
|
||||||
|
state.set_max_size(self.options.buffer_size);
|
||||||
|
|
||||||
|
// A fuzzer with feedbacks and a corpus scheduler
|
||||||
|
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||||
|
|
||||||
|
if let Some(rerun_input) = &self.options.rerun_input {
|
||||||
|
// TODO: We might want to support non-bytes inputs at some point?
|
||||||
|
let bytes = std::fs::read(rerun_input)
|
||||||
|
.unwrap_or_else(|_| panic!("Could not load file {rerun_input:?}"));
|
||||||
|
let input = BytesInput::new(bytes);
|
||||||
|
|
||||||
|
let mut executor = NyxExecutor::builder().build(helper, observers);
|
||||||
|
|
||||||
|
let exit_kind = executor
|
||||||
|
.run_target(
|
||||||
|
&mut NopFuzzer::new(),
|
||||||
|
&mut state,
|
||||||
|
&mut NopEventManager::new(),
|
||||||
|
&input,
|
||||||
|
)
|
||||||
|
.expect("Error running target");
|
||||||
|
println!("Rerun finished with ExitKind {:?}", exit_kind);
|
||||||
|
// We're done :)
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self
|
||||||
|
.options
|
||||||
|
.is_cmplog_core(self.client_description.core_id())
|
||||||
|
{
|
||||||
|
let cmplog_observer = NyxCmpObserver::new("cmplog", helper.redqueen_path.clone(), true);
|
||||||
|
|
||||||
|
let executor = NyxExecutor::builder().build(helper, observers);
|
||||||
|
|
||||||
|
// Show the cmplog observer
|
||||||
|
let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer));
|
||||||
|
|
||||||
|
// Setup a randomic Input2State stage
|
||||||
|
let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(
|
||||||
|
I2SRandReplace::new()
|
||||||
|
)));
|
||||||
|
|
||||||
|
let tracing = ShadowTracingStage::new(&mut executor);
|
||||||
|
|
||||||
|
// Setup a MOPT mutator
|
||||||
|
let mutator = StdMOptMutator::new(
|
||||||
|
&mut state,
|
||||||
|
havoc_mutations().merge(tokens_mutations()),
|
||||||
|
7,
|
||||||
|
5,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let power: StdPowerMutationalStage<_, _, BytesInput, _, _, _> =
|
||||||
|
StdPowerMutationalStage::new(mutator);
|
||||||
|
|
||||||
|
// The order of the stages matter!
|
||||||
|
let mut stages = tuple_list!(calibration, tracing, i2s, power);
|
||||||
|
|
||||||
|
return self.fuzz(&mut state, &mut fuzzer, &mut executor, &mut stages);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut executor = NyxExecutor::builder().build(helper, observers);
|
||||||
|
|
||||||
|
// Setup an havoc mutator with a mutational stage
|
||||||
|
let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
|
||||||
|
|
||||||
|
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
|
||||||
|
|
||||||
|
self.fuzz(&mut state, &mut fuzzer, &mut executor, &mut stages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fuzz<Z, E, ST>(
|
||||||
|
&mut self,
|
||||||
|
state: &mut ClientState,
|
||||||
|
fuzzer: &mut Z,
|
||||||
|
executor: &mut E,
|
||||||
|
stages: &mut ST,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
Z: Fuzzer<E, ClientMgr<M>, ClientState, ST>
|
||||||
|
+ Evaluator<E, ClientMgr<M>, BytesInput, ClientState>,
|
||||||
|
ST: StagesTuple<E, ClientMgr<M>, ClientState, Z>,
|
||||||
|
{
|
||||||
|
let corpus_dirs = [self.options.input_dir()];
|
||||||
|
|
||||||
|
if state.must_load_initial_inputs() {
|
||||||
|
state
|
||||||
|
.load_initial_inputs(fuzzer, executor, &mut self.mgr, &corpus_dirs)
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
println!("Failed to load initial corpus at {corpus_dirs:?}");
|
||||||
|
process::exit(0);
|
||||||
|
});
|
||||||
|
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(iters) = self.options.iterations {
|
||||||
|
fuzzer.fuzz_loop_for(stages, executor, state, &mut self.mgr, iters)?;
|
||||||
|
|
||||||
|
// It's important, that we store the state before restarting!
|
||||||
|
// Else, the parent will not respawn a new child and quit.
|
||||||
|
self.mgr.on_restart(state)?;
|
||||||
|
} else {
|
||||||
|
fuzzer.fuzz_loop(stages, executor, state, &mut self.mgr)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
22
fuzzers/full_system/nyx_launcher/src/main.rs
Normal file
22
fuzzers/full_system/nyx_launcher/src/main.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod client;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod fuzzer;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod instance;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod options;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use crate::fuzzer::Fuzzer;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn main() {
|
||||||
|
Fuzzer::new().fuzz().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
pub fn main() {
|
||||||
|
panic!("libafl_nyx is only supported on linux!");
|
||||||
|
}
|
112
fuzzers/full_system/nyx_launcher/src/options.rs
Normal file
112
fuzzers/full_system/nyx_launcher/src/options.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::{error::ErrorKind, CommandFactory, Parser};
|
||||||
|
use libafl_bolts::core_affinity::{CoreId, Cores};
|
||||||
|
|
||||||
|
#[readonly::make]
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(author, about, long_about = None)]
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
#[command(
|
||||||
|
name = format!("nyx_launcher"),
|
||||||
|
about,
|
||||||
|
long_about = "Binary fuzzer using NYX"
|
||||||
|
)]
|
||||||
|
pub struct FuzzerOptions {
|
||||||
|
#[arg(short, long, help = "Input directory")]
|
||||||
|
pub input: String,
|
||||||
|
|
||||||
|
#[arg(short, long, help = "Output directory")]
|
||||||
|
pub output: String,
|
||||||
|
|
||||||
|
#[arg(short, long, help = "Shared directory")]
|
||||||
|
pub share: String,
|
||||||
|
|
||||||
|
#[arg(short, long, help = "Input buffer size")]
|
||||||
|
pub buffer_size: usize,
|
||||||
|
|
||||||
|
#[arg(short = 'x', long, help = "Tokens file")]
|
||||||
|
pub tokens: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long, help = "Log file")]
|
||||||
|
pub log: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long, help = "Timeout in milli-seconds", default_value = "1000")]
|
||||||
|
pub timeout: u32,
|
||||||
|
|
||||||
|
#[arg(long = "port", help = "Broker port", default_value_t = 1337_u16)]
|
||||||
|
pub port: u16,
|
||||||
|
|
||||||
|
#[arg(long, help = "Cpu cores to use", default_value = "all", value_parser = Cores::from_cmdline)]
|
||||||
|
pub cores: Cores,
|
||||||
|
|
||||||
|
#[arg(long, help = "Cpu cores to use for CmpLog", value_parser = Cores::from_cmdline)]
|
||||||
|
pub cmplog_cores: Option<Cores>,
|
||||||
|
|
||||||
|
#[clap(short, long, help = "Enable output from the fuzzer clients")]
|
||||||
|
pub verbose: bool,
|
||||||
|
|
||||||
|
#[clap(long, help = "Enable AFL++ style output", conflicts_with = "verbose")]
|
||||||
|
pub tui: bool,
|
||||||
|
|
||||||
|
#[arg(long = "iterations", help = "Maximum numer of iterations")]
|
||||||
|
pub iterations: Option<u64>,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
short = 'r',
|
||||||
|
help = "An input to rerun, instead of starting to fuzz. Will ignore all other settings apart from -d."
|
||||||
|
)]
|
||||||
|
pub rerun_input: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FuzzerOptions {
|
||||||
|
pub fn input_dir(&self) -> PathBuf {
|
||||||
|
PathBuf::from(&self.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shared_dir(&self) -> PathBuf {
|
||||||
|
PathBuf::from(&self.share)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output_dir(&self, core_id: CoreId) -> PathBuf {
|
||||||
|
let mut dir = PathBuf::from(&self.output);
|
||||||
|
dir.push(format!("cpu_{:03}", core_id.0));
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_dir(&self, core_id: CoreId) -> PathBuf {
|
||||||
|
let mut dir = self.output_dir(core_id).clone();
|
||||||
|
dir.push("queue");
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn crashes_dir(&self, core_id: CoreId) -> PathBuf {
|
||||||
|
let mut dir = self.output_dir(core_id).clone();
|
||||||
|
dir.push("crashes");
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_cmplog_core(&self, core_id: CoreId) -> bool {
|
||||||
|
self.cmplog_cores
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|c| c.contains(core_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate(&self) {
|
||||||
|
if let Some(cmplog_cores) = &self.cmplog_cores {
|
||||||
|
for id in &cmplog_cores.ids {
|
||||||
|
if !self.cores.contains(*id) {
|
||||||
|
let mut cmd = FuzzerOptions::command();
|
||||||
|
cmd.error(
|
||||||
|
ErrorKind::ValueValidation,
|
||||||
|
format!(
|
||||||
|
"Cmplog cores ({}) must be a subset of total cores ({})",
|
||||||
|
cmplog_cores.cmdline, self.cores.cmdline
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,6 +38,11 @@ libafl_targets = { workspace = true, default-features = true, features = [
|
|||||||
|
|
||||||
nix = { workspace = true, default-features = true, features = ["fs"] }
|
nix = { workspace = true, default-features = true, features = ["fs"] }
|
||||||
typed-builder = { workspace = true }
|
typed-builder = { workspace = true }
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
regex = "1.11.1"
|
||||||
|
serde = { version = "1.0.210", default-features = false, features = [
|
||||||
|
"alloc",
|
||||||
|
] } # serialization lib
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
232
libafl_nyx/src/cmplog.rs
Normal file
232
libafl_nyx/src/cmplog.rs
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
//! The Nyx `CmpLog` Observer
|
||||||
|
//!
|
||||||
|
//! Reads and parses the redqueen results written by QEMU-Nyx and adds them to the state as `CmpValuesMetadata`.
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use libafl::{
|
||||||
|
executors::ExitKind,
|
||||||
|
observers::{CmpValues, CmpValuesMetadata, Observer},
|
||||||
|
state::HasExecutions,
|
||||||
|
Error, HasMetadata,
|
||||||
|
};
|
||||||
|
use libafl_bolts::Named;
|
||||||
|
pub use libafl_targets::{
|
||||||
|
cmps::{
|
||||||
|
__libafl_targets_cmplog_instructions, __libafl_targets_cmplog_routines, CMPLOG_ENABLED,
|
||||||
|
},
|
||||||
|
CmpLogMap, CmpLogObserver, CMPLOG_MAP_H, CMPLOG_MAP_PTR, CMPLOG_MAP_SIZE, CMPLOG_MAP_W,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A [`CmpObserver`] observer for Nyx
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct NyxCmpObserver {
|
||||||
|
/// Observer name
|
||||||
|
name: Cow<'static, str>,
|
||||||
|
/// Path to redqueen results file
|
||||||
|
path: Cow<'static, str>,
|
||||||
|
add_meta: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NyxCmpObserver {
|
||||||
|
/// Creates a new [`struct@NyxCmpObserver`] with the given filepath.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(name: &'static str, path: String, add_meta: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
name: Cow::from(name),
|
||||||
|
path: Cow::from(path),
|
||||||
|
add_meta,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, S> Observer<I, S> for NyxCmpObserver
|
||||||
|
where
|
||||||
|
S: HasMetadata + HasExecutions,
|
||||||
|
I: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
|
||||||
|
unsafe {
|
||||||
|
CMPLOG_ENABLED = 1;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_exec(&mut self, state: &mut S, _input: &I, _exit_kind: &ExitKind) -> Result<(), Error> {
|
||||||
|
unsafe {
|
||||||
|
CMPLOG_ENABLED = 0;
|
||||||
|
}
|
||||||
|
if self.add_meta {
|
||||||
|
let meta = state.metadata_or_insert_with(CmpValuesMetadata::new);
|
||||||
|
let rq_data = parse_redqueen_data(&std::fs::read_to_string(self.path.as_ref())?);
|
||||||
|
for event in rq_data.bps {
|
||||||
|
if let Ok(cmp_value) = event.try_into() {
|
||||||
|
meta.list.push(cmp_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Named for NyxCmpObserver {
|
||||||
|
fn name(&self) -> &Cow<'static, str> {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on https://github.com/nyx-fuzz/spec-fuzzer/blob/main/rust_fuzzer/src/runner.rs
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum RedqueenBpType {
|
||||||
|
Str,
|
||||||
|
Cmp,
|
||||||
|
Sub,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RedqueenBpType {
|
||||||
|
fn new(data: &str) -> Result<RedqueenBpType, String> {
|
||||||
|
match data {
|
||||||
|
"STR" => Ok(Self::Str),
|
||||||
|
"CMP" => Ok(Self::Cmp),
|
||||||
|
"SUB" => Ok(Self::Sub),
|
||||||
|
_ => Err("Unknown redqueen type".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
struct RedqueenEvent {
|
||||||
|
pub addr: u64,
|
||||||
|
pub bp_type: RedqueenBpType,
|
||||||
|
pub size: usize,
|
||||||
|
pub lhs: Vec<u8>,
|
||||||
|
pub rhs: Vec<u8>,
|
||||||
|
pub imm: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RedqueenEvent {
|
||||||
|
fn new(line: &str) -> Result<Self, String> {
|
||||||
|
lazy_static! {
|
||||||
|
static ref RE: regex::Regex = regex::Regex::new(
|
||||||
|
r"([0-9a-fA-F]+)\s+(CMP|SUB|STR)\s+(\d+)\s+([0-9a-fA-F]+)-([0-9a-fA-F]+)(\sIMM)?"
|
||||||
|
)
|
||||||
|
.expect("Invalid regex pattern");
|
||||||
|
}
|
||||||
|
|
||||||
|
let captures = RE
|
||||||
|
.captures(line)
|
||||||
|
.ok_or_else(|| format!("Failed to parse Redqueen line: '{line}'"))?;
|
||||||
|
|
||||||
|
let addr_s = captures.get(1).ok_or("Missing address field")?.as_str();
|
||||||
|
let type_s = captures.get(2).ok_or("Missing type field")?.as_str();
|
||||||
|
let size_s = captures.get(3).ok_or("Missing size field")?.as_str();
|
||||||
|
let lhs_s = captures.get(4).ok_or("Missing LHS field")?.as_str();
|
||||||
|
let rhs_s = captures.get(5).ok_or("Missing RHS field")?.as_str();
|
||||||
|
let imm = captures.get(6).is_some_and(|_x| true);
|
||||||
|
|
||||||
|
let addr =
|
||||||
|
u64::from_str_radix(addr_s, 16).map_err(|_| format!("Invalid address: '{addr_s}'"))?;
|
||||||
|
let bp_type = RedqueenBpType::new(type_s)
|
||||||
|
.map_err(|e| format!("Invalid redqueen type: '{type_s}' - {e}"))?;
|
||||||
|
let size = size_s
|
||||||
|
.parse::<usize>()
|
||||||
|
.map_err(|_| format!("Invalid size: '{size_s}'"))?;
|
||||||
|
let lhs = hex_to_bytes(lhs_s).ok_or("Decoding LHS failed")?;
|
||||||
|
let rhs = hex_to_bytes(rhs_s).ok_or("Decoding RHS failed")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
addr,
|
||||||
|
bp_type,
|
||||||
|
size,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
imm,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hex_to_bytes(s: &str) -> Option<Vec<u8>> {
|
||||||
|
if s.len() % 2 == 0 {
|
||||||
|
(0..s.len())
|
||||||
|
.step_by(2)
|
||||||
|
.map(|i| {
|
||||||
|
s.get(i..i + 2)
|
||||||
|
.and_then(|sub| u8::from_str_radix(sub, 16).ok())
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
struct RedqueenInfo {
|
||||||
|
bps: Vec<RedqueenEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_redqueen_data(data: &str) -> RedqueenInfo {
|
||||||
|
let bps = data
|
||||||
|
.lines()
|
||||||
|
.filter_map(|line| RedqueenEvent::new(line).ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
RedqueenInfo { bps }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<CmpValues> for RedqueenEvent {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<CmpValues, Self::Error> {
|
||||||
|
match self.bp_type {
|
||||||
|
RedqueenBpType::Cmp => match self.size {
|
||||||
|
8 => Ok(CmpValues::U8((
|
||||||
|
*self.rhs.first().ok_or("Invalid RHS length for U8")?,
|
||||||
|
*self.lhs.first().ok_or("Invalid LHS length for U8")?,
|
||||||
|
self.imm,
|
||||||
|
))),
|
||||||
|
16 => Ok(CmpValues::U16((
|
||||||
|
u16::from_be_bytes(
|
||||||
|
self.rhs
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| "Invalid RHS length for U16")?,
|
||||||
|
),
|
||||||
|
u16::from_be_bytes(
|
||||||
|
self.lhs
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| "Invalid LHS length for U16")?,
|
||||||
|
),
|
||||||
|
self.imm,
|
||||||
|
))),
|
||||||
|
32 => Ok(CmpValues::U32((
|
||||||
|
u32::from_be_bytes(
|
||||||
|
self.rhs
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| "Invalid RHS length for U32")?,
|
||||||
|
),
|
||||||
|
u32::from_be_bytes(
|
||||||
|
self.lhs
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| "Invalid LHS length for U32")?,
|
||||||
|
),
|
||||||
|
self.imm,
|
||||||
|
))),
|
||||||
|
64 => Ok(CmpValues::U64((
|
||||||
|
u64::from_be_bytes(
|
||||||
|
self.rhs
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| "Invalid RHS length for U64")?,
|
||||||
|
),
|
||||||
|
u64::from_be_bytes(
|
||||||
|
self.lhs
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| "Invalid LHS length for U64")?,
|
||||||
|
),
|
||||||
|
self.imm,
|
||||||
|
))),
|
||||||
|
_ => Err("Invalid size".to_string()),
|
||||||
|
},
|
||||||
|
// TODO: Add encoding for `STR` and `SUB`
|
||||||
|
_ => Err("Redqueen type not implemented".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ use libafl::{
|
|||||||
use libafl_bolts::{tuples::RefIndexable, AsSlice};
|
use libafl_bolts::{tuples::RefIndexable, AsSlice};
|
||||||
use libnyx::NyxReturnValue;
|
use libnyx::NyxReturnValue;
|
||||||
|
|
||||||
use crate::helper::NyxHelper;
|
use crate::{cmplog::CMPLOG_ENABLED, helper::NyxHelper};
|
||||||
|
|
||||||
/// executor for nyx standalone mode
|
/// executor for nyx standalone mode
|
||||||
pub struct NyxExecutor<S, OT> {
|
pub struct NyxExecutor<S, OT> {
|
||||||
@ -80,6 +80,13 @@ where
|
|||||||
self.helper.nyx_process.set_input(buffer, size);
|
self.helper.nyx_process.set_input(buffer, size);
|
||||||
self.helper.nyx_process.set_hprintf_fd(hprintf_fd);
|
self.helper.nyx_process.set_hprintf_fd(hprintf_fd);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if CMPLOG_ENABLED == 1 {
|
||||||
|
self.helper.nyx_process.option_set_redqueen_mode(true);
|
||||||
|
self.helper.nyx_process.option_apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// exec will take care of trace_bits, so no need to reset
|
// exec will take care of trace_bits, so no need to reset
|
||||||
let exit_kind = match self.helper.nyx_process.exec() {
|
let exit_kind = match self.helper.nyx_process.exec() {
|
||||||
NyxReturnValue::Normal => ExitKind::Ok,
|
NyxReturnValue::Normal => ExitKind::Ok,
|
||||||
@ -116,6 +123,13 @@ where
|
|||||||
ob.observe_stdout(&stdout);
|
ob.observe_stdout(&stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if CMPLOG_ENABLED == 1 {
|
||||||
|
self.helper.nyx_process.option_set_redqueen_mode(false);
|
||||||
|
self.helper.nyx_process.option_apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(exit_kind)
|
Ok(exit_kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ use crate::settings::NyxSettings;
|
|||||||
pub struct NyxHelper {
|
pub struct NyxHelper {
|
||||||
pub nyx_process: NyxProcess,
|
pub nyx_process: NyxProcess,
|
||||||
pub nyx_stdout: File,
|
pub nyx_stdout: File,
|
||||||
|
pub redqueen_path: String,
|
||||||
|
|
||||||
pub timeout: Duration,
|
pub timeout: Duration,
|
||||||
|
|
||||||
@ -71,9 +72,16 @@ impl NyxHelper {
|
|||||||
let mut timeout = Duration::from_secs(u64::from(settings.timeout_secs));
|
let mut timeout = Duration::from_secs(u64::from(settings.timeout_secs));
|
||||||
timeout += Duration::from_micros(u64::from(settings.timeout_micro_secs));
|
timeout += Duration::from_micros(u64::from(settings.timeout_micro_secs));
|
||||||
|
|
||||||
|
let redqueen_path = format!(
|
||||||
|
"{}/redqueen_workdir_{}/redqueen_results.txt",
|
||||||
|
nyx_config.workdir_path(),
|
||||||
|
nyx_config.worker_id()
|
||||||
|
);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
nyx_process,
|
nyx_process,
|
||||||
nyx_stdout,
|
nyx_stdout,
|
||||||
|
redqueen_path,
|
||||||
timeout,
|
timeout,
|
||||||
bitmap_size,
|
bitmap_size,
|
||||||
bitmap_buffer,
|
bitmap_buffer,
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
pub mod cmplog;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
pub mod executor;
|
pub mod executor;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub mod helper;
|
pub mod helper;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user