libafl-fuzz: introduce nyx_mode (#2503)
* add nyx_mode * fix frida ci? * damn clippy * clippy
This commit is contained in:
parent
f7f8dff6cd
commit
87f5f21e01
@ -31,6 +31,7 @@ memmap2 = "0.9.4"
|
||||
nix = { version = "0.29.0", features = ["fs"] }
|
||||
regex = "1.10.5"
|
||||
serde = { version = "1.0.117", features = ["derive"] }
|
||||
libafl_nyx = { path = "../../../libafl_nyx" }
|
||||
|
||||
[features]
|
||||
default = ["track_hit_feedbacks"]
|
||||
|
@ -226,6 +226,36 @@ test -n "$( ls ./test/output-unicorn-cmpcov/fuzzer_main/queue/id:000002* 2>/dev/
|
||||
'''
|
||||
dependencies = ["build_libafl_fuzz", "build_afl", "build_unicorn_mode"]
|
||||
|
||||
[tasks.test_nyx_mode]
|
||||
script_runner = "@shell"
|
||||
script = '''
|
||||
export AFL_PATH=${AFL_DIR}
|
||||
export AFL_CORES=0
|
||||
export AFL_STATS_INTERVAL=1
|
||||
export AFL_DEBUG=1
|
||||
export LIBAFL_DEBUG_OUTPUT=1
|
||||
AFL_PATH=${AFL_DIR} ${AFL_CC_PATH} ./test/test-instr.c -o ./test/out-instr
|
||||
rm -rf ./test/nyx-test
|
||||
cd ../../../libafl_nyx
|
||||
rm -rf packer
|
||||
git clone https://github.com/nyx-fuzz/packer.git
|
||||
python3 packer/packer/nyx_packer.py \
|
||||
../fuzzers/forkserver/libafl-fuzz/test/out-instr \
|
||||
../fuzzers/forkserver/libafl-fuzz/test/out-nyx \
|
||||
afl \
|
||||
instrumentation \
|
||||
--fast_reload_mode \
|
||||
--purge
|
||||
python3 packer/packer/nyx_config_gen.py ../fuzzers/forkserver/libafl-fuzz/test/out-nyx Kernel
|
||||
cd ../fuzzers/forkserver/libafl-fuzz/
|
||||
timeout 15s ${FUZZER} -i ./test/seeds_nyx -o ./test/output-nyx -X -- ./test/out-nyx
|
||||
test -n "$( ls ./test/output-nyx/fuzzer_main/queue/id:000003* 2>/dev/null )" || {
|
||||
echo "No new corpus entries found for Nyx mode!"
|
||||
exit 1
|
||||
}
|
||||
'''
|
||||
dependencies = ["build_afl", "build_libafl_fuzz"]
|
||||
|
||||
[tasks.clean]
|
||||
linux_alias = "clean_unix"
|
||||
mac_alias = "clean_unix"
|
||||
|
@ -1,10 +1,17 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
marker::PhantomData,
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use libafl::Error;
|
||||
use libafl::{
|
||||
executors::{Executor, ExitKind, HasObservers, HasTimeout},
|
||||
observers::ObserversTuple,
|
||||
state::{State, UsesState},
|
||||
Error,
|
||||
};
|
||||
use libafl_bolts::tuples::RefIndexable;
|
||||
use memmap2::{Mmap, MmapOptions};
|
||||
use nix::libc::{S_IRUSR, S_IXUSR};
|
||||
|
||||
@ -244,3 +251,87 @@ fn check_file_found(file: &Path, perm: u32) -> bool {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub enum SupportedExecutors<S, OT, FSV, NYX> {
|
||||
Forkserver(FSV, PhantomData<(S, OT)>),
|
||||
#[cfg(target_os = "linux")]
|
||||
Nyx(NYX),
|
||||
}
|
||||
|
||||
impl<S, OT, FSV, NYX> UsesState for SupportedExecutors<S, OT, FSV, NYX>
|
||||
where
|
||||
S: State,
|
||||
{
|
||||
type State = S;
|
||||
}
|
||||
|
||||
impl<S, OT, FSV, NYX, EM, Z> Executor<EM, Z> for SupportedExecutors<S, OT, FSV, NYX>
|
||||
where
|
||||
S: State,
|
||||
Z: UsesState<State = S>,
|
||||
EM: UsesState<State = S>,
|
||||
FSV: Executor<EM, Z, State = S>,
|
||||
NYX: Executor<EM, Z, State = S>,
|
||||
{
|
||||
fn run_target(
|
||||
&mut self,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
mgr: &mut EM,
|
||||
input: &S::Input,
|
||||
) -> Result<ExitKind, Error> {
|
||||
match self {
|
||||
Self::Forkserver(fsrv, _) => fsrv.run_target(fuzzer, state, mgr, input),
|
||||
#[cfg(target_os = "linux")]
|
||||
Self::Nyx(nyx) => nyx.run_target(fuzzer, state, mgr, input),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, OT, FSV, NYX> HasObservers for SupportedExecutors<S, OT, FSV, NYX>
|
||||
where
|
||||
OT: ObserversTuple<S::Input, S>,
|
||||
S: State,
|
||||
FSV: HasObservers<Observers = OT>,
|
||||
NYX: HasObservers<Observers = OT>,
|
||||
{
|
||||
type Observers = OT;
|
||||
#[inline]
|
||||
fn observers(&self) -> RefIndexable<&Self::Observers, Self::Observers> {
|
||||
match self {
|
||||
Self::Forkserver(fsrv, _) => fsrv.observers(),
|
||||
#[cfg(target_os = "linux")]
|
||||
Self::Nyx(nyx) => nyx.observers(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn observers_mut(&mut self) -> RefIndexable<&mut Self::Observers, Self::Observers> {
|
||||
match self {
|
||||
Self::Forkserver(fsrv, _) => fsrv.observers_mut(),
|
||||
#[cfg(target_os = "linux")]
|
||||
Self::Nyx(nyx) => nyx.observers_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, OT, FSV, NYX> HasTimeout for SupportedExecutors<S, OT, FSV, NYX>
|
||||
where
|
||||
FSV: HasTimeout,
|
||||
NYX: HasTimeout,
|
||||
{
|
||||
fn set_timeout(&mut self, timeout: std::time::Duration) {
|
||||
match self {
|
||||
Self::Forkserver(fsrv, _) => fsrv.set_timeout(timeout),
|
||||
#[cfg(target_os = "linux")]
|
||||
Self::Nyx(nyx) => nyx.set_timeout(timeout),
|
||||
}
|
||||
}
|
||||
fn timeout(&self) -> std::time::Duration {
|
||||
match self {
|
||||
Self::Forkserver(fsrv, _) => fsrv.timeout(),
|
||||
#[cfg(target_os = "linux")]
|
||||
Self::Nyx(nyx) => nyx.timeout(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,13 +49,14 @@ use libafl_bolts::{
|
||||
tuples::{tuple_list, Handled, Merge},
|
||||
AsSliceMut,
|
||||
};
|
||||
use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings};
|
||||
use libafl_targets::{cmps::AFLppCmpLogMap, AFLppCmpLogObserver, AFLppCmplogTracingStage};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
corpus::{set_corpus_filepath, set_solution_filepath},
|
||||
env_parser::AFL_DEFAULT_MAP_SIZE,
|
||||
executor::find_afl_binary,
|
||||
executor::{find_afl_binary, SupportedExecutors},
|
||||
feedback::{
|
||||
filepath::CustomFilepathToTestcaseFeedback, persistent_record::PersitentRecordFeedback,
|
||||
seed::SeedFeedback,
|
||||
@ -94,10 +95,38 @@ where
|
||||
shmem.write_to_env(SHMEM_ENV_VAR).unwrap();
|
||||
let shmem_buf = shmem.as_slice_mut();
|
||||
|
||||
// Create an observation channel to keep track of edges hit.
|
||||
let edges_observer = unsafe {
|
||||
HitcountsMapObserver::new(StdMapObserver::new("edges", shmem_buf)).track_indices()
|
||||
// If we are in Nyx Mode, we need to use a different map observer.
|
||||
#[cfg(target_os = "linux")]
|
||||
let (nyx_helper, edges_observer) = {
|
||||
if opt.nyx_mode {
|
||||
// main node is the first core id in CentralizedLauncher
|
||||
let cores = opt.cores.clone().expect("invariant; should never occur");
|
||||
let main_node_core_id = match cores.ids.len() {
|
||||
1 => None,
|
||||
_ => Some(cores.ids.first().expect("invariant; should never occur").0),
|
||||
};
|
||||
let nyx_settings = NyxSettings::builder()
|
||||
.cpu_id(core_id.0)
|
||||
.parent_cpu_id(main_node_core_id)
|
||||
.build();
|
||||
let nyx_helper = NyxHelper::new(opt.executable.clone(), nyx_settings).unwrap();
|
||||
let observer = unsafe {
|
||||
StdMapObserver::from_mut_ptr(
|
||||
"edges",
|
||||
nyx_helper.bitmap_buffer,
|
||||
nyx_helper.bitmap_size,
|
||||
)
|
||||
};
|
||||
(Some(nyx_helper), observer)
|
||||
} else {
|
||||
let observer = unsafe { StdMapObserver::new("edges", shmem_buf) };
|
||||
(None, observer)
|
||||
}
|
||||
};
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let edges_observer = { unsafe { StdMapObserver::new("edges", shmem_buf) } };
|
||||
|
||||
let edges_observer = HitcountsMapObserver::new(edges_observer).track_indices();
|
||||
|
||||
// Create a MapFeedback for coverage guided fuzzin'
|
||||
let map_feedback = MaxMapFeedback::new(&edges_observer);
|
||||
@ -261,9 +290,17 @@ where
|
||||
std::env::set_var("LD_PRELOAD", &preload);
|
||||
std::env::set_var("DYLD_INSERT_LIBRARIES", &preload);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let mut executor = {
|
||||
if opt.nyx_mode {
|
||||
SupportedExecutors::Nyx(NyxExecutor::builder().build(
|
||||
nyx_helper.unwrap(),
|
||||
(edges_observer, tuple_list!(time_observer)),
|
||||
))
|
||||
} else {
|
||||
// Create the base Executor
|
||||
let mut executor_builder = base_executor_builder(opt, &mut shmem_provider, fuzzer_dir);
|
||||
let mut executor_builder =
|
||||
base_forkserver_builder(opt, &mut shmem_provider, fuzzer_dir);
|
||||
// Set a custom exit code to be interpreted as a Crash if configured.
|
||||
if let Some(crash_exitcode) = opt.crash_exitcode {
|
||||
executor_builder = executor_builder.crash_exitcode(crash_exitcode);
|
||||
@ -275,9 +312,36 @@ where
|
||||
};
|
||||
|
||||
// Finalize and build our Executor
|
||||
let mut executor = executor_builder
|
||||
SupportedExecutors::Forkserver(
|
||||
executor_builder
|
||||
.build_dynamic_map(edges_observer, tuple_list!(time_observer))
|
||||
.unwrap();
|
||||
.unwrap(),
|
||||
PhantomData,
|
||||
)
|
||||
}
|
||||
};
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let executor = {
|
||||
// Create the base Executor
|
||||
let mut executor_builder = base_forkserver_builder(opt, &mut shmem_provider, fuzzer_dir);
|
||||
// Set a custom exit code to be interpreted as a Crash if configured.
|
||||
if let Some(crash_exitcode) = opt.crash_exitcode {
|
||||
executor_builder = executor_builder.crash_exitcode(crash_exitcode);
|
||||
}
|
||||
|
||||
// Enable autodict if configured
|
||||
if !opt.no_autodict {
|
||||
executor_builder = executor_builder.autotokens(&mut tokens);
|
||||
};
|
||||
|
||||
// Finalize and build our Executor
|
||||
SupportedExecutors::Forkserver(
|
||||
executor_builder
|
||||
.build_dynamic_map(edges_observer, tuple_list!(time_observer))
|
||||
.unwrap(),
|
||||
PhantomData,
|
||||
)
|
||||
};
|
||||
|
||||
let queue_dir = fuzzer_dir.join("queue");
|
||||
if opt.auto_resume {
|
||||
@ -383,7 +447,7 @@ where
|
||||
|
||||
// Create the CmpLog executor.
|
||||
// Cmplog has 25% execution overhead so we give it double the timeout
|
||||
let cmplog_executor = base_executor_builder(opt, &mut shmem_provider, fuzzer_dir)
|
||||
let cmplog_executor = base_forkserver_builder(opt, &mut shmem_provider, fuzzer_dir)
|
||||
.timeout(Duration::from_millis(opt.hang_timeout * 2))
|
||||
.program(cmplog_executable_path)
|
||||
.build(tuple_list!(cmplog_observer))
|
||||
@ -458,7 +522,7 @@ where
|
||||
// TODO: serialize state when exiting.
|
||||
}
|
||||
|
||||
fn base_executor_builder<'a>(
|
||||
fn base_forkserver_builder<'a>(
|
||||
opt: &'a Opt,
|
||||
shmem_provider: &'a mut UnixShMemProvider,
|
||||
fuzzer_dir: &Path,
|
||||
@ -475,6 +539,9 @@ fn base_executor_builder<'a>(
|
||||
if let Some(target_env) = &opt.target_env {
|
||||
executor = executor.envs(target_env);
|
||||
}
|
||||
if opt.frida_mode {
|
||||
executor = executor.kill_signal(nix::sys::signal::Signal::SIGKILL);
|
||||
}
|
||||
if let Some(kill_signal) = opt.kill_signal {
|
||||
executor = executor.kill_signal(kill_signal);
|
||||
}
|
||||
|
@ -297,7 +297,7 @@ struct Opt {
|
||||
#[arg(short = 'Q')]
|
||||
qemu_mode: bool,
|
||||
#[cfg(target_os = "linux")]
|
||||
#[clap(skip)]
|
||||
#[arg(short = 'X')]
|
||||
nyx_mode: bool,
|
||||
/// use unicorn-based instrumentation (Unicorn mode)
|
||||
#[arg(short = 'U')]
|
||||
|
@ -870,6 +870,7 @@ impl<'a, T> From<&'a mut &'a mut [T]> for OwnedMutSlice<'a, T> {
|
||||
}
|
||||
|
||||
/// Wrap a mutable slice and convert to a Box on serialize.
|
||||
///
|
||||
/// We use a hidden inner enum so the public API can be safe,
|
||||
/// unless the user uses the unsafe [`OwnedMutSizedSlice::from_raw_mut`].
|
||||
/// The variable length version is [`OwnedMutSlice`].
|
||||
|
@ -20,7 +20,7 @@ categories = [
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libnyx = { git = "https://github.com/nyx-fuzz/libnyx.git", rev = "6833d236dfe785a8a23d8c8d79e74c99fa635004" }
|
||||
libnyx = { git = "https://github.com/nyx-fuzz/libnyx.git", rev = "ea6ceb994ab975b81aea0daaf64b92a3066c1e8d" }
|
||||
libafl = { path = "../libafl", version = "0.13.2", features = [
|
||||
"std",
|
||||
"libafl_derive",
|
||||
|
@ -5,7 +5,7 @@ use std::{
|
||||
};
|
||||
|
||||
use libafl::{
|
||||
executors::{Executor, ExitKind, HasObservers},
|
||||
executors::{Executor, ExitKind, HasObservers, HasTimeout},
|
||||
inputs::HasTargetBytes,
|
||||
observers::{ObserversTuple, StdOutObserver},
|
||||
state::{HasExecutions, State, UsesState},
|
||||
@ -129,6 +129,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, OT> HasTimeout for NyxExecutor<S, OT> {
|
||||
fn timeout(&self) -> std::time::Duration {
|
||||
self.helper.timeout
|
||||
}
|
||||
|
||||
fn set_timeout(&mut self, timeout: std::time::Duration) {
|
||||
let micros = 1000000;
|
||||
let mut timeout_secs = timeout.as_secs();
|
||||
let mut timeout_micros = timeout.as_micros() - u128::from(timeout.as_secs() * micros);
|
||||
// since timeout secs is a u8 -> convert any overflow into micro secs
|
||||
if timeout_secs > 255 {
|
||||
timeout_micros = u128::from((timeout_secs - 255) * micros);
|
||||
timeout_secs = 255;
|
||||
}
|
||||
|
||||
self.helper.timeout = timeout;
|
||||
|
||||
self.helper
|
||||
.set_timeout(timeout_secs as u8, timeout_micros as u32);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, OT> NyxExecutor<S, OT> {
|
||||
/// Convert `trace_bits` ptr into real trace map
|
||||
///
|
||||
|
@ -1,5 +1,5 @@
|
||||
/// [`NyxHelper`] is used to wrap `NyxProcess`
|
||||
use std::{fmt::Debug, fs::File, path::Path};
|
||||
use std::{fmt::Debug, fs::File, path::Path, time::Duration};
|
||||
|
||||
use libafl::Error;
|
||||
use libnyx::{NyxConfig, NyxProcess, NyxProcessRole};
|
||||
@ -10,6 +10,8 @@ pub struct NyxHelper {
|
||||
pub nyx_process: NyxProcess,
|
||||
pub nyx_stdout: File,
|
||||
|
||||
pub timeout: Duration,
|
||||
|
||||
pub bitmap_size: usize,
|
||||
pub bitmap_buffer: *mut u8,
|
||||
}
|
||||
@ -66,12 +68,10 @@ impl NyxHelper {
|
||||
let bitmap_size = nyx_process.bitmap_buffer_size();
|
||||
let bitmap_buffer = nyx_process.bitmap_buffer_mut().as_mut_ptr();
|
||||
|
||||
Ok(Self {
|
||||
nyx_process,
|
||||
nyx_stdout,
|
||||
bitmap_size,
|
||||
bitmap_buffer,
|
||||
})
|
||||
let mut timeout = Duration::from_secs(u64::from(settings.timeout_secs));
|
||||
timeout += Duration::from_micros(u64::from(settings.timeout_micro_secs));
|
||||
|
||||
Ok(Self { nyx_process, nyx_stdout, timeout, bitmap_size, bitmap_buffer })
|
||||
}
|
||||
|
||||
/// Set a timeout for Nyx.
|
||||
|
Loading…
x
Reference in New Issue
Block a user