Skip to content

Commit

Permalink
libafl-fuzz: introduce nyx_mode (AFLplusplus#2503)
Browse files Browse the repository at this point in the history
* add nyx_mode

* fix frida ci?

* damn clippy

* clippy
  • Loading branch information
R9295 authored Nov 13, 2024
1 parent f7f8dff commit 87f5f21
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 31 deletions.
1 change: 1 addition & 0 deletions fuzzers/forkserver/libafl-fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
30 changes: 30 additions & 0 deletions fuzzers/forkserver/libafl-fuzz/Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
93 changes: 92 additions & 1 deletion fuzzers/forkserver/libafl-fuzz/src/executor.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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(),
}
}
}
107 changes: 87 additions & 20 deletions fuzzers/forkserver/libafl-fuzz/src/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -261,23 +290,58 @@ 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_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);
}

// Create the base Executor
let mut executor_builder = base_executor_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);
// 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,
)
}
};
#[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);
}

// Finalize and build our Executor
let mut executor = executor_builder
.build_dynamic_map(edges_observer, tuple_list!(time_observer))
.unwrap();
// 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 {
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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,
Expand All @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion fuzzers/forkserver/libafl-fuzz/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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')]
Expand Down
1 change: 1 addition & 0 deletions libafl_bolts/src/ownedref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
Expand Down
2 changes: 1 addition & 1 deletion libafl_nyx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 23 additions & 1 deletion libafl_nyx/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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
///
Expand Down
Loading

0 comments on commit 87f5f21

Please sign in to comment.