Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

libafl-fuzz: introduce nyx_mode #2503

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzzers/others/libafl-fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ libafl_bolts = { path = "../../../libafl_bolts", features = [
"errors_backtrace",
] }
libafl_targets = { path = "../../../libafl_targets" }
libafl_nyx = { path = "../../../libafl_nyx" }
memmap2 = "0.9.4"
nix = { version = "0.29", features = ["fs"] }
regex = "1.10.5"
Expand Down
49 changes: 44 additions & 5 deletions fuzzers/others/libafl-fuzz/Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ cd ../..
'''
dependencies = ["build_afl"]

[tasks.get_nyx_packer]
script_runner = "@shell"
script = '''
'''

[tasks.build_unicorn_mode]
script_runner = "@shell"
script = '''
Expand Down Expand Up @@ -77,7 +82,7 @@ script = '''
AFL_PATH=${AFL_DIR} ${AFL_CC_PATH} ./test/test-instr.c -o ./test/out-instr

export LIBAFL_DEBUG_OUTPUT=1
export AFL_CORES=1
export AFL_CORES=0
export AFL_STATS_INTERVAL=1

timeout 5 ${FUZZER} -i ./test/seeds -o ./test/output ./test/out-instr || true
Expand Down Expand Up @@ -109,7 +114,7 @@ script_runner = "@shell"
script = '''
# cmplog TODO: AFL_BENCH_UNTIL_CRASH=1 instead of timeout 15s
AFL_LLVM_CMPLOG=1 AFL_PATH=${AFL_DIR} ${AFL_CC_PATH} ./test/test-cmplog.c -o ./test/out-cmplog
AFL_CORES=1 timeout 5 ${FUZZER} -Z -l 3 -m 0 -V30 -i ./test/seeds_cmplog -o ./test/output-cmplog -c 0 ./test/out-cmplog || true
AFL_CORES=0 timeout 5 ${FUZZER} -Z -l 3 -m 0 -V30 -i ./test/seeds_cmplog -o ./test/output-cmplog -c 0 ./test/out-cmplog || true
test -n "$( ls -A ./test/output-cmplog/fuzzer_main/crashes/)" || {
echo "No crashes found"
exit 1
Expand All @@ -123,7 +128,7 @@ script = '''
${CC} -no-pie ./test/test-instr.c -o ./test/out-frida

export AFL_PATH=${AFL_DIR}
export AFL_CORES=1
export AFL_CORES=0
export AFL_STATS_INTERVAL=1

timeout 5 ${FUZZER} -m 0 -O -i ./test/seeds_frida -o ./test/output-frida -- ./test/out-frida || true
Expand Down Expand Up @@ -171,7 +176,7 @@ ${CC} -pie -fPIE ./test/test-instr.c -o ./test/out-qemu
${CC} -o ./test/out-qemu-cmpcov ./test/test-cmpcov.c

export AFL_PATH=${AFL_DIR}
export AFL_CORES=1
export AFL_CORES=0
export AFL_STATS_INTERVAL=1

timeout 5 ${FUZZER} -m 0 -Q -i ./test/seeds_qemu -o ./test/output-qemu -- ./test/out-qemu || true
Expand Down Expand Up @@ -202,7 +207,7 @@ dependencies = ["build_afl", "build_qemuafl", "build_libafl_fuzz"]
script_runner = "@shell"
script = '''
export AFL_PATH=${AFL_DIR}
export AFL_CORES=1
export AFL_CORES=0
export AFL_STATS_INTERVAL=1

# TODO: test unicorn persistent mode once it's fixed on AFL++
Expand All @@ -222,6 +227,39 @@ 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/others/libafl-fuzz/test/out-instr \
../fuzzers/others/libafl-fuzz/test/out-nyx \
afl \
instrumentation \
--fast_reload_mode \
--purge

python3 packer/packer/nyx_config_gen.py ../fuzzers/others/libafl-fuzz/test/out-nyx Kernel
cd ../fuzzers/others/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 All @@ -237,5 +275,6 @@ rm -rf ./test/output-frida*
rm -rf ./test/output-cmplog
rm -rf ./test/output-qemu*
rm -rf ./test/output-unicorn*
rm -rf ./test/output-nyx*
rm ./test/out-*
'''
77 changes: 76 additions & 1 deletion fuzzers/others/libafl-fuzz/src/executor.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
use std::{
fs::File,
marker::PhantomData,
os::{linux::fs::MetadataExt, unix::fs::PermissionsExt},
path::{Path, PathBuf},
};

use libafl::Error;
use libafl::{
executors::{Executor, ExitKind},
prelude::{HasObservers, HasTargetBytes, ObserversTuple, UsesObservers},
stages::HasCurrentStage,
state::{HasExecutions, State, UsesState},
Error,
};
use libafl_bolts::tuples::RefIndexable;
use memmap2::{Mmap, MmapOptions};
use nix::libc::{S_IRUSR, S_IXUSR};

Expand Down Expand Up @@ -238,3 +246,70 @@ fn check_file_found(file: &PathBuf, perm: u32) -> bool {
}
false
}

pub enum SupportedExecutors<S, OT, FSV, NYX> {
Forkserver(FSV, PhantomData<(S, OT)>),
Nyx(NYX, PhantomData<(S, OT)>),
}

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 + HasExecutions + HasCurrentStage,
S::Input: HasTargetBytes,
Z: UsesState<State = S>,
EM: UsesState<State = S>,
FSV: Executor<EM, Z, State = S> + HasObservers<Observers = OT>,
NYX: Executor<EM, Z, State = S> + HasObservers<Observers = OT>,
{
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),
Self::Nyx(nyx, _) => nyx.run_target(fuzzer, state, mgr, input),
}
}
}

impl<S, OT, FSV, NYX> UsesObservers for SupportedExecutors<S, OT, FSV, NYX>
where
S: State,
OT: ObserversTuple<S>,
{
type Observers = OT;
}

impl<S, OT, FSV, NYX> HasObservers for SupportedExecutors<S, OT, FSV, NYX>
where
OT: ObserversTuple<S>,
S: State + HasExecutions + HasCurrentStage,
FSV: HasObservers<Observers = OT>,
NYX: HasObservers<Observers = OT>,
{
#[inline]
fn observers(&self) -> RefIndexable<&Self::Observers, Self::Observers> {
match self {
Self::Forkserver(fsrv, _) => fsrv.observers(),
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(),
Self::Nyx(nyx, _) => nyx.observers_mut(),
}
}
}
107 changes: 72 additions & 35 deletions fuzzers/others/libafl-fuzz/src/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use std::{borrow::Cow, marker::PhantomData, path::PathBuf, time::Duration};
use libafl::{
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
events::{
CentralizedEventManager, EventManagerHooksTuple, EventProcessor,
LlmpRestartingEventManager, ProgressReporter,
CentralizedEventManager, EventProcessor, LlmpRestartingEventManager, ProgressReporter,
},
executors::forkserver::{ForkserverExecutor, ForkserverExecutorBuilder},
feedback_and, feedback_or, feedback_or_fast,
Expand Down Expand Up @@ -39,14 +38,16 @@ 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 nix::sys::signal::Signal::{SIGKILL, SIGTERM};
use serde::{Deserialize, Serialize};

use crate::{
afl_stats::{AflStatsStage, CalibrationTime, FuzzTime, SyncTime},
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 All @@ -60,23 +61,19 @@ use crate::{
pub type LibaflFuzzState =
StdState<BytesInput, CachedOnDiskCorpus<BytesInput>, StdRand, OnDiskCorpus<BytesInput>>;

pub fn run_client<EMH, SP>(
pub fn run_client(
state: Option<LibaflFuzzState>,
mut restarting_mgr: CentralizedEventManager<
LlmpRestartingEventManager<(), LibaflFuzzState, SP>,
EMH,
LlmpRestartingEventManager<(), LibaflFuzzState, StdShMemProvider>,
(),
LibaflFuzzState,
SP,
StdShMemProvider,
>,
fuzzer_dir: &PathBuf,
core_id: CoreId,
opt: &Opt,
is_main_node: bool,
) -> Result<(), Error>
where
EMH: EventManagerHooksTuple<LibaflFuzzState> + Copy + Clone,
SP: ShMemProvider,
{
) -> Result<(), Error> {
// Create the shared memory map for comms with the forkserver
let mut shmem_provider = StdShMemProvider::new().unwrap();
let mut shmem = shmem_provider
Expand All @@ -86,10 +83,30 @@ where
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.
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)
};

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 @@ -234,23 +251,33 @@ where
std::env::set_var("DYLD_INSERT_LIBRARIES", &preload);
}

// Create the base Executor
let mut executor = base_executor(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 = executor.crash_exitcode(crash_exitcode);
}
let mut executor = if opt.nyx_mode {
SupportedExecutors::Nyx(
NyxExecutor::builder().build(
nyx_helper.unwrap(),
tuple_list!(time_observer, edges_observer),
),
PhantomData,
)
} else {
// Create the base Executor
let mut executor = base_forkserver(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 = executor.crash_exitcode(crash_exitcode);
}

// Enable autodict if configured
if !opt.no_autodict {
executor = executor.autotokens(&mut tokens);
// Enable autodict if configured
if !opt.no_autodict {
executor = executor.autotokens(&mut tokens);
};
// Finalize and build our Executor
let executor = executor
.build(tuple_list!(time_observer, edges_observer))
.unwrap();
SupportedExecutors::Forkserver(executor, PhantomData)
};

// Finalize and build our Executor
let mut executor = executor
.build(tuple_list!(time_observer, edges_observer))
.unwrap();

// Load our seeds.
if state.must_load_initial_inputs() {
state
Expand Down Expand Up @@ -307,7 +334,6 @@ where
},
};
let run_cmplog = cmplog_executable_path != "-" && is_main_node;

if run_cmplog {
// The CmpLog map shared between the CmpLog observer and CmpLog executor
let mut cmplog_shmem = shmem_provider.uninit_on_shmem::<AFLppCmpLogMap>().unwrap();
Expand All @@ -322,7 +348,7 @@ where

// Create the CmpLog executor.
// Cmplog has 25% execution overhead so we give it double the timeout
let cmplog_executor = base_executor(opt, &mut shmem_provider, fuzzer_dir)?
let cmplog_executor = base_forkserver(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 @@ -375,7 +401,6 @@ where
} else {
// The order of the stages matter!
let mut stages = tuple_list!(calibration, mutational_stage, afl_stats_stage, sync_stage);

// Run our fuzzer; NO CmpLog
run_fuzzer_with_stages(
opt,
Expand All @@ -390,7 +415,7 @@ where
// TODO: serialize state when exiting.
}

fn base_executor<'a>(
fn base_forkserver<'a>(
opt: &'a Opt,
shmem_provider: &'a mut StdShMemProvider,
fuzzer_dir: &PathBuf,
Expand All @@ -407,9 +432,21 @@ fn base_executor<'a>(
if let Some(target_env) = &opt.target_env {
executor = executor.envs(target_env);
}
if let Some(kill_signal) = opt.kill_signal {
executor = executor.kill_signal(kill_signal);
}
let kill_signal = if let Some(signal) = opt.kill_signal {
signal
} else {
let mut signal = if opt.unicorn_mode || opt.qemu_mode {
SIGKILL
} else {
SIGTERM
};
#[cfg(target_os = "linux")]
if opt.nyx_mode {
signal = SIGKILL;
};
signal
};
executor = executor.kill_signal(kill_signal);
if opt.is_persistent || opt.qemu_mode || opt.unicorn_mode {
executor = executor.shmem_provider(shmem_provider);
}
Expand Down
Loading