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| {
|
||||
// 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 {
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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).
|
||||
|
Loading…
x
Reference in New Issue
Block a user