Writing Custom Mutators in Rust
Relevant source files
The following files were used as context for generating this wiki page: - [custom_mutators/rust/README.md](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/custom_mutators/rust/README.md) - [custom_mutators/rust/custom_mutator/src/lib.rs](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/custom_mutators/rust/custom_mutator/src/lib.rs) - [custom_mutators/rust/custom_mutator/Cargo.toml](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/custom_mutators/rust/custom_mutator/Cargo.toml) - [custom_mutators/rust/example/Cargo.toml](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/custom_mutators/rust/example/Cargo.toml) - [include/afl-fuzz.h](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/include/afl-fuzz.h) - [src/afl-fuzz-mutators.c](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/src/afl-fuzz-mutators.c) - [custom_mutators/README.md](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/custom_mutators/README.md)AFL++ provides support for creating custom mutators in Rust, allowing developers to leverage Rust's safety, performance, and rich ecosystem to implement complex and structured mutation strategies. The custom_mutator crate offers safe and ergonomic bindings for the AFL++ custom mutator C API, simplifying the integration process.
This document outlines the architecture, API, and steps required to build and use a custom mutator written in Rust. The bindings are designed to be panic-safe, preventing panics from unwinding into the C code of AFL++ by aborting the process at the FFI boundary.
Sources: custom_mutators/rust/custom_mutator/src/lib.rs:2-17, custom_mutators/rust/README.md
Setup and Build Process
To create a Rust custom mutator, you must define your crate as a dynamic system library (cdylib) so that AFL++ can load it at runtime.
Cargo Configuration
Add the following to your Cargo.toml to produce a .so shared library:
[lib]
crate-type = ["cdylib"]
Your Cargo.toml will also need to include the custom_mutator crate as a dependency.
[dependencies]
custom_mutator = { path = "../custom_mutator" }
An example configuration for a mutator named example_mutator would look like this:
[package]
name = "example_mutator"
version = "0.1.0"
authors = ["Julius Hohnerlein <julihoh@users.noreply.github.com>"]
edition = "2021"
[dependencies]
custom_mutator = { path = "../custom_mutator" }
[[example]]
name = "example_mutator"
path = "./src/example_mutator.rs"
crate-type = ["cdylib"]
Sources: custom_mutators/rust/custom_mutator/src/lib.rs:18-24, custom_mutators/rust/example/Cargo.toml:1-14
Building and Documentation
You can build an example mutator with a standard Cargo command:
cargo build --example example_mutator
The Rust bindings are documented using rustdoc. To view the generated documentation, run:
cargo doc -p custom_mutator --open
Sources: custom_mutators/rust/README.md:5-8
Architecture and FFI Bridge
The Rust bindings provide a safe abstraction over the C-based custom mutator API of AFL++. The core components are the CustomMutator trait, which developers implement, and the export_mutator! macro, which exposes the Rust code to the C world.
The following diagram illustrates the interaction between AFL++, the Rust bindings, and the user's custom mutator implementation.
This diagram shows the call chain from the afl-fuzz process to the user-defined Rust mutator logic.
- AFL++ (
afl-fuzz): Loads the compiled.sofile and resolves symbols likeafl_custom_initusingdlopenanddlsym. - Exported C-ABI Functions: The
export_mutator!macro generates the C-compatible functions that AFL++ calls. wrappersModule: These internal Rust functions handle the unsafe FFI boundary, catching panics, and translating C types (*const c_char, raw pointers) to safe Rust types (&Path, slices).RawCustomMutatorTrait: An internal,unsafetrait that mirrors the C API. TheCustomMutatortrait is a safe wrapper around this.CustomMutatorTrait: The public, safe trait that developers implement. It usesResultfor error handling.- User Implementation: A struct that implements the
CustomMutatortrait, containing the actual mutation logic.
Sources: src/afl-fuzz-mutators.c:166-173, custom_mutators/rust/custom_mutator/src/lib.rs:38-40, 575-598, include/afl-fuzz.h:1395-1402
The CustomMutator Trait
The central piece of the Rust bindings is the CustomMutator trait. Developers must implement this trait for their mutator struct.
Sources: custom_mutators/rust/custom_mutator/src/lib.rs:693-696
Key Methods
The following table details the methods available in the CustomMutator trait. Most methods return a Result, allowing for graceful error handling.
| Method | Description |
|---|---|
init(afl: &'static afl_state, seed: u32) | Initializes the mutator. Receives the afl_state (if afl_internals feature is enabled) and a random seed. |
fuzz<'b, 's: 'b>(&'s mut self, buffer: &'b mut [u8], add_buff: Option<&[u8]>, max_size: usize) | The primary mutation function. It receives the input buffer to mutate, an optional additional buffer for splicing, and the maximum allowed output size. It should return an Option containing a slice of the mutated data. |
fuzz_count(&mut self, buffer: &[u8]) | Determines how many times fuzz should be called for a given input. Defaults to 1. |
post_process<'b, 's: 'b>(&'s mut self, buffer: &'b mut [u8]) | A function to fix up a mutated input before it is sent to the target. This should not perform random changes. |
queue_get(&mut self, filename: &Path) | Called to determine if a specific queue entry should be fuzzed by this mutator. Defaults to true. |
queue_new_entry(&mut self, filename_new_queue: &Path, filename_orig_queue: Option<&Path>) | A hook that is called whenever a new entry is added to the queue. Can be used for additional analysis. |
describe(&mut self, max_description: usize) | Returns a string describing the last mutation performed. Used for naming crashing test cases. |
introspection(&mut self) | Returns a string containing internal state or information about the mutator. |
handle_error(err: Self::Error) | A static method to handle errors returned by other trait methods. By default, it prints the error to stderr if AFL_CUSTOM_MUTATOR_DEBUG is set. |
Sources: custom_mutators/rust/custom_mutator/src/lib.rs:693-762
Exporting the Mutator
After implementing the CustomMutator trait, you must use the export_mutator! macro to generate the necessary C FFI exports.
struct MyMutator;
impl CustomMutator for MyMutator {
// ... implementation of trait methods
}
// Export the mutator to be loaded by AFL++
export_mutator!(MyMutator);
This macro generates all the #[no_mangle] pub extern "C" functions that AFL++ expects, such as afl_custom_init, afl_custom_fuzz, and afl_custom_deinit.
Sources: custom_mutators/rust/custom_mutator/src/lib.rs:540-643
Mutator Lifecycle and Data Flow
The lifecycle of a Rust custom mutator is managed by afl-fuzz. The following sequence diagram shows the key interactions.
The FFIContext<M> struct is a key internal component that holds the user's mutator state (M), along with buffers for description and introspection strings. It is created in afl_custom_init and destroyed in afl_custom_deinit.
Sources: custom_mutators/rust/custom_mutator/src/lib.rs:100-111, 150-157, 313-319
Advanced Features
Access to AFL++ Internals
By enabling the afl_internals feature in Cargo.toml, the init function of the CustomMutator trait gains access to a 'static reference to the afl_state struct. This provides read-only access to AFL++'s internal state, enabling more sophisticated, context-aware mutation strategies.
This feature depends on the custom_mutator-sys crate, which uses bindgen to generate Rust types from AFL++'s C headers.
Cargo.toml:
[features]
afl_internals = ["custom_mutator-sys"]
[dependencies]
custom_mutator-sys = { path = "../custom_mutator-sys", optional=true }
Mutator init signature:
#[cfg(feature = "afl_internals")]
fn init(afl: &'static afl_state, seed: u32) -> Result<Self, Self::Error>
where
Self: Sized;
Sources: custom_mutators/rust/custom_mutator/src/lib.rs:30-34, 722-726, custom_mutators/rust/custom_mutator/Cargo.toml:10-13
Example: Structured Fuzzing with lain
The repository includes examples of structured fuzzing using the lain crate.
example_lain: Demonstrates generating a new structure at each iteration.example_lain_post_process: Shows how to use thepost_processhook. This example usesbincodeto serialize alain-generated structure. It requires specific environment variables to function correctly, as it needs to work with the raw, untrimmed input:AFL_DISABLE_TRIM=1AFL_NO_STARTUP_CALIBRATION=1(if not providing abincode-serialized seed)
Sources: custom_mutators/rust/README.md:10-24, custom_mutators/rust/example_lain/Cargo.toml, custom_mutators/rust/example_lain_post_process/Cargo.toml
Conclusion
The Rust bindings for AFL++ custom mutators provide a powerful and safe way to extend the fuzzer's capabilities. By abstracting away the complexities of the C FFI, developers can focus on writing effective mutation logic in modern Rust. The CustomMutator trait, combined with the export_mutator! macro, offers a streamlined development experience, while optional features like afl_internals provide the depth needed for advanced, stateful fuzzing strategies.