Skip to content

Commit

Permalink
Add NyxCmpObserver to libafl_nyx
Browse files Browse the repository at this point in the history
  • Loading branch information
d0ntrash committed Jan 10, 2025
1 parent ca647f0 commit 1f9f1f8
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 1 deletion.
6 changes: 6 additions & 0 deletions libafl_nyx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ libafl_targets = { workspace = true, default-features = true, features = [

nix = { workspace = true, default-features = true, features = ["fs"] }
typed-builder = { workspace = true }
lazy_static = "1.5.0"
regex = "1.11.1"
hex = "0.4.3"
serde = { version = "1.0.210", default-features = false, features = [
"alloc",
] } # serialization lib

[lints]
workspace = true
222 changes: 222 additions & 0 deletions libafl_nyx/src/cmplog.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
//! The Nyx `CmpLog` Observer
//!
//! Reads and parses the redqueen results written by QEMU-Nyx and adds them to the state as `CmpValuesMetadata`.
use std::borrow::Cow;

use lazy_static::lazy_static;
use libafl::{
executors::ExitKind,
observers::{CmpValues, CmpValuesMetadata, Observer},
state::HasExecutions,
Error, HasMetadata,
};
use libafl_bolts::Named;
pub use libafl_targets::{
cmps::{
__libafl_targets_cmplog_instructions, __libafl_targets_cmplog_routines, CMPLOG_ENABLED,
},
CmpLogMap, CmpLogObserver, CMPLOG_MAP_H, CMPLOG_MAP_PTR, CMPLOG_MAP_SIZE, CMPLOG_MAP_W,
};
use serde::{Deserialize, Serialize};

/// A [`CmpObserver`] observer for Nyx
#[derive(Serialize, Deserialize, Debug)]
pub struct NyxCmpObserver {
/// Observer name
name: Cow<'static, str>,
/// Path to redqueen results file
path: Cow<'static, str>,
add_meta: bool,
}

impl NyxCmpObserver {
/// Creates a new [`struct@NyxCmpObserver`] with the given filepath.
#[must_use]
pub fn new(name: &'static str, path: String, add_meta: bool) -> Self {
Self {
name: Cow::from(name),
path: Cow::from(path),
add_meta,
}
}
}

impl<I, S> Observer<I, S> for NyxCmpObserver
where
S: HasMetadata + HasExecutions,
I: std::fmt::Debug,
{
fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
unsafe {
CMPLOG_ENABLED = 1;
}
Ok(())
}

fn post_exec(&mut self, state: &mut S, _input: &I, _exit_kind: &ExitKind) -> Result<(), Error> {
unsafe {
CMPLOG_ENABLED = 0;
}
if self.add_meta {
let meta = state.metadata_or_insert_with(CmpValuesMetadata::new);
let rq_data = parse_redqueen_data(&std::fs::read_to_string(self.path.as_ref())?);
for event in rq_data.bps {
if let Ok(cmp_value) = event.try_into() {
meta.list.push(cmp_value);
}
}
}
Ok(())
}
}

impl Named for NyxCmpObserver {
fn name(&self) -> &Cow<'static, str> {
&self.name
}
}

// Based on https://github.com/nyx-fuzz/spec-fuzzer/blob/main/rust_fuzzer/src/runner.rs
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum RedqueenBPType {
Str,
Cmp,
Sub,
}

