libafl
Builds custom fuzzers with LibAFL, the modular Rust fuzzing library. Covers composing observers, feedbacks, mutators, schedulers, and executors into a fuzzer for targets the standard tools do not fit. Use when writing a bespoke fuzzer or mutator, fuzzing a non-standard target or
Install
npx skills add https://github.com/trailofbits/skills/tree/main/plugins/testing-handbook-skills/skills/libafl
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install trailofbits-skills@llmmart
git clone https://github.com/trailofbits/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole trailofbits/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
LibAFL
LibAFL is a modular fuzzing library that implements features from AFL-based fuzzers like AFL++. Unlike traditional fuzzers, LibAFL provides all functionality in a modular and customizable way as a Rust library. It can be used as a drop-in replacement for libFuzzer or as a library to build custom fuzzers from scratch.
When to Use
| Fuzzer | Best For | Complexity |
|---|---|---|
| libFuzzer | Quick setup, single-threaded | Low |
| AFL++ | Multi-core, general purpose | Medium |
| LibAFL | Custom fuzzers, advanced features, research | High |
Choose LibAFL when:
- You need custom mutation strategies or feedback mechanisms
- Standard fuzzers don't support your target architecture
- You want to implement novel fuzzing techniques
- You need fine-grained control over fuzzing components
- You're conducting fuzzing research
Quick Start
LibAFL can be used as a drop-in replacement for libFuzzer with minimal setup:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Call your code with fuzzer-provided data
my_function(data, size);
return 0;
}
Build LibAFL's libFuzzer compatibility layer:
git clone https://github.com/AFLplusplus/LibAFL
cd LibAFL/libafl_libfuzzer_runtime
./build.sh
Compile and run:
clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
./fuzz corpus/
Installation
Prerequisites
- Clang/LLVM 15-18
- Rust (via rustup)
- Additional system dependencies
Linux/macOS
Install Clang:
apt install clang
Or install a specific version via apt.llvm.org:
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 15
Configure environment for Rust:
export RUSTFLAGS="-C linker=/usr/bin/clang-15"
export CC="clang-15"
export CXX="clang++-15"
Install Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Install additional dependencies:
apt install libssl-dev pkg-config
For libFuzzer compatibility mode, install nightly Rust:
rustup toolchain install nightly --component llvm-tools
Verification
Build LibAFL to verify installation:
cd LibAFL/libafl_libfuzzer_runtime
./build.sh
# Should produce libFuzzer.a
Writing a Harness
LibAFL harnesses follow the same pattern as libFuzzer when using drop-in replacement mode:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Your fuzzing target code here
return 0;
}
When building custom fuzzers with LibAFL as a Rust library, harness logic is integrated directly into the fuzzer. See the "Writing a Custom Fuzzer" section below for the full pattern.
See Also: For detailed harness writing techniques, see the harness-writing technique skill.
Usage Modes
LibAFL supports two primary usage modes:
1. libFuzzer Drop-in Replacement
Use LibAFL as a replacement for libFuzzer with existing harnesses.
Compilation:
clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
Running:
./fuzz corpus/
Recommended for long campaigns:
./fuzz -fork=1 -ignore_crashes=1 corpus/
2. Custom Fuzzer as Rust Library
Build a fully customized fuzzer using LibAFL components.
Create project:
cargo init --lib my_fuzzer
cd my_fuzzer
cargo add libafl@0.13 libafl_targets@0.13 libafl_bolts@0.13 libafl_cc@0.13 \
--features "libafl_targets@0.13/libfuzzer,libafl_targets@0.13/sancov_pcguard_hitcounts"
Configure Cargo.toml:
[lib]
crate-type = ["staticlib"]
Writing a Custom Fuzzer
See Also: For detailed harness writing techniques, patterns for handling complex inputs, and advanced strategies, see the fuzz-harness-writing technique skill.
Fuzzer Components
A LibAFL fuzzer consists of modular components:
- Observers - Collect execution feedback (coverage, timing)
- Feedback - Determine if inputs are interesting
- Objective - Define fuzzing goals (crashes, timeouts)
- State - Maintain corpus and metadata
- Mutators - Generate new inputs
- Scheduler - Select which inputs to mutate
- Executor - Run the target with inputs
Basic Fuzzer Structure
use libafl::prelude::*;
use libafl_bolts::prelude::*;
use libafl_targets::{libfuzzer_test_one_input, std_edges_map_observer};
#[no_mangle]
pub extern "C" fn libafl_main() {
let mut run_client = |state: Option<_>, mut restarting_mgr, _core_id| {
// 1. Setup observers
let edges_observer = HitcountsMapObserver::new(
unsafe { std_edges_map_observer("edges") }
).track_indices();
let time_observer = TimeObserver::new("time");
// 2. Define feedback
let mut feedback = feedback_or!(
MaxMapFeedback::new(&edges_observer),
TimeFeedback::new(&time_observer)
);
// 3. Define objective
let mut objective = feedback_or_fast!(
CrashFeedback::new(),
TimeoutFeedback::new()
);
// 4. Create or restore state
let mut state = state.unwrap_or_else(|| {
StdState::new(
StdRand::new(),
InMemoryCorpus::new(),
OnDiskCorpus::new(&output_dir).unwrap(),
&mut feedback,
&mut objective,
).unwrap()
});
// 5. Setup mutator
let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
// 6. Setup scheduler
let scheduler = IndexesLenTimeMinimizerScheduler::new(
&edges_observer,
QueueScheduler::new()
);
// 7. Create fuzzer
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// 8. Define harness
let mut harness = |input: &BytesInput| {
let buf = input.target_bytes().as_slice();
libfuzzer_test_one_input(buf);
ExitKind::Ok
};
// 9. Setup executor
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
// 10. Load initial inputs
if state.must_load_initial_inputs() {
state.load_initial_inputs(
&mut fuzzer,
&mut executor,
&mut restarting_mgr,
&input_dir
)?;
}
// 11. Start fuzzing
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?;
Ok(())
};
// Launch fuzzer
Launcher::builder()
.run_client(&mut run_client)
.cores(&cores)
.build()
.launch()
.unwrap();
}
Compilation
Verbose Mode
Manually specify all instrumentation flags:
clang++-15 -DNO_MAIN -g -O2 \
-fsanitize-coverage=trace-pc-guard \
-fsanitize=address \
-Wl,--whole-archive target/release/libmy_fuzzer.a -Wl,--no-whole-archive \
main.cc harness.cc -o fuzz
Compiler Wrapper (Recommended)
Create a LibAFL compiler wrapper to handle instrumentation automatically.
Create src/bin/libafl_cc.rs:
use libafl_cc::{ClangWrapper, CompilerWrapper, Configuration, ToolWrapper};
pub fn main() {
let args: Vec<String> = env::args().collect();
let mut cc = ClangWrapper::new();
cc.cpp(is_cpp)
.parse_args(&args)
.link_staticlib(&dir, "my_fuzzer")
.add_args(&Configuration::GenerateCoverageMap.to_flags().unwrap())
.add_args(&Configuration::AddressSanitizer.to_flags().unwrap())
.run()
.unwrap();
}
Compile and use:
cargo build --release
target/release/libafl_cxx -DNO_MAIN -g -O2 main.cc harness.cc -o fuzz
See Also: For detailed sanitizer configuration, common issues, and advanced flags, see the address-sanitizer and undefined-behavior-sanitizer technique skills.
Running Campaigns
Basic Run
./fuzz --cores 0 --input corpus/
Multi-Core Fuzzing
./fuzz --cores 0,8-15 --input corpus/
This runs 9 clients: one on core 0, and 8 on cores 8-15.
With Options
./fuzz --cores 0-7 --input corpus/ --output crashes/ --timeout 1000
Text User Interface (TUI)
Enable graphical statistics view:
./fuzz -tui=1 corpus/
Interpreting Output
| Output | Meaning |
|---|---|
corpus: N |
Number of interesting test cases found |
objectives: N |
Number of crashes/timeouts found |
executions: N |
Total number of target invocations |
exec/sec: N |
Current execution throughput |
edges: X% |
Code coverage percentage |
clients: N |
Number of parallel fuzzing processes |
The fuzzer emits two main event types:
- UserStats - Regular heartbeat with current statistics
- Testcase - New interesting input discovered
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
Use -fork=1 -ignore_crashes=1 |
Continue fuzzing after first crash |
Use InMemoryOnDiskCorpus |
Persist corpus across restarts |
Enable TUI with -tui=1 |
Better visualization of progress |
| Use specific LLVM version | Avoid compatibility issues |
Set RUSTFLAGS correctly |
Prevent linking errors |
Crash Deduplication
Avoid storing duplicate crashes from the same bug:
Add backtrace observer:
let backtrace_observer = BacktraceObserver::owned(
"BacktraceObserver",
libafl::observers::HarnessType::InProcess
);
Update executor:
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer, backtrace_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
Update objective with hash feedback:
let mut objective = feedback_and!(
feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()),
NewHashFeedback::new(&backtrace_observer)
);
This ensures only crashes with unique backtraces are saved.
Dictionary Fuzzing
Use dictionaries to guide fuzzing toward specific tokens:
Add tokens from file:
let mut tokens = Tokens::new();
if let Some(tokenfile) = &tokenfile {
tokens.add_from_file(tokenfile)?;
}
state.add_metadata(tokens);
Update mutator:
let mutator = StdScheduledMutator::new(
havoc_mutations().merge(tokens_mutations())
);
Hard-coded tokens example (PNG):
state.add_metadata(Tokens::from([
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
"IHDR".as_bytes().to_vec(),
"IDAT".as_bytes().to_vec(),
"PLTE".as_bytes().to_vec(),
"IEND".as_bytes().to_vec(),
]));
See Also: For detailed dictionary creation strategies and format-specific dictionaries, see the fuzzing-dictionaries technique skill.
Auto Tokens
Automatically extract magic values and checksums from the program:
Enable in compiler wrapper:
cc.add_pass(LLVMPasses::AutoTokens)
Load auto tokens in fuzzer:
tokens += libafl_targets::autotokens()?;
Verify tokens section:
echo "p (uint8_t *)__token_start" | gdb fuzz
Performance Tuning
| Setting | Impact |
|---|---|
| Multi-core fuzzing | Linear speedup with cores |
InMemoryCorpus |
Faster but non-persistent |
InMemoryOnDiskCorpus |
Balanced speed and persistence |
| Sanitizers | 2-5x slowdown, essential for bugs |
Optimization level -O2 |
Balance between speed and coverage |
Debugging Fuzzer
Run fuzzer in single-process mode for easier debugging:
// Replace launcher with direct call
run_client(None, SimpleEventManager::new(monitor), 0).unwrap();
// Comment out:
// Launcher::builder()
// .run_client(&mut run_client)
// ...
// .launch()
Then debug with GDB:
gdb --args ./fuzz --cores 0 --input corpus/
Real-World Examples
Example: libpng
Fuzzing libpng using LibAFL:
1. Get source code:
curl -L -O https://downloads.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar xf libpng-1.6.37.tar.xz
cd libpng-1.6.37/
apt install zlib1g-dev
2. Set compiler wrapper:
export FUZZER_CARGO_DIR="/path/to/libafl/project"
export CC=$FUZZER_CARGO_DIR/target/release/libafl_cc
export CXX=$FUZZER_CARGO_DIR/target/release/libafl_cxx
3. Build static library:
./configure --enable-shared=no
make
4. Get harness:
curl -O https://raw.githubusercontent.com/glennrp/libpng/f8e5fa92b0e37ab597616f554bee254157998227/contrib/oss-fuzz/libpng_read_fuzzer.cc
5. Link fuzzer:
$CXX libpng_read_fuzzer.cc .libs/libpng16.a -lz -o fuzz
6. Prepare seeds:
mkdir seeds/
curl -o seeds/input.png https://raw.githubusercontent.com/glennrp/libpng/acfd50ae0ba3198ad734e5d4dec2b05341e50924/contrib/pngsuite/iftp1n3p08.png
7. Get dictionary (optional):
curl -O https://raw.githubusercontent.com/glennrp/libpng/2fff013a6935967960a5ae626fc21432807933dd/contrib/oss-fuzz/png.dict
8. Start fuzzing:
./fuzz --input seeds/ --cores 0 -x png.dict
Example: CMake Project
Integrate LibAFL with CMake build system:
CMakeLists.txt:
project(BuggyProgram)
cmake_minimum_required(VERSION 3.0)
add_executable(buggy_program main.cc)
add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -g -O2)
Build non-instrumented binary:
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build . --target buggy_program
Build fuzzer:
export FUZZER_CARGO_DIR="/path/to/libafl/project"
cmake -DCMAKE_C_COMPILER=$FUZZER_CARGO_DIR/target/release/libafl_cc \
-DCMAKE_CXX_COMPILER=$FUZZER_CARGO_DIR/target/release/libafl_cxx .
cmake --build . --target fuzz
Run fuzzing:
./fuzz --input seeds/ --cores 0
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| No coverage increases | Instrumentation failed | Verify compiler wrapper used, check for -fsanitize-coverage |
| Fuzzer won't start | Empty corpus with no interesting inputs | Provide seed inputs that trigger code paths |
Linker errors with libafl_main |
Runtime not linked | Use -Wl,--whole-archive or -u libafl_main |
| LLVM version mismatch | LibAFL requires LLVM 15-18 | Install compatible LLVM version, set environment variables |
| Rust compilation fails | Outdated Rust or Cargo | Update Rust with rustup update |
| Slow fuzzing | Sanitizers enabled | Expected 2-5x slowdown, necessary for finding bugs |
| Environment variable interference | CC, CXX, RUSTFLAGS set |
Unset after building LibAFL project |
| Cannot attach debugger | Multi-process fuzzing | Run in single-process mode (see Debugging section) |
Related Skills
Technique Skills
| Skill | Use Case |
|---|---|
| fuzz-harness-writing | Detailed guidance on writing effective harnesses |
| address-sanitizer | Memory error detection during fuzzing |
| undefined-behavior-sanitizer | Undefined behavior detection |
| coverage-analysis | Measuring and improving code coverage |
| fuzzing-corpus | Building and managing seed corpora |
| fuzzing-dictionaries | Creating dictionaries for format-aware fuzzing |
Related Fuzzers
| Skill | When to Consider |
|---|---|
| libfuzzer | Simpler setup, don't need LibAFL's advanced features |
| aflpp | Multi-core fuzzing without custom fuzzer development |
| cargo-fuzz | Fuzzing Rust projects with less setup |
Resources
Official Documentation
- LibAFL Book - Official handbook with comprehensive documentation
- LibAFL GitHub - Source code and examples
- LibAFL API Documentation - Rust API reference
Examples and Tutorials
- LibAFL Examples - Collection of example fuzzers
- cargo-fuzz with LibAFL - Using LibAFL as cargo-fuzz backend
- Testing Handbook LibAFL Examples - Complete working examples from this handbook
Files (skills)
-
agents
-
openai.yaml 228 B
interface: display_name: "LibAFL Fuzzing" short_description: "Build custom fuzzers with LibAFL components" icon_small: "assets/trail-of-bits-mark.svg" icon_large: "assets/trail-of-bits-mark.svg" brand_color: "#D83A34"
-
-
assets
-
trail-of-bits-mark.svg 3 KB · in bundle
-
-
SKILL.md 16.7 KB
--- name: libafl type: fuzzer description: "Builds custom fuzzers with LibAFL, the modular Rust fuzzing library. Covers composing observers, feedbacks, mutators, schedulers, and executors into a fuzzer for targets the standard tools do not fit. Use when writing a bespoke fuzzer or mutator, fuzzing a non-standard target or architecture, implementing a fuzzing research idea, or when libFuzzer and AFL++ lack the control you need." --- # LibAFL LibAFL is a modular fuzzing library that implements features from AFL-based fuzzers like AFL++. Unlike traditional fuzzers, LibAFL provides all functionality in a modular and customizable way as a Rust library. It can be used as a drop-in replacement for libFuzzer or as a library to build custom fuzzers from scratch. ## When to Use | Fuzzer | Best For | Complexity | |--------|----------|------------| | libFuzzer | Quick setup, single-threaded | Low | | AFL++ | Multi-core, general purpose | Medium | | LibAFL | Custom fuzzers, advanced features, research | High | **Choose LibAFL when:** - You need custom mutation strategies or feedback mechanisms - Standard fuzzers don't support your target architecture - You want to implement novel fuzzing techniques - You need fine-grained control over fuzzing components - You're conducting fuzzing research ## Quick Start LibAFL can be used as a drop-in replacement for libFuzzer with minimal setup: ```c++ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { // Call your code with fuzzer-provided data my_function(data, size); return 0; } ``` Build LibAFL's libFuzzer compatibility layer: ```bash git clone https://github.com/AFLplusplus/LibAFL cd LibAFL/libafl_libfuzzer_runtime ./build.sh ``` Compile and run: ```bash clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz ./fuzz corpus/ ``` ## Installation ### Prerequisites - Clang/LLVM 15-18 - Rust (via rustup) - Additional system dependencies ### Linux/macOS Install Clang: ```bash apt install clang ``` Or install a specific version via apt.llvm.org: ```bash wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh sudo ./llvm.sh 15 ``` Configure environment for Rust: ```bash export RUSTFLAGS="-C linker=/usr/bin/clang-15" export CC="clang-15" export CXX="clang++-15" ``` Install Rust: ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` Install additional dependencies: ```bash apt install libssl-dev pkg-config ``` For libFuzzer compatibility mode, install nightly Rust: ```bash rustup toolchain install nightly --component llvm-tools ``` ### Verification Build LibAFL to verify installation: ```bash cd LibAFL/libafl_libfuzzer_runtime ./build.sh # Should produce libFuzzer.a ``` ## Writing a Harness LibAFL harnesses follow the same pattern as libFuzzer when using drop-in replacement mode: ```c++ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { // Your fuzzing target code here return 0; } ``` When building custom fuzzers with LibAFL as a Rust library, harness logic is integrated directly into the fuzzer. See the "Writing a Custom Fuzzer" section below for the full pattern. > **See Also:** For detailed harness writing techniques, see the **harness-writing** technique skill. ## Usage Modes LibAFL supports two primary usage modes: ### 1. libFuzzer Drop-in Replacement Use LibAFL as a replacement for libFuzzer with existing harnesses. **Compilation:** ```bash clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz ``` **Running:** ```bash ./fuzz corpus/ ``` **Recommended for long campaigns:** ```bash ./fuzz -fork=1 -ignore_crashes=1 corpus/ ``` ### 2. Custom Fuzzer as Rust Library Build a fully customized fuzzer using LibAFL components. **Create project:** ```bash cargo init --lib my_fuzzer cd my_fuzzer cargo add libafl@0.13 libafl_targets@0.13 libafl_bolts@0.13 libafl_cc@0.13 \ --features "libafl_targets@0.13/libfuzzer,libafl_targets@0.13/sancov_pcguard_hitcounts" ``` **Configure Cargo.toml:** ```toml [lib] crate-type = ["staticlib"] ``` ## Writing a Custom Fuzzer > **See Also:** For detailed harness writing techniques, patterns for handling complex inputs, > and advanced strategies, see the **fuzz-harness-writing** technique skill. ### Fuzzer Components A LibAFL fuzzer consists of modular components: 1. **Observers** - Collect execution feedback (coverage, timing) 2. **Feedback** - Determine if inputs are interesting 3. **Objective** - Define fuzzing goals (crashes, timeouts) 4. **State** - Maintain corpus and metadata 5. **Mutators** - Generate new inputs 6. **Scheduler** - Select which inputs to mutate 7. **Executor** - Run the target with inputs ### Basic Fuzzer Structure ```rust use libafl::prelude::*; use libafl_bolts::prelude::*; use libafl_targets::{libfuzzer_test_one_input, std_edges_map_observer}; #[no_mangle] pub extern "C" fn libafl_main() { let mut run_client = |state: Option<_>, mut restarting_mgr, _core_id| { // 1. Setup observers let edges_observer = HitcountsMapObserver::new( unsafe { std_edges_map_observer("edges") } ).track_indices(); let time_observer = TimeObserver::new("time"); // 2. Define feedback let mut feedback = feedback_or!( MaxMapFeedback::new(&edges_observer), TimeFeedback::new(&time_observer) ); // 3. Define objective let mut objective = feedback_or_fast!( CrashFeedback::new(), TimeoutFeedback::new() ); // 4. Create or restore state let mut state = state.unwrap_or_else(|| { StdState::new( StdRand::new(), InMemoryCorpus::new(), OnDiskCorpus::new(&output_dir).unwrap(), &mut feedback, &mut objective, ).unwrap() }); // 5. Setup mutator let mutator = StdScheduledMutator::new(havoc_mutations()); let mut stages = tuple_list!(StdMutationalStage::new(mutator)); // 6. Setup scheduler let scheduler = IndexesLenTimeMinimizerScheduler::new( &edges_observer, QueueScheduler::new() ); // 7. Create fuzzer let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); // 8. Define harness let mut harness = |input: &BytesInput| { let buf = input.target_bytes().as_slice(); libfuzzer_test_one_input(buf); ExitKind::Ok }; // 9. Setup executor let mut executor = InProcessExecutor::with_timeout( &mut harness, tuple_list!(edges_observer, time_observer), &mut fuzzer, &mut state, &mut restarting_mgr, timeout, )?; // 10. Load initial inputs if state.must_load_initial_inputs() { state.load_initial_inputs( &mut fuzzer, &mut executor, &mut restarting_mgr, &input_dir )?; } // 11. Start fuzzing fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?; Ok(()) }; // Launch fuzzer Launcher::builder() .run_client(&mut run_client) .cores(&cores) .build() .launch() .unwrap(); } ``` ## Compilation ### Verbose Mode Manually specify all instrumentation flags: ```bash clang++-15 -DNO_MAIN -g -O2 \ -fsanitize-coverage=trace-pc-guard \ -fsanitize=address \ -Wl,--whole-archive target/release/libmy_fuzzer.a -Wl,--no-whole-archive \ main.cc harness.cc -o fuzz ``` ### Compiler Wrapper (Recommended) Create a LibAFL compiler wrapper to handle instrumentation automatically. **Create `src/bin/libafl_cc.rs`:** ```rust use libafl_cc::{ClangWrapper, CompilerWrapper, Configuration, ToolWrapper}; pub fn main() { let args: Vec<String> = env::args().collect(); let mut cc = ClangWrapper::new(); cc.cpp(is_cpp) .parse_args(&args) .link_staticlib(&dir, "my_fuzzer") .add_args(&Configuration::GenerateCoverageMap.to_flags().unwrap()) .add_args(&Configuration::AddressSanitizer.to_flags().unwrap()) .run() .unwrap(); } ``` **Compile and use:** ```bash cargo build --release target/release/libafl_cxx -DNO_MAIN -g -O2 main.cc harness.cc -o fuzz ``` > **See Also:** For detailed sanitizer configuration, common issues, and advanced flags, > see the **address-sanitizer** and **undefined-behavior-sanitizer** technique skills. ## Running Campaigns ### Basic Run ```bash ./fuzz --cores 0 --input corpus/ ``` ### Multi-Core Fuzzing ```bash ./fuzz --cores 0,8-15 --input corpus/ ``` This runs 9 clients: one on core 0, and 8 on cores 8-15. ### With Options ```bash ./fuzz --cores 0-7 --input corpus/ --output crashes/ --timeout 1000 ``` ### Text User Interface (TUI) Enable graphical statistics view: ```bash ./fuzz -tui=1 corpus/ ``` ### Interpreting Output | Output | Meaning | |--------|---------| | `corpus: N` | Number of interesting test cases found | | `objectives: N` | Number of crashes/timeouts found | | `executions: N` | Total number of target invocations | | `exec/sec: N` | Current execution throughput | | `edges: X%` | Code coverage percentage | | `clients: N` | Number of parallel fuzzing processes | The fuzzer emits two main event types: - **UserStats** - Regular heartbeat with current statistics - **Testcase** - New interesting input discovered ## Advanced Usage ### Tips and Tricks | Tip | Why It Helps | |-----|--------------| | Use `-fork=1 -ignore_crashes=1` | Continue fuzzing after first crash | | Use `InMemoryOnDiskCorpus` | Persist corpus across restarts | | Enable TUI with `-tui=1` | Better visualization of progress | | Use specific LLVM version | Avoid compatibility issues | | Set `RUSTFLAGS` correctly | Prevent linking errors | ### Crash Deduplication Avoid storing duplicate crashes from the same bug: **Add backtrace observer:** ```rust let backtrace_observer = BacktraceObserver::owned( "BacktraceObserver", libafl::observers::HarnessType::InProcess ); ``` **Update executor:** ```rust let mut executor = InProcessExecutor::with_timeout( &mut harness, tuple_list!(edges_observer, time_observer, backtrace_observer), &mut fuzzer, &mut state, &mut restarting_mgr, timeout, )?; ``` **Update objective with hash feedback:** ```rust let mut objective = feedback_and!( feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()), NewHashFeedback::new(&backtrace_observer) ); ``` This ensures only crashes with unique backtraces are saved. ### Dictionary Fuzzing Use dictionaries to guide fuzzing toward specific tokens: **Add tokens from file:** ```rust let mut tokens = Tokens::new(); if let Some(tokenfile) = &tokenfile { tokens.add_from_file(tokenfile)?; } state.add_metadata(tokens); ``` **Update mutator:** ```rust let mutator = StdScheduledMutator::new( havoc_mutations().merge(tokens_mutations()) ); ``` **Hard-coded tokens example (PNG):** ```rust state.add_metadata(Tokens::from([ vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header "IHDR".as_bytes().to_vec(), "IDAT".as_bytes().to_vec(), "PLTE".as_bytes().to_vec(), "IEND".as_bytes().to_vec(), ])); ``` > **See Also:** For detailed dictionary creation strategies and format-specific dictionaries, > see the **fuzzing-dictionaries** technique skill. ### Auto Tokens Automatically extract magic values and checksums from the program: **Enable in compiler wrapper:** ```rust cc.add_pass(LLVMPasses::AutoTokens) ``` **Load auto tokens in fuzzer:** ```rust tokens += libafl_targets::autotokens()?; ``` **Verify tokens section:** ```bash echo "p (uint8_t *)__token_start" | gdb fuzz ``` ### Performance Tuning | Setting | Impact | |---------|--------| | Multi-core fuzzing | Linear speedup with cores | | `InMemoryCorpus` | Faster but non-persistent | | `InMemoryOnDiskCorpus` | Balanced speed and persistence | | Sanitizers | 2-5x slowdown, essential for bugs | | Optimization level `-O2` | Balance between speed and coverage | ### Debugging Fuzzer Run fuzzer in single-process mode for easier debugging: ```rust // Replace launcher with direct call run_client(None, SimpleEventManager::new(monitor), 0).unwrap(); // Comment out: // Launcher::builder() // .run_client(&mut run_client) // ... // .launch() ``` Then debug with GDB: ```bash gdb --args ./fuzz --cores 0 --input corpus/ ``` ## Real-World Examples ### Example: libpng Fuzzing libpng using LibAFL: **1. Get source code:** ```bash curl -L -O https://downloads.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz tar xf libpng-1.6.37.tar.xz cd libpng-1.6.37/ apt install zlib1g-dev ``` **2. Set compiler wrapper:** ```bash export FUZZER_CARGO_DIR="/path/to/libafl/project" export CC=$FUZZER_CARGO_DIR/target/release/libafl_cc export CXX=$FUZZER_CARGO_DIR/target/release/libafl_cxx ``` **3. Build static library:** ```bash ./configure --enable-shared=no make ``` **4. Get harness:** ```bash curl -O https://raw.githubusercontent.com/glennrp/libpng/f8e5fa92b0e37ab597616f554bee254157998227/contrib/oss-fuzz/libpng_read_fuzzer.cc ``` **5. Link fuzzer:** ```bash $CXX libpng_read_fuzzer.cc .libs/libpng16.a -lz -o fuzz ``` **6. Prepare seeds:** ```bash mkdir seeds/ curl -o seeds/input.png https://raw.githubusercontent.com/glennrp/libpng/acfd50ae0ba3198ad734e5d4dec2b05341e50924/contrib/pngsuite/iftp1n3p08.png ``` **7. Get dictionary (optional):** ```bash curl -O https://raw.githubusercontent.com/glennrp/libpng/2fff013a6935967960a5ae626fc21432807933dd/contrib/oss-fuzz/png.dict ``` **8. Start fuzzing:** ```bash ./fuzz --input seeds/ --cores 0 -x png.dict ``` ### Example: CMake Project Integrate LibAFL with CMake build system: **CMakeLists.txt:** ```cmake project(BuggyProgram) cmake_minimum_required(VERSION 3.0) add_executable(buggy_program main.cc) add_executable(fuzz main.cc harness.cc) target_compile_definitions(fuzz PRIVATE NO_MAIN=1) target_compile_options(fuzz PRIVATE -g -O2) ``` **Build non-instrumented binary:** ```bash cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ . cmake --build . --target buggy_program ``` **Build fuzzer:** ```bash export FUZZER_CARGO_DIR="/path/to/libafl/project" cmake -DCMAKE_C_COMPILER=$FUZZER_CARGO_DIR/target/release/libafl_cc \ -DCMAKE_CXX_COMPILER=$FUZZER_CARGO_DIR/target/release/libafl_cxx . cmake --build . --target fuzz ``` **Run fuzzing:** ```bash ./fuzz --input seeds/ --cores 0 ``` ## Troubleshooting | Problem | Cause | Solution | |---------|-------|----------| | No coverage increases | Instrumentation failed | Verify compiler wrapper used, check for `-fsanitize-coverage` | | Fuzzer won't start | Empty corpus with no interesting inputs | Provide seed inputs that trigger code paths | | Linker errors with `libafl_main` | Runtime not linked | Use `-Wl,--whole-archive` or `-u libafl_main` | | LLVM version mismatch | LibAFL requires LLVM 15-18 | Install compatible LLVM version, set environment variables | | Rust compilation fails | Outdated Rust or Cargo | Update Rust with `rustup update` | | Slow fuzzing | Sanitizers enabled | Expected 2-5x slowdown, necessary for finding bugs | | Environment variable interference | `CC`, `CXX`, `RUSTFLAGS` set | Unset after building LibAFL project | | Cannot attach debugger | Multi-process fuzzing | Run in single-process mode (see Debugging section) | ## Related Skills ### Technique Skills | Skill | Use Case | |-------|----------| | **fuzz-harness-writing** | Detailed guidance on writing effective harnesses | | **address-sanitizer** | Memory error detection during fuzzing | | **undefined-behavior-sanitizer** | Undefined behavior detection | | **coverage-analysis** | Measuring and improving code coverage | | **fuzzing-corpus** | Building and managing seed corpora | | **fuzzing-dictionaries** | Creating dictionaries for format-aware fuzzing | ### Related Fuzzers | Skill | When to Consider | |-------|------------------| | **libfuzzer** | Simpler setup, don't need LibAFL's advanced features | | **aflpp** | Multi-core fuzzing without custom fuzzer development | | **cargo-fuzz** | Fuzzing Rust projects with less setup | ## Resources ### Official Documentation - [LibAFL Book](https://aflplus.plus/libafl-book/) - Official handbook with comprehensive documentation - [LibAFL GitHub](https://github.com/AFLplusplus/LibAFL) - Source code and examples - [LibAFL API Documentation](https://docs.rs/libafl/latest/libafl/) - Rust API reference ### Examples and Tutorials - [LibAFL Examples](https://github.com/AFLplusplus/LibAFL/tree/main/fuzzers) - Collection of example fuzzers - [cargo-fuzz with LibAFL](https://github.com/AFLplusplus/LibAFL/tree/main/fuzzers/fuzz_anything/cargo_fuzz) - Using LibAFL as cargo-fuzz backend - [Testing Handbook LibAFL Examples](https://github.com/trailofbits/testing-handbook/tree/main/materials/fuzzing/libafl) - Complete working examples from this handbook
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.