diff --git a/fuzzers/forkserver/libafl-fuzz/Cargo.toml b/fuzzers/forkserver/libafl-fuzz/Cargo.toml index 2b6bff3ebb..84da232f54 100644 --- a/fuzzers/forkserver/libafl-fuzz/Cargo.toml +++ b/fuzzers/forkserver/libafl-fuzz/Cargo.toml @@ -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"] diff --git a/fuzzers/forkserver/libafl-fuzz/Makefile.toml b/fuzzers/forkserver/libafl-fuzz/Makefile.toml index 15264bccf3..dadcfd781f 100644 --- a/fuzzers/forkserver/libafl-fuzz/Makefile.toml +++ b/fuzzers/forkserver/libafl-fuzz/Makefile.toml @@ -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" diff --git a/fuzzers/forkserver/libafl-fuzz/src/executor.rs b/fuzzers/forkserver/libafl-fuzz/src/executor.rs index 8496ff0ebe..90f49a9b57 100644 --- a/fuzzers/forkserver/libafl-fuzz/src/executor.rs +++ b/fuzzers/forkserver/libafl-fuzz/src/executor.rs @@ -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 { + Forkserver(FSV, PhantomData<(S, OT)>), + #[cfg(target_os = "linux")] + Nyx(NYX), +} + +impl UsesState for SupportedExecutors +where + S: State, +{ + type State = S; +} + +impl Executor for SupportedExecutors +where + S: State, + Z: UsesState, + EM: UsesState, + FSV: Executor, + NYX: Executor, +{ + fn run_target( + &mut self, + fuzzer: &mut Z, + state: &mut S, + mgr: &mut EM, + input: &S::Input, + ) -> Result { + 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 HasObservers for SupportedExecutors +where + OT: ObserversTuple, + S: State, + FSV: HasObservers, + NYX: HasObservers, +{ + 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 HasTimeout for SupportedExecutors +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(), + } + } +} diff --git a/fuzzers/forkserver/libafl-fuzz/src/fuzzer.rs b/fuzzers/forkserver/libafl-fuzz/src/fuzzer.rs index b72ec252c2..517240ab8b 100644 --- a/fuzzers/forkserver/libafl-fuzz/src/fuzzer.rs +++ b/fuzzers/forkserver/libafl-fuzz/src/fuzzer.rs @@ -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,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 { @@ -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); } diff --git a/fuzzers/forkserver/libafl-fuzz/src/main.rs b/fuzzers/forkserver/libafl-fuzz/src/main.rs index 49bee26ab1..53d8562192 100644 --- a/fuzzers/forkserver/libafl-fuzz/src/main.rs +++ b/fuzzers/forkserver/libafl-fuzz/src/main.rs @@ -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')] diff --git a/libafl_bolts/src/ownedref.rs b/libafl_bolts/src/ownedref.rs index e11e74afa0..d176a8dc6e 100644 --- a/libafl_bolts/src/ownedref.rs +++ b/libafl_bolts/src/ownedref.rs @@ -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`]. diff --git a/libafl_nyx/Cargo.toml b/libafl_nyx/Cargo.toml index 850229c3e0..c0c993c22b 100644 --- a/libafl_nyx/Cargo.toml +++ b/libafl_nyx/Cargo.toml @@ -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", diff --git a/libafl_nyx/src/executor.rs b/libafl_nyx/src/executor.rs index b3834068a2..5f100a5c83 100644 --- a/libafl_nyx/src/executor.rs +++ b/libafl_nyx/src/executor.rs @@ -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 HasTimeout for NyxExecutor { + 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 NyxExecutor { /// Convert `trace_bits` ptr into real trace map /// diff --git a/libafl_nyx/src/helper.rs b/libafl_nyx/src/helper.rs index fc7b2163de..40dd454d99 100644 --- a/libafl_nyx/src/helper.rs +++ b/libafl_nyx/src/helper.rs @@ -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.