diff --git a/fuzzers/nyx_libxml2_parallel/src/main.rs b/fuzzers/nyx_libxml2_parallel/src/main.rs index 94874d9885..ed17553ab5 100644 --- a/fuzzers/nyx_libxml2_parallel/src/main.rs +++ b/fuzzers/nyx_libxml2_parallel/src/main.rs @@ -34,8 +34,8 @@ fn main() { let mut run_client = |state: Option<_>, mut restarting_mgr, core_id: CoreId| { // nyx stuff let settings = NyxSettings::builder() - .cpu_id(0) - .parent_cpu_id(Some(parent_cpu_id.0 as u32)) + .cpu_id(core_id.0) + .parent_cpu_id(Some(parent_cpu_id.0)) .build(); let helper = NyxHelper::new("/tmp/nyx_libxml2/", settings).unwrap(); let observer = unsafe { diff --git a/libafl_nyx/Cargo.toml b/libafl_nyx/Cargo.toml index cc000c1f85..6bb6a19f8d 100644 --- a/libafl_nyx/Cargo.toml +++ b/libafl_nyx/Cargo.toml @@ -14,9 +14,10 @@ categories = ["development-tools::testing", "emulators", "embedded", "os", "no-s # 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 = "acaf7f6" } +libnyx = { git = "https://github.com/nyx-fuzz/libnyx.git", rev = "6833d23" } libafl = { path = "../libafl", version = "0.11.2", features = ["std", "libafl_derive", "frida_cli" ]} libafl_bolts = { path = "../libafl_bolts", version = "0.11.2", features = ["std", "libafl_derive", "frida_cli" ]} libafl_targets = { path = "../libafl_targets", version = "0.11.2", features = ["std", "sancov_cmplog"] } +nix = { version = "0.28.0", features = ["fs"] } typed-builder = "0.18.1" diff --git a/libafl_nyx/src/executor.rs b/libafl_nyx/src/executor.rs index 83689fcb84..f39b56367d 100644 --- a/libafl_nyx/src/executor.rs +++ b/libafl_nyx/src/executor.rs @@ -1,4 +1,8 @@ -use std::marker::PhantomData; +use std::{ + io::{Read, Seek}, + marker::PhantomData, + os::fd::AsRawFd, +}; use libafl::{ executors::{Executor, ExitKind, HasObservers}, @@ -43,6 +47,7 @@ where S: State + HasExecutions, S::Input: HasTargetBytes, Z: UsesState, + OT: ObserversTuple, { fn run_target( &mut self, @@ -65,35 +70,57 @@ where ))); } + self.helper + .nyx_stdout + .set_len(0) + .map_err(|e| Error::illegal_state(format!("Failed to clear Nyx stdout: {e}")))?; + let size = u32::try_from(buffer.len()) .map_err(|_| Error::unsupported("Inputs larger than 4GB are not supported"))?; + // Duplicate the file descriptor since QEMU(?) closes it and we + // want to keep |self.helper.nyx_stdout| open. + let hprintf_fd = nix::unistd::dup(self.helper.nyx_stdout.as_raw_fd()) + .map_err(|e| Error::illegal_state(format!("Failed to duplicate Nyx stdout fd: {e}")))?; self.helper.nyx_process.set_input(buffer, size); + self.helper.nyx_process.set_hprintf_fd(hprintf_fd); // exec will take care of trace_bits, so no need to reset - match self.helper.nyx_process.exec() { - NyxReturnValue::Normal => Ok(ExitKind::Ok), - NyxReturnValue::Crash | NyxReturnValue::Asan => Ok(ExitKind::Crash), - NyxReturnValue::Timeout => Ok(ExitKind::Timeout), + let exit_kind = match self.helper.nyx_process.exec() { + NyxReturnValue::Normal => ExitKind::Ok, + NyxReturnValue::Crash | NyxReturnValue::Asan => ExitKind::Crash, + NyxReturnValue::Timeout => ExitKind::Timeout, NyxReturnValue::InvalidWriteToPayload => { self.helper.nyx_process.shutdown(); - Err(Error::illegal_state( + return Err(Error::illegal_state( "FixMe: Nyx InvalidWriteToPayload handler is missing", - )) + )); } NyxReturnValue::Error => { self.helper.nyx_process.shutdown(); - Err(Error::illegal_state("Nyx runtime error has occurred")) + return Err(Error::illegal_state("Nyx runtime error has occurred")); } NyxReturnValue::IoError => { self.helper.nyx_process.shutdown(); - Err(Error::unknown("QEMU-nyx died")) + return Err(Error::unknown("QEMU-nyx died")); } NyxReturnValue::Abort => { self.helper.nyx_process.shutdown(); - Err(Error::shutting_down()) + return Err(Error::shutting_down()); } + }; + + if self.observers.observes_stdout() { + let mut stdout = Vec::new(); + self.helper.nyx_stdout.rewind()?; + self.helper + .nyx_stdout + .read_to_end(&mut stdout) + .map_err(|e| Error::illegal_state(format!("Failed to read Nyx stdout: {e}")))?; + self.observers.observe_stdout(&stdout); } + + Ok(exit_kind) } } diff --git a/libafl_nyx/src/helper.rs b/libafl_nyx/src/helper.rs index ca7b8e79b7..fc7b2163de 100644 --- a/libafl_nyx/src/helper.rs +++ b/libafl_nyx/src/helper.rs @@ -1,13 +1,14 @@ /// [`NyxHelper`] is used to wrap `NyxProcess` -use std::{fmt::Debug, path::Path}; +use std::{fmt::Debug, fs::File, path::Path}; use libafl::Error; -use libnyx::NyxProcess; +use libnyx::{NyxConfig, NyxProcess, NyxProcessRole}; use crate::settings::NyxSettings; pub struct NyxHelper { pub nyx_process: NyxProcess, + pub nyx_stdout: File, pub bitmap_size: usize, pub bitmap_buffer: *mut u8, @@ -31,52 +32,43 @@ impl NyxHelper { where P: AsRef, { - let work_dir = share_dir.as_ref().join("workdir"); let share_dir_str = share_dir.as_ref().to_str().ok_or(Error::illegal_argument( "`share_dir` contains invalid UTF-8", ))?; - let work_dir_str = work_dir - .to_str() - .ok_or(Error::illegal_argument("`work_dir` contains invalid UTF-8"))?; - let nyx_process_type = match settings.parent_cpu_id { - None => NyxProcessType::ALONE, - Some(parent_cpu_id) if settings.cpu_id == parent_cpu_id => NyxProcessType::PARENT, - _ => NyxProcessType::CHILD, - }; - let mut nyx_process = (match nyx_process_type { - NyxProcessType::ALONE => NyxProcess::new( - share_dir_str, - work_dir_str, - settings.cpu_id, - settings.input_buffer_size, - /* input_buffer_write_protection= */ true, - ), - NyxProcessType::PARENT => NyxProcess::new_parent( - share_dir_str, - work_dir_str, - settings.cpu_id, - settings.input_buffer_size, - /* input_buffer_write_protection= */ true, - ), - NyxProcessType::CHILD => NyxProcess::new_child( - share_dir_str, - work_dir_str, - settings.cpu_id, - /* worker_id= */ settings.cpu_id, - ), - }) - .map_err(Error::illegal_argument)?; + let mut nyx_config = NyxConfig::load(share_dir_str).map_err(|e| { + Error::illegal_argument(format!("Failed to load Nyx config from share dir: {e}")) + })?; + nyx_config.set_input_buffer_size(settings.input_buffer_size); + nyx_config.set_process_role(match settings.parent_cpu_id { + None => NyxProcessRole::StandAlone, + Some(parent_cpu_id) if parent_cpu_id == settings.cpu_id => NyxProcessRole::Parent, + _ => NyxProcessRole::Child, + }); + nyx_config.set_worker_id(settings.cpu_id); + let mut nyx_process = NyxProcess::new(&mut nyx_config, settings.cpu_id) + .map_err(|e| Error::illegal_state(format!("Failed to create Nyx process: {e}")))?; nyx_process.option_set_reload_mode(settings.snap_mode); nyx_process.option_set_timeout(settings.timeout_secs, settings.timeout_micro_secs); nyx_process.option_apply(); + let path = Path::new(nyx_config.workdir_path()) + .join(format!("hprintf_{}", nyx_config.worker_id())); + let nyx_stdout = File::options() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(path) + .map_err(|e| Error::illegal_state(format!("Failed to create Nyx stdout file: {e}")))?; + 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, }) diff --git a/libafl_nyx/src/settings.rs b/libafl_nyx/src/settings.rs index 04f6e5ff8c..51e9eef051 100644 --- a/libafl_nyx/src/settings.rs +++ b/libafl_nyx/src/settings.rs @@ -1,6 +1,6 @@ use typed_builder::TypedBuilder; -const DEFAULT_INPUT_BUFFER_SIZE: u32 = 1024 * 1024; +const DEFAULT_INPUT_BUFFER_SIZE: usize = 1024 * 1024; const DEFAULT_TIMEOUT_SECS: u8 = 2; const DEFAULT_TIMEOUT_MICRO_SECS: u32 = 0; const DEFAULT_SNAP_MODE: bool = true; @@ -14,14 +14,14 @@ pub struct NyxSettings { /// * Standalone: `parent_cpu_id.is_none()`. /// * Parent: `parent_cpu_id.is_some_and(|parent_cpu_id| parent_cpu_id == cpu_id)`. /// * Child: `parent_cpu_id.is_some_and(|parent_cpu_id| parent_cpu_id != cpu_id)`. - pub cpu_id: u32, + pub cpu_id: usize, /// The CPU core for the Nyx parent process. The parent process /// creates the fuzzing snapshot that can then be used by the child /// processes. /// /// Not specifying this will start the Nyx process in standalone mode. - pub parent_cpu_id: Option, + pub parent_cpu_id: Option, /// Reload the VM by using the fuzzing snapshot. You probably want /// this to be `true`. @@ -33,7 +33,7 @@ pub struct NyxSettings { /// /// Default is `1MB`. #[builder(default = DEFAULT_INPUT_BUFFER_SIZE)] - pub input_buffer_size: u32, + pub input_buffer_size: usize, /// The timeout for a single execution in seconds (until the /// hypervisor restore snapshot call).