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:
parent
94a2a2363a
commit
631b1746e2
@ -34,8 +34,8 @@ fn main() {
|
|||||||
let mut run_client = |state: Option<_>, mut restarting_mgr, core_id: CoreId| {
|
let mut run_client = |state: Option<_>, mut restarting_mgr, core_id: CoreId| {
|
||||||
// nyx stuff
|
// nyx stuff
|
||||||
let settings = NyxSettings::builder()
|
let settings = NyxSettings::builder()
|
||||||
.cpu_id(0)
|
.cpu_id(core_id.0)
|
||||||
.parent_cpu_id(Some(parent_cpu_id.0 as u32))
|
.parent_cpu_id(Some(parent_cpu_id.0))
|
||||||
.build();
|
.build();
|
||||||
let helper = NyxHelper::new("/tmp/nyx_libxml2/", settings).unwrap();
|
let helper = NyxHelper::new("/tmp/nyx_libxml2/", settings).unwrap();
|
||||||
let observer = unsafe {
|
let observer = unsafe {
|
||||||
|
@ -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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[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 = { 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_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"] }
|
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"
|
typed-builder = "0.18.1"
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
use std::marker::PhantomData;
|
use std::{
|
||||||
|
io::{Read, Seek},
|
||||||
|
marker::PhantomData,
|
||||||
|
os::fd::AsRawFd,
|
||||||
|
};
|
||||||
|
|
||||||
use libafl::{
|
use libafl::{
|
||||||
executors::{Executor, ExitKind, HasObservers},
|
executors::{Executor, ExitKind, HasObservers},
|
||||||
@ -43,6 +47,7 @@ where
|
|||||||
S: State + HasExecutions,
|
S: State + HasExecutions,
|
||||||
S::Input: HasTargetBytes,
|
S::Input: HasTargetBytes,
|
||||||
Z: UsesState<State = S>,
|
Z: UsesState<State = S>,
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
{
|
{
|
||||||
fn run_target(
|
fn run_target(
|
||||||
&mut self,
|
&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())
|
let size = u32::try_from(buffer.len())
|
||||||
.map_err(|_| Error::unsupported("Inputs larger than 4GB are not supported"))?;
|
.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_input(buffer, size);
|
||||||
|
self.helper.nyx_process.set_hprintf_fd(hprintf_fd);
|
||||||
|
|
||||||
// exec will take care of trace_bits, so no need to reset
|
// exec will take care of trace_bits, so no need to reset
|
||||||
match self.helper.nyx_process.exec() {
|
let exit_kind = match self.helper.nyx_process.exec() {
|
||||||
NyxReturnValue::Normal => Ok(ExitKind::Ok),
|
NyxReturnValue::Normal => ExitKind::Ok,
|
||||||
NyxReturnValue::Crash | NyxReturnValue::Asan => Ok(ExitKind::Crash),
|
NyxReturnValue::Crash | NyxReturnValue::Asan => ExitKind::Crash,
|
||||||
NyxReturnValue::Timeout => Ok(ExitKind::Timeout),
|
NyxReturnValue::Timeout => ExitKind::Timeout,
|
||||||
NyxReturnValue::InvalidWriteToPayload => {
|
NyxReturnValue::InvalidWriteToPayload => {
|
||||||
self.helper.nyx_process.shutdown();
|
self.helper.nyx_process.shutdown();
|
||||||
Err(Error::illegal_state(
|
return Err(Error::illegal_state(
|
||||||
"FixMe: Nyx InvalidWriteToPayload handler is missing",
|
"FixMe: Nyx InvalidWriteToPayload handler is missing",
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
NyxReturnValue::Error => {
|
NyxReturnValue::Error => {
|
||||||
self.helper.nyx_process.shutdown();
|
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 => {
|
NyxReturnValue::IoError => {
|
||||||
self.helper.nyx_process.shutdown();
|
self.helper.nyx_process.shutdown();
|
||||||
Err(Error::unknown("QEMU-nyx died"))
|
return Err(Error::unknown("QEMU-nyx died"));
|
||||||
}
|
}
|
||||||
NyxReturnValue::Abort => {
|
NyxReturnValue::Abort => {
|
||||||
self.helper.nyx_process.shutdown();
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
/// [`NyxHelper`] is used to wrap `NyxProcess`
|
/// [`NyxHelper`] is used to wrap `NyxProcess`
|
||||||
use std::{fmt::Debug, path::Path};
|
use std::{fmt::Debug, fs::File, path::Path};
|
||||||
|
|
||||||
use libafl::Error;
|
use libafl::Error;
|
||||||
use libnyx::NyxProcess;
|
use libnyx::{NyxConfig, NyxProcess, NyxProcessRole};
|
||||||
|
|
||||||
use crate::settings::NyxSettings;
|
use crate::settings::NyxSettings;
|
||||||
|
|
||||||
pub struct NyxHelper {
|
pub struct NyxHelper {
|
||||||
pub nyx_process: NyxProcess,
|
pub nyx_process: NyxProcess,
|
||||||
|
pub nyx_stdout: File,
|
||||||
|
|
||||||
pub bitmap_size: usize,
|
pub bitmap_size: usize,
|
||||||
pub bitmap_buffer: *mut u8,
|
pub bitmap_buffer: *mut u8,
|
||||||
@ -31,52 +32,43 @@ impl NyxHelper {
|
|||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
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(
|
let share_dir_str = share_dir.as_ref().to_str().ok_or(Error::illegal_argument(
|
||||||
"`share_dir` contains invalid UTF-8",
|
"`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 {
|
let mut nyx_config = NyxConfig::load(share_dir_str).map_err(|e| {
|
||||||
None => NyxProcessType::ALONE,
|
Error::illegal_argument(format!("Failed to load Nyx config from share dir: {e}"))
|
||||||
Some(parent_cpu_id) if settings.cpu_id == parent_cpu_id => NyxProcessType::PARENT,
|
})?;
|
||||||
_ => NyxProcessType::CHILD,
|
nyx_config.set_input_buffer_size(settings.input_buffer_size);
|
||||||
};
|
nyx_config.set_process_role(match settings.parent_cpu_id {
|
||||||
let mut nyx_process = (match nyx_process_type {
|
None => NyxProcessRole::StandAlone,
|
||||||
NyxProcessType::ALONE => NyxProcess::new(
|
Some(parent_cpu_id) if parent_cpu_id == settings.cpu_id => NyxProcessRole::Parent,
|
||||||
share_dir_str,
|
_ => NyxProcessRole::Child,
|
||||||
work_dir_str,
|
});
|
||||||
settings.cpu_id,
|
nyx_config.set_worker_id(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_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_reload_mode(settings.snap_mode);
|
||||||
nyx_process.option_set_timeout(settings.timeout_secs, settings.timeout_micro_secs);
|
nyx_process.option_set_timeout(settings.timeout_secs, settings.timeout_micro_secs);
|
||||||
nyx_process.option_apply();
|
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_size = nyx_process.bitmap_buffer_size();
|
||||||
let bitmap_buffer = nyx_process.bitmap_buffer_mut().as_mut_ptr();
|
let bitmap_buffer = nyx_process.bitmap_buffer_mut().as_mut_ptr();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
nyx_process,
|
nyx_process,
|
||||||
|
nyx_stdout,
|
||||||
bitmap_size,
|
bitmap_size,
|
||||||
bitmap_buffer,
|
bitmap_buffer,
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use typed_builder::TypedBuilder;
|
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_SECS: u8 = 2;
|
||||||
const DEFAULT_TIMEOUT_MICRO_SECS: u32 = 0;
|
const DEFAULT_TIMEOUT_MICRO_SECS: u32 = 0;
|
||||||
const DEFAULT_SNAP_MODE: bool = true;
|
const DEFAULT_SNAP_MODE: bool = true;
|
||||||
@ -14,14 +14,14 @@ pub struct NyxSettings {
|
|||||||
/// * Standalone: `parent_cpu_id.is_none()`.
|
/// * Standalone: `parent_cpu_id.is_none()`.
|
||||||
/// * Parent: `parent_cpu_id.is_some_and(|parent_cpu_id| parent_cpu_id == cpu_id)`.
|
/// * 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)`.
|
/// * 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
|
/// The CPU core for the Nyx parent process. The parent process
|
||||||
/// creates the fuzzing snapshot that can then be used by the child
|
/// creates the fuzzing snapshot that can then be used by the child
|
||||||
/// processes.
|
/// processes.
|
||||||
///
|
///
|
||||||
/// Not specifying this will start the Nyx process in standalone mode.
|
/// 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
|
/// Reload the VM by using the fuzzing snapshot. You probably want
|
||||||
/// this to be `true`.
|
/// this to be `true`.
|
||||||
@ -33,7 +33,7 @@ pub struct NyxSettings {
|
|||||||
///
|
///
|
||||||
/// Default is `1MB`.
|
/// Default is `1MB`.
|
||||||
#[builder(default = DEFAULT_INPUT_BUFFER_SIZE)]
|
#[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
|
/// The timeout for a single execution in seconds (until the
|
||||||
/// hypervisor restore snapshot call).
|
/// hypervisor restore snapshot call).
|
||||||
|
Loading…
x
Reference in New Issue
Block a user