libafl_nyx: Add support for StdOutObserver (#2033)

* libafl_nyx: Add support for `StdOutObserver`

* reset file offset

* update example fuzzers

* fix
This commit is contained in:
Maurice 2024-04-11 18:24:32 +02:00 committed by GitHub
parent 94a2a2363a
commit 631b1746e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 71 additions and 51 deletions

View File

@ -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 {

View File

@ -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"

View File

@ -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<State = S>,
OT: ObserversTuple<S>,
{
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)
}
}

View File

@ -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<Path>,
{
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,
})

View File

@ -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<u32>,
pub parent_cpu_id: Option<usize>,
/// 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).