impl RedqueenBPType {
fn new(data: &str) -> Result<RedqueenBPType, String> {
match data {
"STR" => return Ok(Self::Str),
"CMP" => return Ok(Self::Cmp),
"SUB" => return Ok(Self::Sub),
_ => Err("Unknown redqueen type".to_string()),
}
}
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
struct RedqueenEvent {
pub addr: u64,
pub bp_type: RedqueenBPType,
pub size: usize,
pub lhs: Vec<u8>,
pub rhs: Vec<u8>,
pub imm: bool,
}

impl RedqueenEvent {
fn new(line: &str) -> Result<Self, String> {
lazy_static! {
static ref RE: regex::Regex = regex::Regex::new(
r"([0-9a-fA-F]+)\s+(CMP|SUB|STR)\s+(\d+)\s+([0-9a-fA-F]+)-([0-9a-fA-F]+)(\sIMM)?"
)
.expect("Invalid regex pattern");
}

let captures = RE
.captures(line)
.ok_or_else(|| format!("Failed to parse Redqueen line: '{}'", line))?;

let addr_s = captures.get(1).ok_or("Missing address field")?.as_str();
let type_s = captures.get(2).ok_or("Missing type field")?.as_str();
let size_s = captures.get(3).ok_or("Missing size field")?.as_str();
let lhs_s = captures.get(4).ok_or("Missing LHS field")?.as_str();
let rhs_s = captures.get(5).ok_or("Missing RHS field")?.as_str();
let imm = captures.get(6).map(|_x| true).unwrap_or(false);

let addr = u64::from_str_radix(addr_s, 16)
.map_err(|_| format!("Invalid address: '{}'", addr_s))?;
let bp_type = RedqueenBPType::new(type_s)
.map_err(|e| format!("Invalid redqueen type: '{}' - {}", type_s, e))?;
let size = size_s
.parse::<usize>()
.map_err(|_| format!("Invalid size: '{}'", size_s))?;
let lhs =
hex::decode(lhs_s).map_err(|e| format!("Failed to decode LHS: '{}' - {}", lhs_s, e))?;
let rhs =
hex::decode(rhs_s).map_err(|e| format!("Failed to decode RHS: '{}' - {}", rhs_s, e))?;

Ok(Self {
addr,
bp_type,
size,
lhs,
rhs,
imm,
})
}
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
struct RedqueenInfo {
bps: Vec<RedqueenEvent>,
}

fn parse_redqueen_data(data: &str) -> RedqueenInfo {
let bps = data
.lines()
.filter_map(|line| RedqueenEvent::new(line).ok())
.collect::<Vec<_>>();
return RedqueenInfo { bps };
}

impl TryInto<CmpValues> for RedqueenEvent {
type Error = String;

fn try_into(self) -> Result<CmpValues, Self::Error> {
match self.bp_type {
RedqueenBPType::Cmp => {
return match self.size {
8 => Ok(CmpValues::U8((
*self.rhs.first().ok_or("Invalid RHS length for U8")?,
*self.lhs.first().ok_or("Invalid LHS length for U8")?,
self.imm,
))),
16 => Ok(CmpValues::U16((
u16::from_be_bytes(
self.rhs
.try_into()
.map_err(|_| "Invalid RHS length for U16")?,
),
u16::from_be_bytes(
self.lhs
.try_into()
.map_err(|_| "Invalid LHS length for U16")?,
),
self.imm,
))),
32 => Ok(CmpValues::U32((
u32::from_be_bytes(
self.rhs
.try_into()
.map_err(|_| "Invalid RHS length for U32")?,
),
u32::from_be_bytes(
self.lhs
.try_into()
.map_err(|_| "Invalid LHS length for U32")?,
),
self.imm,
))),
64 => Ok(CmpValues::U64((
u64::from_be_bytes(
self.rhs
.try_into()
.map_err(|_| "Invalid RHS length for U64")?,
),
u64::from_be_bytes(
self.lhs
.try_into()
.map_err(|_| "Invalid LHS length for U64")?,
),
self.imm,
))),
_ => Err("Invalid size".to_string()),
}
}
// TODO: Add encoding for `STR` and `SUB`
_ => Err("Redqueen type not implemented".to_string()),
}
}
}
16 changes: 15 additions & 1 deletion libafl_nyx/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use libafl::{
use libafl_bolts::{tuples::RefIndexable, AsSlice};
use libnyx::NyxReturnValue;

use crate::helper::NyxHelper;
use crate::{cmplog::CMPLOG_ENABLED, helper::NyxHelper};

/// executor for nyx standalone mode
pub struct NyxExecutor<S, OT> {
Expand Down Expand Up @@ -88,6 +88,13 @@ where
self.helper.nyx_process.set_input(buffer, size);
self.helper.nyx_process.set_hprintf_fd(hprintf_fd);

unsafe {
if CMPLOG_ENABLED == 1 {
self.helper.nyx_process.option_set_redqueen_mode(true);
self.helper.nyx_process.option_apply();
}
}

// exec will take care of trace_bits, so no need to reset
let exit_kind = match self.helper.nyx_process.exec() {
NyxReturnValue::Normal => ExitKind::Ok,
Expand Down Expand Up @@ -124,6 +131,13 @@ where
ob.observe_stdout(&stdout);
}

unsafe {
if CMPLOG_ENABLED == 1 {
self.helper.nyx_process.option_set_redqueen_mode(false);
self.helper.nyx_process.option_apply();
}
}

Ok(exit_kind)
}
}
Expand Down
8 changes: 8 additions & 0 deletions libafl_nyx/src/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::settings::NyxSettings;
pub struct NyxHelper {
pub nyx_process: NyxProcess,
pub nyx_stdout: File,
pub redqueen_path: String,

pub timeout: Duration,

Expand Down Expand Up @@ -71,9 +72,16 @@ impl NyxHelper {
let mut timeout = Duration::from_secs(u64::from(settings.timeout_secs));
timeout += Duration::from_micros(u64::from(settings.timeout_micro_secs));

let redqueen_path = format!(
"{}/redqueen_workdir_{}/redqueen_results.txt",
nyx_config.workdir_path(),
nyx_config.worker_id()
);

Ok(Self {
nyx_process,
nyx_stdout,
redqueen_path,
timeout,
bitmap_size,
bitmap_buffer,
Expand Down
2 changes: 2 additions & 0 deletions libafl_nyx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ pub mod executor;
pub mod helper;
#[cfg(target_os = "linux")]
pub mod settings;
#[cfg(target_os = "linux")]
pub mod cmplog;

0 comments on commit 1f9f1f8

Please sign in to comment.