libafl_nyx: Allow custom input buffer size to be passed to NyxHelper
(#1960)
* add option to specify input buffer size * fix typo * use `libafl::Error` as default error type * derive `TypedBuilder` for `NyxSettings` * update nyx_libxml2_standalone * update nyx_libxml2_parallel * update nyx_libxml2_standalone * update nyx_libxml2_standalone * update nyx_libxml2_parallel
This commit is contained in:
parent
6b94db2260
commit
50843b19d1
@ -19,7 +19,7 @@ use libafl_bolts::{
|
||||
shmem::{ShMemProvider, StdShMemProvider},
|
||||
tuples::tuple_list,
|
||||
};
|
||||
use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper};
|
||||
use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings};
|
||||
|
||||
fn main() {
|
||||
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
|
||||
@ -32,21 +32,17 @@ fn main() {
|
||||
|
||||
// region: fuzzer start function
|
||||
let mut run_client = |state: Option<_>, mut restarting_mgr, core_id: CoreId| {
|
||||
// nyx shared dir, created by nyx-fuzz/packer/packer/nyx_packer.py
|
||||
let share_dir = Path::new("/tmp/nyx_libxml2/");
|
||||
let cpu_id = core_id.0.try_into().unwrap();
|
||||
let parallel_mode = true;
|
||||
// nyx stuff
|
||||
let mut helper = NyxHelper::new(
|
||||
share_dir,
|
||||
cpu_id,
|
||||
true,
|
||||
parallel_mode,
|
||||
Some(parent_cpu_id.0.try_into().unwrap()),
|
||||
)
|
||||
.unwrap();
|
||||
let observer =
|
||||
unsafe { StdMapObserver::from_mut_ptr("trace", helper.trace_bits, helper.map_size) };
|
||||
let settings = NyxSettings::builder()
|
||||
.cpu_id(0)
|
||||
.snap_mode(true)
|
||||
.parallel_mode(true)
|
||||
.parent_cpu_id(Some(parent_cpu_id.0 as u32))
|
||||
.build();
|
||||
let helper = NyxHelper::new("/tmp/nyx_libxml2/", settings).unwrap();
|
||||
let observer = unsafe {
|
||||
StdMapObserver::from_mut_ptr("trace", helper.bitmap_buffer, helper.bitmap_size)
|
||||
};
|
||||
|
||||
let input = BytesInput::new(b"22".to_vec());
|
||||
let rand = StdRand::new();
|
||||
@ -60,7 +56,7 @@ fn main() {
|
||||
let mut feedback = MaxMapFeedback::new(&observer);
|
||||
let mut objective = CrashFeedback::new();
|
||||
let scheduler = RandScheduler::new();
|
||||
let mut executor = NyxExecutor::new(&mut helper, tuple_list!(observer)).unwrap();
|
||||
let mut executor = NyxExecutor::new(helper, tuple_list!(observer));
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use libafl::{
|
||||
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus, Testcase},
|
||||
@ -17,17 +17,19 @@ use libafl_bolts::{
|
||||
rands::{RandomSeed, StdRand},
|
||||
tuples::tuple_list,
|
||||
};
|
||||
use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper};
|
||||
use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings};
|
||||
|
||||
fn main() {
|
||||
let share_dir = Path::new("/tmp/nyx_libxml2/");
|
||||
let cpu_id = 0;
|
||||
let parallel_mode = false;
|
||||
|
||||
// nyx stuff
|
||||
let mut helper = NyxHelper::new(share_dir, cpu_id, true, parallel_mode, None).unwrap();
|
||||
let settings = NyxSettings::builder()
|
||||
.cpu_id(0)
|
||||
.snap_mode(true)
|
||||
.parallel_mode(false)
|
||||
.parent_cpu_id(None)
|
||||
.build();
|
||||
let helper = NyxHelper::new("/tmp/nyx_libxml2/", settings).unwrap();
|
||||
let observer =
|
||||
unsafe { StdMapObserver::from_mut_ptr("trace", helper.trace_bits, helper.map_size) };
|
||||
unsafe { StdMapObserver::from_mut_ptr("trace", helper.bitmap_buffer, helper.bitmap_size) };
|
||||
|
||||
let input = BytesInput::new(b"22".to_vec());
|
||||
let rand = StdRand::new();
|
||||
@ -50,7 +52,7 @@ fn main() {
|
||||
let monitor = TuiMonitor::new(ui);
|
||||
|
||||
let mut mgr = SimpleEventManager::new(monitor);
|
||||
let mut executor = NyxExecutor::new(&mut helper, tuple_list!(observer)).unwrap();
|
||||
let mut executor = NyxExecutor::new(helper, tuple_list!(observer));
|
||||
let mutator = StdScheduledMutator::new(havoc_mutations());
|
||||
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
|
||||
|
||||
|
@ -14,7 +14,9 @@ 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 = "acaf7f6" }
|
||||
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"] }
|
||||
|
||||
typed-builder = "0.18.1"
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use libafl::{
|
||||
executors::{Executor, ExitKind, HasObservers},
|
||||
@ -13,31 +13,23 @@ use libnyx::NyxReturnValue;
|
||||
use crate::helper::NyxHelper;
|
||||
|
||||
/// executor for nyx standalone mode
|
||||
pub struct NyxExecutor<'a, S, OT> {
|
||||
pub struct NyxExecutor<S, OT> {
|
||||
/// implement nyx function
|
||||
pub helper: &'a mut NyxHelper,
|
||||
pub helper: NyxHelper,
|
||||
/// observers
|
||||
observers: OT,
|
||||
/// phantom data to keep generic type <I,S>
|
||||
phantom: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<'a, S, OT> Debug for NyxExecutor<'a, S, OT> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("NyxInprocessExecutor")
|
||||
.field("helper", &self.helper)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S, OT> UsesState for NyxExecutor<'a, S, OT>
|
||||
impl<S, OT> UsesState for NyxExecutor<S, OT>
|
||||
where
|
||||
S: State,
|
||||
{
|
||||
type State = S;
|
||||
}
|
||||
|
||||
impl<'a, S, OT> UsesObservers for NyxExecutor<'a, S, OT>
|
||||
impl<S, OT> UsesObservers for NyxExecutor<S, OT>
|
||||
where
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
@ -45,7 +37,7 @@ where
|
||||
type Observers = OT;
|
||||
}
|
||||
|
||||
impl<'a, EM, S, Z, OT> Executor<EM, Z> for NyxExecutor<'a, S, OT>
|
||||
impl<EM, S, Z, OT> Executor<EM, Z> for NyxExecutor<S, OT>
|
||||
where
|
||||
EM: UsesState<State = S>,
|
||||
S: State + HasExecutions,
|
||||
@ -60,56 +52,59 @@ where
|
||||
input: &Self::Input,
|
||||
) -> Result<ExitKind, Error> {
|
||||
*state.executions_mut() += 1;
|
||||
let input_owned = input.target_bytes();
|
||||
let input = input_owned.as_slice();
|
||||
self.helper.nyx_process.set_input(
|
||||
input,
|
||||
input
|
||||
.len()
|
||||
.try_into()
|
||||
.expect("Inputs larger than 4GB not supported"),
|
||||
);
|
||||
|
||||
let bytes = input.target_bytes();
|
||||
let buffer = bytes.as_slice();
|
||||
let size = u32::try_from(buffer.len())
|
||||
.map_err(|_| Error::unsupported("Inputs larger than 4GB are not supported"))?;
|
||||
|
||||
self.helper.nyx_process.set_input(buffer, size);
|
||||
|
||||
// exec will take care of trace_bits, so no need to reset
|
||||
let ret_val = self.helper.nyx_process.exec();
|
||||
match ret_val {
|
||||
match self.helper.nyx_process.exec() {
|
||||
NyxReturnValue::Normal => Ok(ExitKind::Ok),
|
||||
NyxReturnValue::Crash | NyxReturnValue::Asan => Ok(ExitKind::Crash),
|
||||
NyxReturnValue::Timeout => Ok(ExitKind::Timeout),
|
||||
NyxReturnValue::InvalidWriteToPayload => Err(libafl::Error::illegal_state(
|
||||
"FixMe: Nyx InvalidWriteToPayload handler is missing",
|
||||
)),
|
||||
NyxReturnValue::Error => Err(libafl::Error::illegal_state(
|
||||
"Error: Nyx runtime error has occurred...",
|
||||
)),
|
||||
NyxReturnValue::InvalidWriteToPayload => {
|
||||
self.helper.nyx_process.shutdown();
|
||||
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"))
|
||||
}
|
||||
NyxReturnValue::IoError => {
|
||||
// todo! *stop_soon_p = 0
|
||||
Err(libafl::Error::unknown("Error: QEMU-nyx died..."))
|
||||
self.helper.nyx_process.shutdown();
|
||||
Err(Error::unknown("QEMU-nyx died"))
|
||||
}
|
||||
NyxReturnValue::Abort => {
|
||||
self.helper.nyx_process.shutdown();
|
||||
Err(libafl::Error::shutting_down())
|
||||
Err(Error::shutting_down())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S, OT> NyxExecutor<'a, S, OT> {
|
||||
pub fn new(helper: &'a mut NyxHelper, observers: OT) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
impl<S, OT> NyxExecutor<S, OT> {
|
||||
pub fn new(helper: NyxHelper, observers: OT) -> Self {
|
||||
Self {
|
||||
helper,
|
||||
observers,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// convert `trace_bits` ptr into real trace map
|
||||
pub fn trace_bits(self) -> &'static mut [u8] {
|
||||
unsafe { std::slice::from_raw_parts_mut(self.helper.trace_bits, self.helper.real_map_size) }
|
||||
unsafe {
|
||||
std::slice::from_raw_parts_mut(self.helper.bitmap_buffer, self.helper.bitmap_size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S, OT> HasObservers for NyxExecutor<'a, S, OT>
|
||||
impl<S, OT> HasObservers for NyxExecutor<S, OT>
|
||||
where
|
||||
S: State,
|
||||
OT: ObserversTuple<S>,
|
||||
|
@ -1,25 +1,18 @@
|
||||
/// [`NyxHelper`] is used to wrap `NyxProcess`
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
path::Path,
|
||||
time::Duration,
|
||||
};
|
||||
use std::{fmt::Debug, path::Path};
|
||||
|
||||
use libafl::Error;
|
||||
use libnyx::{NyxProcess, NyxReturnValue};
|
||||
use libnyx::NyxProcess;
|
||||
|
||||
use crate::settings::NyxSettings;
|
||||
|
||||
const INIT_TIMEOUT: Duration = Duration::new(2, 0);
|
||||
pub struct NyxHelper {
|
||||
pub nyx_process: NyxProcess,
|
||||
/// real size of `trace_bits`
|
||||
pub real_map_size: usize,
|
||||
// real size of the trace_bits
|
||||
pub map_size: usize,
|
||||
/// shared memory with instruction bitmaps
|
||||
pub trace_bits: *mut u8,
|
||||
|
||||
pub bitmap_size: usize,
|
||||
pub bitmap_buffer: *mut u8,
|
||||
}
|
||||
|
||||
const MAX_FILE: u32 = 1024 * 1024;
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum NyxProcessType {
|
||||
/// stand alone mode
|
||||
@ -29,125 +22,77 @@ pub enum NyxProcessType {
|
||||
/// parallel mode's child, consume snapshot and execute
|
||||
CHILD,
|
||||
}
|
||||
|
||||
impl NyxHelper {
|
||||
/// Create [`NyxProcess`] and do basic settings
|
||||
/// It will convert instance to parent or child using `parent_cpu_id` when set`parallel_mode`
|
||||
/// will fail if initial connection takes more than 2 seconds
|
||||
pub fn new(
|
||||
target_dir: &Path,
|
||||
cpu_id: u32,
|
||||
snap_mode: bool,
|
||||
parallel_mode: bool,
|
||||
parent_cpu_id: Option<u32>,
|
||||
) -> Result<Self, Error> {
|
||||
NyxHelper::with_initial_timeout(
|
||||
target_dir,
|
||||
cpu_id,
|
||||
snap_mode,
|
||||
parallel_mode,
|
||||
parent_cpu_id,
|
||||
INIT_TIMEOUT,
|
||||
)
|
||||
}
|
||||
/// Create [`NyxProcess`] and do basic settings
|
||||
/// It will convert instance to parent or child using `parent_cpu_id` when set`parallel_mode`
|
||||
/// will fail if initial connection takes more than `initial_timeout` seconds
|
||||
pub fn with_initial_timeout(
|
||||
target_dir: &Path,
|
||||
cpu_id: u32,
|
||||
snap_mode: bool,
|
||||
parallel_mode: bool,
|
||||
parent_cpu_id: Option<u32>,
|
||||
initial_timeout: Duration,
|
||||
) -> Result<Self, Error> {
|
||||
let Some(sharedir) = target_dir.to_str() else {
|
||||
return Err(Error::illegal_argument("can't convert sharedir to str"));
|
||||
};
|
||||
let work_dir = target_dir.join("workdir");
|
||||
let work_dir = work_dir.to_str().expect("unable to convert workdir to str");
|
||||
let nyx_type = if parallel_mode {
|
||||
let Some(parent_cpu_id) = parent_cpu_id else {
|
||||
return Err(Error::illegal_argument(
|
||||
"please set parent_cpu_id in nyx parallel mode",
|
||||
));
|
||||
};
|
||||
if cpu_id == parent_cpu_id {
|
||||
/// Create [`NyxProcess`] and do basic settings. It will convert the
|
||||
/// instance to a parent or child using `parent_cpu_id` when
|
||||
/// `parallel_mode` is set.
|
||||
pub fn new<P>(share_dir: P, settings: NyxSettings) -> Result<Self, Error>
|
||||
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.parallel_mode, settings.parent_cpu_id) {
|
||||
(false, _) => NyxProcessType::ALONE,
|
||||
(true, Some(parent_cpu_id)) if settings.cpu_id == parent_cpu_id => {
|
||||
NyxProcessType::PARENT
|
||||
} else {
|
||||
NyxProcessType::CHILD
|
||||
}
|
||||
} else {
|
||||
NyxProcessType::ALONE
|
||||
};
|
||||
(true, Some(_)) => NyxProcessType::CHILD,
|
||||
|
||||
let nyx_process = match nyx_type {
|
||||
NyxProcessType::ALONE => NyxProcess::new(sharedir, work_dir, cpu_id, MAX_FILE, true),
|
||||
NyxProcessType::PARENT => {
|
||||
NyxProcess::new_parent(sharedir, work_dir, cpu_id, MAX_FILE, true)
|
||||
(true, _) => {
|
||||
return Err(Error::illegal_argument(
|
||||
"`parent_cpu_id` is required in nyx parallel mode",
|
||||
))
|
||||
}
|
||||
NyxProcessType::CHILD => NyxProcess::new_child(sharedir, work_dir, cpu_id, cpu_id),
|
||||
};
|
||||
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_process =
|
||||
nyx_process.map_err(|msg: String| -> Error { Error::illegal_argument(msg) })?;
|
||||
|
||||
let real_map_size = nyx_process.bitmap_buffer_size();
|
||||
let map_size = ((real_map_size + 63) >> 6) << 6;
|
||||
let trace_bits = nyx_process.bitmap_buffer_mut().as_mut_ptr();
|
||||
nyx_process.option_set_reload_mode(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_apply();
|
||||
|
||||
// default timeout for initial dry-run
|
||||
let sec = initial_timeout
|
||||
.as_secs()
|
||||
.try_into()
|
||||
.map_err(|_| -> Error { Error::illegal_argument("can't cast time's sec to u8") })?;
|
||||
let bitmap_size = nyx_process.bitmap_buffer_size();
|
||||
let bitmap_buffer = nyx_process.bitmap_buffer_mut().as_mut_ptr();
|
||||
|
||||
let micro_sec: u32 = initial_timeout.subsec_micros();
|
||||
nyx_process.option_set_timeout(sec, micro_sec);
|
||||
nyx_process.option_apply();
|
||||
|
||||
// dry run to check if qemu is spawned
|
||||
nyx_process.set_input(b"INIT", 4);
|
||||
match nyx_process.exec() {
|
||||
NyxReturnValue::Error => {
|
||||
nyx_process.shutdown();
|
||||
let msg = "Error: Nyx runtime error has occurred...";
|
||||
return Err(Error::illegal_state(msg));
|
||||
}
|
||||
NyxReturnValue::IoError => {
|
||||
let msg = "Error: QEMU-nyx died...";
|
||||
return Err(Error::illegal_state(msg));
|
||||
}
|
||||
NyxReturnValue::Abort => {
|
||||
nyx_process.shutdown();
|
||||
let msg = "Error: Nyx abort occurred...";
|
||||
return Err(Error::illegal_state(msg));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(Self {
|
||||
nyx_process,
|
||||
real_map_size,
|
||||
map_size,
|
||||
trace_bits,
|
||||
bitmap_size,
|
||||
bitmap_buffer,
|
||||
})
|
||||
}
|
||||
|
||||
/// Set a timeout for Nyx
|
||||
pub fn set_timeout(&mut self, time: Duration) {
|
||||
let sec: u8 = time
|
||||
.as_secs()
|
||||
.try_into()
|
||||
.expect("can't cast time's sec to u8");
|
||||
let micro_sec: u32 = time.subsec_micros();
|
||||
self.nyx_process.option_set_timeout(sec, micro_sec);
|
||||
/// Set a timeout for Nyx.
|
||||
pub fn set_timeout(&mut self, secs: u8, micro_secs: u32) {
|
||||
self.nyx_process.option_set_timeout(secs, micro_secs);
|
||||
self.nyx_process.option_apply();
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for NyxHelper {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("NyxInprocessHelper").finish()
|
||||
}
|
||||
}
|
||||
|
@ -3,3 +3,5 @@
|
||||
pub mod executor;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod helper;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod settings;
|
||||
|
23
libafl_nyx/src/settings.rs
Normal file
23
libafl_nyx/src/settings.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
const DEFAULT_INPUT_BUFFER_SIZE: u32 = 1024 * 1024;
|
||||
const DEFAULT_TIMEOUT_SECS: u8 = 2;
|
||||
const DEFAULT_TIMEOUT_MICRO_SECS: u32 = 0;
|
||||
|
||||
#[derive(Debug, Clone, Copy, TypedBuilder)]
|
||||
pub struct NyxSettings {
|
||||
pub cpu_id: u32,
|
||||
pub parent_cpu_id: Option<u32>,
|
||||
|
||||
pub snap_mode: bool,
|
||||
pub parallel_mode: bool,
|
||||
|
||||
#[builder(default = DEFAULT_INPUT_BUFFER_SIZE)]
|
||||
pub input_buffer_size: u32,
|
||||
|
||||
#[builder(default = DEFAULT_TIMEOUT_SECS)]
|
||||
pub timeout_secs: u8,
|
||||
|
||||
#[builder(default = DEFAULT_TIMEOUT_MICRO_SECS)]
|
||||
pub timeout_micro_secs: u32,
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user