mirror of
https://github.com/AFLplusplus/AFLplusplus.git
synced 2025-06-11 17:51:32 +00:00
Ergonomic Improvements for Rust Custom Mutators (#772)
* allow returning of str instead of CStr in rust custom mutator * use OsStr instead of CStr for file paths * fix cfg and compiler errors * custom mutator default descriptions * fix usage of afl_internal feature flag * fix example mutator cfg * fix lain mutator * Revert "fix lain mutator" This reverts commit adf700180888d86e8331939cea62d0b39fc699a7. * actually fix lain mutator * resolve question around utf-8 null bytes * change from OsStr to Path to be even more ergonomic * add rust custom mutator ci * fix github action * again * separate compilation check
This commit is contained in:
parent
9844e1a856
commit
2dd5a02061
30
.github/workflows/rust_custom_mutator.yml
vendored
Normal file
30
.github/workflows/rust_custom_mutator.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
name: Rust Custom Mutators
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ stable, dev ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ stable, dev ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test Rust Custom Mutator Support
|
||||||
|
runs-on: '${{ matrix.os }}'
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: custom_mutators/rust
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-20.04]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Rust Toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
- name: Check Code Compiles
|
||||||
|
run: cargo check
|
||||||
|
- name: Run General Tests
|
||||||
|
run: cargo test
|
||||||
|
- name: Run Tests for afl_internals feature flag
|
||||||
|
run: cd custom_mutator && cargo test --features=afl_internals
|
@ -1,3 +1,4 @@
|
|||||||
|
#![cfg(unix)]
|
||||||
//! Somewhat safe and somewhat ergonomic bindings for creating [AFL++](https://github.com/AFLplusplus/AFLplusplus) [custom mutators](https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/custom_mutators.md) in Rust.
|
//! Somewhat safe and somewhat ergonomic bindings for creating [AFL++](https://github.com/AFLplusplus/AFLplusplus) [custom mutators](https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/custom_mutators.md) in Rust.
|
||||||
//!
|
//!
|
||||||
//! # Usage
|
//! # Usage
|
||||||
@ -23,7 +24,7 @@
|
|||||||
//! The state is passed to [`CustomMutator::init`], when the feature is activated.
|
//! The state is passed to [`CustomMutator::init`], when the feature is activated.
|
||||||
//!
|
//!
|
||||||
//! _This is completely unsafe and uses automatically generated types extracted from the AFL++ source._
|
//! _This is completely unsafe and uses automatically generated types extracted from the AFL++ source._
|
||||||
use std::{ffi::CStr, fmt::Debug};
|
use std::{fmt::Debug, path::Path};
|
||||||
|
|
||||||
#[cfg(feature = "afl_internals")]
|
#[cfg(feature = "afl_internals")]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@ -33,7 +34,7 @@ pub use custom_mutator_sys::afl_state;
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait RawCustomMutator {
|
pub trait RawCustomMutator {
|
||||||
#[cfg(feature = "afl_internals")]
|
#[cfg(feature = "afl_internals")]
|
||||||
fn init(afl: &'static afl_state, seed: c_uint) -> Self
|
fn init(afl: &'static afl_state, seed: u32) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
#[cfg(not(feature = "afl_internals"))]
|
#[cfg(not(feature = "afl_internals"))]
|
||||||
@ -52,17 +53,17 @@ pub trait RawCustomMutator {
|
|||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queue_new_entry(&mut self, filename_new_queue: &CStr, _filename_orig_queue: Option<&CStr>) {}
|
fn queue_new_entry(&mut self, filename_new_queue: &Path, _filename_orig_queue: Option<&Path>) {}
|
||||||
|
|
||||||
fn queue_get(&mut self, filename: &CStr) -> bool {
|
fn queue_get(&mut self, filename: &Path) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn describe(&mut self, max_description: usize) -> Option<&CStr> {
|
fn describe(&mut self, max_description: usize) -> Option<&str> {
|
||||||
None
|
Some(default_mutator_describe::<Self>(max_description))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn introspection(&mut self) -> Option<&CStr> {
|
fn introspection(&mut self) -> Option<&str> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,16 +82,17 @@ pub mod wrappers {
|
|||||||
#[cfg(feature = "afl_internals")]
|
#[cfg(feature = "afl_internals")]
|
||||||
use custom_mutator_sys::afl_state;
|
use custom_mutator_sys::afl_state;
|
||||||
|
|
||||||
use core::slice;
|
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
ffi::{c_void, CStr},
|
ffi::{c_void, CStr, OsStr},
|
||||||
mem::ManuallyDrop,
|
mem::ManuallyDrop,
|
||||||
os::raw::c_char,
|
os::{raw::c_char, unix::ffi::OsStrExt},
|
||||||
panic::catch_unwind,
|
panic::catch_unwind,
|
||||||
|
path::Path,
|
||||||
process::abort,
|
process::abort,
|
||||||
ptr::null,
|
ptr::null,
|
||||||
|
slice,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::RawCustomMutator;
|
use crate::RawCustomMutator;
|
||||||
@ -99,6 +101,10 @@ pub mod wrappers {
|
|||||||
/// Also has some convenience functions for FFI conversions (from and to ptr) and tries to make misuse hard (see [`FFIContext::from`]).
|
/// Also has some convenience functions for FFI conversions (from and to ptr) and tries to make misuse hard (see [`FFIContext::from`]).
|
||||||
struct FFIContext<M: RawCustomMutator> {
|
struct FFIContext<M: RawCustomMutator> {
|
||||||
mutator: M,
|
mutator: M,
|
||||||
|
/// buffer for storing the description returned by [`RawCustomMutator::describe`] as a CString
|
||||||
|
description_buffer: Vec<u8>,
|
||||||
|
/// buffer for storing the introspection returned by [`RawCustomMutator::introspect`] as a CString
|
||||||
|
introspection_buffer: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: RawCustomMutator> FFIContext<M> {
|
impl<M: RawCustomMutator> FFIContext<M> {
|
||||||
@ -115,12 +121,16 @@ pub mod wrappers {
|
|||||||
fn new(afl: &'static afl_state, seed: u32) -> Box<Self> {
|
fn new(afl: &'static afl_state, seed: u32) -> Box<Self> {
|
||||||
Box::new(Self {
|
Box::new(Self {
|
||||||
mutator: M::init(afl, seed),
|
mutator: M::init(afl, seed),
|
||||||
|
description_buffer: Vec::new(),
|
||||||
|
introspection_buffer: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "afl_internals"))]
|
#[cfg(not(feature = "afl_internals"))]
|
||||||
fn new(seed: u32) -> Box<Self> {
|
fn new(seed: u32) -> Box<Self> {
|
||||||
Box::new(Self {
|
Box::new(Self {
|
||||||
mutator: M::init(seed),
|
mutator: M::init(seed),
|
||||||
|
description_buffer: Vec::new(),
|
||||||
|
introspection_buffer: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -242,9 +252,13 @@ pub mod wrappers {
|
|||||||
if filename_new_queue.is_null() {
|
if filename_new_queue.is_null() {
|
||||||
panic!("received null filename_new_queue in afl_custom_queue_new_entry");
|
panic!("received null filename_new_queue in afl_custom_queue_new_entry");
|
||||||
}
|
}
|
||||||
let filename_new_queue = unsafe { CStr::from_ptr(filename_new_queue) };
|
let filename_new_queue = Path::new(OsStr::from_bytes(
|
||||||
|
unsafe { CStr::from_ptr(filename_new_queue) }.to_bytes(),
|
||||||
|
));
|
||||||
let filename_orig_queue = if !filename_orig_queue.is_null() {
|
let filename_orig_queue = if !filename_orig_queue.is_null() {
|
||||||
Some(unsafe { CStr::from_ptr(filename_orig_queue) })
|
Some(Path::new(OsStr::from_bytes(
|
||||||
|
unsafe { CStr::from_ptr(filename_orig_queue) }.to_bytes(),
|
||||||
|
)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@ -271,9 +285,14 @@ pub mod wrappers {
|
|||||||
/// Internal function used in the macro
|
/// Internal function used in the macro
|
||||||
pub fn afl_custom_introspection_<M: RawCustomMutator>(data: *mut c_void) -> *const c_char {
|
pub fn afl_custom_introspection_<M: RawCustomMutator>(data: *mut c_void) -> *const c_char {
|
||||||
match catch_unwind(|| {
|
match catch_unwind(|| {
|
||||||
let mut context = FFIContext::<M>::from(data);
|
let context = &mut *FFIContext::<M>::from(data);
|
||||||
if let Some(res) = context.mutator.introspection() {
|
if let Some(res) = context.mutator.introspection() {
|
||||||
res.as_ptr()
|
let buf = &mut context.introspection_buffer;
|
||||||
|
buf.clear();
|
||||||
|
buf.extend_from_slice(res.as_bytes());
|
||||||
|
buf.push(0);
|
||||||
|
// unwrapping here, as the error case should be extremely rare
|
||||||
|
CStr::from_bytes_with_nul(&buf).unwrap().as_ptr()
|
||||||
} else {
|
} else {
|
||||||
null()
|
null()
|
||||||
}
|
}
|
||||||
@ -289,9 +308,14 @@ pub mod wrappers {
|
|||||||
max_description_len: usize,
|
max_description_len: usize,
|
||||||
) -> *const c_char {
|
) -> *const c_char {
|
||||||
match catch_unwind(|| {
|
match catch_unwind(|| {
|
||||||
let mut context = FFIContext::<M>::from(data);
|
let context = &mut *FFIContext::<M>::from(data);
|
||||||
if let Some(res) = context.mutator.describe(max_description_len) {
|
if let Some(res) = context.mutator.describe(max_description_len) {
|
||||||
res.as_ptr()
|
let buf = &mut context.description_buffer;
|
||||||
|
buf.clear();
|
||||||
|
buf.extend_from_slice(res.as_bytes());
|
||||||
|
buf.push(0);
|
||||||
|
// unwrapping here, as the error case should be extremely rare
|
||||||
|
CStr::from_bytes_with_nul(&buf).unwrap().as_ptr()
|
||||||
} else {
|
} else {
|
||||||
null()
|
null()
|
||||||
}
|
}
|
||||||
@ -310,9 +334,9 @@ pub mod wrappers {
|
|||||||
let mut context = FFIContext::<M>::from(data);
|
let mut context = FFIContext::<M>::from(data);
|
||||||
assert!(!filename.is_null());
|
assert!(!filename.is_null());
|
||||||
|
|
||||||
context
|
context.mutator.queue_get(Path::new(OsStr::from_bytes(
|
||||||
.mutator
|
unsafe { CStr::from_ptr(filename) }.to_bytes(),
|
||||||
.queue_get(unsafe { CStr::from_ptr(filename) }) as u8
|
))) as u8
|
||||||
}) {
|
}) {
|
||||||
Ok(ret) => ret,
|
Ok(ret) => ret,
|
||||||
Err(err) => panic_handler("afl_custom_queue_get", err),
|
Err(err) => panic_handler("afl_custom_queue_get", err),
|
||||||
@ -516,21 +540,21 @@ pub trait CustomMutator {
|
|||||||
|
|
||||||
fn queue_new_entry(
|
fn queue_new_entry(
|
||||||
&mut self,
|
&mut self,
|
||||||
filename_new_queue: &CStr,
|
filename_new_queue: &Path,
|
||||||
filename_orig_queue: Option<&CStr>,
|
filename_orig_queue: Option<&Path>,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queue_get(&mut self, filename: &CStr) -> Result<bool, Self::Error> {
|
fn queue_get(&mut self, filename: &Path) -> Result<bool, Self::Error> {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn describe(&mut self, max_description: usize) -> Result<Option<&CStr>, Self::Error> {
|
fn describe(&mut self, max_description: usize) -> Result<Option<&str>, Self::Error> {
|
||||||
Ok(None)
|
Ok(Some(default_mutator_describe::<Self>(max_description)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn introspection(&mut self) -> Result<Option<&CStr>, Self::Error> {
|
fn introspection(&mut self) -> Result<Option<&str>, Self::Error> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -593,7 +617,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queue_new_entry(&mut self, filename_new_queue: &CStr, filename_orig_queue: Option<&CStr>) {
|
fn queue_new_entry(&mut self, filename_new_queue: &Path, filename_orig_queue: Option<&Path>) {
|
||||||
match self.queue_new_entry(filename_new_queue, filename_orig_queue) {
|
match self.queue_new_entry(filename_new_queue, filename_orig_queue) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -602,7 +626,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queue_get(&mut self, filename: &CStr) -> bool {
|
fn queue_get(&mut self, filename: &Path) -> bool {
|
||||||
match self.queue_get(filename) {
|
match self.queue_get(filename) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -612,7 +636,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn describe(&mut self, max_description: usize) -> Option<&CStr> {
|
fn describe(&mut self, max_description: usize) -> Option<&str> {
|
||||||
match self.describe(max_description) {
|
match self.describe(max_description) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -622,7 +646,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn introspection(&mut self) -> Option<&CStr> {
|
fn introspection(&mut self) -> Option<&str> {
|
||||||
match self.introspection() {
|
match self.introspection() {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -632,3 +656,85 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the default value to return from [`CustomMutator::describe`].
|
||||||
|
fn default_mutator_describe<T: ?Sized>(max_len: usize) -> &'static str {
|
||||||
|
truncate_str_unicode_safe(std::any::type_name::<T>(), max_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, not(feature = "afl_internals")))]
|
||||||
|
mod default_mutator_describe {
|
||||||
|
struct MyMutator;
|
||||||
|
use super::CustomMutator;
|
||||||
|
impl CustomMutator for MyMutator {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn init(_: u32) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fuzz<'b, 's: 'b>(
|
||||||
|
&'s mut self,
|
||||||
|
_: &'b mut [u8],
|
||||||
|
_: Option<&[u8]>,
|
||||||
|
_: usize,
|
||||||
|
) -> Result<Option<&'b [u8]>, Self::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_describe() {
|
||||||
|
assert_eq!(
|
||||||
|
MyMutator::init(0).unwrap().describe(64).unwrap().unwrap(),
|
||||||
|
"custom_mutator::default_mutator_describe::MyMutator"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// little helper function to truncate a `str` to a maximum of bytes while retaining unicode safety
|
||||||
|
fn truncate_str_unicode_safe(s: &str, max_len: usize) -> &str {
|
||||||
|
if s.len() <= max_len {
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
if let Some((last_index, _)) = s
|
||||||
|
.char_indices()
|
||||||
|
.take_while(|(index, _)| *index <= max_len)
|
||||||
|
.last()
|
||||||
|
{
|
||||||
|
&s[..last_index]
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod truncate_test {
|
||||||
|
use super::truncate_str_unicode_safe;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_truncate() {
|
||||||
|
for (max_len, input, expected_output) in &[
|
||||||
|
(0usize, "a", ""),
|
||||||
|
(1, "a", "a"),
|
||||||
|
(1, "ä", ""),
|
||||||
|
(2, "ä", "ä"),
|
||||||
|
(3, "äa", "äa"),
|
||||||
|
(4, "äa", "äa"),
|
||||||
|
(1, "👎", ""),
|
||||||
|
(2, "👎", ""),
|
||||||
|
(3, "👎", ""),
|
||||||
|
(4, "👎", "👎"),
|
||||||
|
(1, "abc", "a"),
|
||||||
|
(2, "abc", "ab"),
|
||||||
|
] {
|
||||||
|
let actual_output = truncate_str_unicode_safe(input, *max_len);
|
||||||
|
assert_eq!(
|
||||||
|
&actual_output, expected_output,
|
||||||
|
"{:#?} truncated to {} bytes should be {:#?}, but is {:#?}",
|
||||||
|
input, max_len, expected_output, actual_output
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#![cfg(unix)]
|
||||||
#![allow(unused_variables)]
|
#![allow(unused_variables)]
|
||||||
|
|
||||||
use custom_mutator::{export_mutator, CustomMutator};
|
use custom_mutator::{export_mutator, CustomMutator};
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#![cfg(unix)]
|
||||||
|
|
||||||
use custom_mutator::{export_mutator, CustomMutator};
|
use custom_mutator::{export_mutator, CustomMutator};
|
||||||
use lain::{
|
use lain::{
|
||||||
mutator::Mutator,
|
mutator::Mutator,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user