This repository provides the source code and evaluation scripts used in the "Towards Path-aware Coverage-guided Fuzzing" paper, to appear in Proceedings of CGO 2026 (you can find a preprint copy of the paper in the paper/ directory).
The purpose of this work is to improve coverage-guided fuzzing by introducing a path-aware instrumentation: an intra-procedural execution-path-based feedback mechanism which increases sensitivity to distinct program behaviors within functions.
If you are referencing our work in your research, please consider using the following BibTeX entry:
@INPROCEEDINGS{Priamo-CGO26,
author={Priamo, Giacomo and D’Elia, Daniele Cono and Payer, Mathias and Querzoni, Leonardo},
title={Towards Path-Aware Coverage-Guided Fuzzing},
booktitle = {Proceedings of the 2026 IEEE/ACM International Symposium on Code Generation and Optimization},
publisher = {IEEE Press},
series = {CGO '26},
location = {Sydney, Australia},
year={2026}
}This project was developed around the following toolchain and platform versions (recommended for reproduction):
- AFL++ 4.07a (included in this artifact)
- LLVM 12.0.1
- Ubuntu 20.04 (experiment platform)
- Rust 1.78.0 (required by AFLTriage)
- Python 3.8 (used by AFL++ and AFLTriage)
Other versions may work but could require adjustments to build scripts or environment variables.
The AFL++ components modified to incorporate our path-aware feedback include the following files:
- src/afl-cc.c - to insert the switch to toggle the use of our instrumentation
- src/afl-fuzz-queue.c - to dump the favored seeds from AFL++
- src/afl-fuzz-run.c - to implement a single-calibration cycle (used by the aggressive mode of the culling script)
- include/config.h - to set the default map size and define the single-calibration cycle
- include/envs.h - to define the new environment variables we introduced in the fuzzer
- GNUmakefile.llvm - to compile our analysis and transformation passes (see below) alongside AFL++
The implementation of our path-aware feedback is based on a Ball-Larus path numbering instrumentation for the LLVM IR. We adapted an existing implementation to instrument programs at the LLVM IR level and to integrate path counters into AFL++'s feedback loop.
The specific source files involved in the LLVM IR instrumentation for path-aware fuzzing are:
- instrumentation/PathNumbering.cpp and instrumentation/PathNumbering.h - implementing the path enumeration and numbering algorithms
- instrumentation/afl-llvm-BL-profiling-pass.so.cc - injecting the path profiling instrumentation into the binary and handling the fuzzer's queue management operations
The unifuzz/ directory contains the scripts and Docker images to reproduce the UniFuzz targets evaluated in the paper. Subject directories follow the layout unifuzz/<subject>/[path|pcguard].
path: scripts and Docker image for building and running a subject with the path-aware instrumentation provided by this repository (path,cullandoppfuzzers).pcguard: scripts and Docker image for building and running a subject with AFL++'spcguardinstrumentation (used for baseline comparisons).
Each path/pcguard sub-directory contains a Dockerfile to create a container instance for that specific <fuzzer,subject> pair.
You can run any of them by using the provided shell scripts or by executing equivalent commands inside the container. The primary contents of each benchmark sub-directory are:
-
seeds/- initial seeds to fuzz the subject -
triage_build/build.sh- creates a clean, uninstrumented build used for crash triaging with AFLTriage -
pc_build/build_pcguard.sh(path/only) - builds the target with thepcguardinstrumentation; used by the queue culling script -
build_image.sh- builds the Docker image for the subject -
run_docker.sh- launches the Docker image as a container instance -
build_bench.sh(path/only) - pulls the UniFuzz subject and builds it with the path-aware instrumentation; then invokespc_build/build_pcguard.sh -
build_pcguard.sh(pcguard/only) - builds the subject with thepcguardinstrumentation -
Dockerfile- container recipe for the benchmark -
fuzz_cmplog.sh- starts a standard AFL++ fuzzing run by invokingafl-fuzz -
fuzz_enhanced.sh(path/only) - helper used by thecullfuzzer (scripts/fuzz-cull.sh) for dry-runs and for resuming fuzzing after culling- With the
-Eargument, perform a dry-run over the queue to collect favored seeds used by the culling procedure. - Start the next fuzzing round once the culling round is complete.
- With the
-
start_session.shwraps most of the scripts described above
We provide a Docker image for an easy way to try out our system.
The base image contains the fundamental components of this system (LLVM 12, dependencies, AFL++, and AFLTriage).
docker build -t path-fuzzing -f Dockerfile .You can now directly run the image you've just built:
docker run -it path-fuzzing:latest /bin/bashand use it as shown in "Standalone usage" - step #3.
Alternatively, if you want to use our scripts, please follow these steps:
- Navigate into the desired
unifuzz/<subject>/[path/pcguard]directory - Build the Docker image using the
build_image.shscript - Create the container using the
run_docker.shscript - Start the building and testing session by invoking
./start_session.sh. This script will:- Build the target program using
build_bench.shorbuild_pcguard.sh. - Start the fuzzing campaign using
fuzz_cmplog.sh. You can optionally pass the-cullparameter tostart_session.shto fuzz the program using our queue culling technique (path-aware fuzzing only). This invokes the queue culling script (scripts/fuzz-cull.sh), which interleaves the fuzzing process with queue culling rounds and collects the results at the end of the run - Invoke the crash deduplication script (scripts/deduplicate_crashes.sh) to automatically derive the unique crashes* detected by the fuzzer (if present) using the AFLTriage tool
- Build the target program using
Here are the complete steps for running a minimal working example of our system:
- Build the main Docker image:
docker build -t path-fuzzing -f Dockerfile .- Navigate to the unifuzz/mp3gain/path directory:
cd unifuzz/mp3gain/path- Build the dedicated Docker image:
./build_image.sh- Run the related container:
./run_docker.sh- Configure runtime environment variables. For example:
export RUNTIME=10800 # 3 hours; default is 172800 (48 hours)Optionally, adjust FUZZING_WINDOW_ORIG to change the duration of culling rounds.
export FUZZING_WINDOW_ORIG=3600 # 1 hour; default is 21600 (6 hours)- (a) Start the session, invoking one among:
./start_session.sh # normal run./start_session.sh -cull # culling-enabled run- (b) We also provide a queue produced with the edge coverage feedback (edge_queue.7z). You can use it to try out our opportunistic strategy (
oppfuzzer):
7z x edge_queue.7z
rm -rf seeds
mv edge_queue seeds
./start_session.sh # opportunistic runAt the end of the session, the afl_out_0/ directory will contain the complete results of the fuzzing campaign. The results of the crash deduplication* process (if at least 1 crash was detected) will be located in the afl_out_0/u-crashes5 directory for a normal fuzzing run, while for a culling run the final results will be located in the afl_out_0/u-crashes5/uniques folder.
*Please beware that in our work we also manually analyzed the deduplicated crashes to derive unique bugs (i.e., we distinguished those with a different underlying root cause), as stack hash-based crash deduplication notoriously results in bug over-/under-counting.
These steps can be generalized to run any of the UniFuzz subjects.
To run the system locally on your own machine, please refer to the following instructions.
CC=clang CXX=clang++ LLVM_CONFIG=llvm-config make- Before building your target program, export the following environment variables:
export AFL_PATH="/path/to/afl-fuzz" # Set this to point to the root directory of this repository
export AFL_LLVM_INSTRUMENT="classic"
export AFL_PATH_PROFILING="1"export CC="${AFL_PATH}/afl-clang-fast"
export CXX="${AFL_PATH}/afl-clang-fast++"-
Now you can build the program you want to fuzz using its default building system (
make,cmake,automake, etc.)2.5. [Optional] As in our paper evaluation, you may also enable the cmplog instrumentation by setting the required environment variable (
export AFL_LLVM_CMPLOG=1), and then building the target program once again.
Start the fuzzing campaign as you would normally do with AFL++:
${AFL_PATH}/afl-fuzz -t 1000+ -i <seeds> -o <output> -m none [-c <cmplog_target>] -- ./<target_program> [<placeholder>]Ensure you set correct values for the seeds and output directories, and the placeholder argument for the target program (usually @@). This will run the baseline path-aware fuzzer.
Refer to the culling script (scripts/fuzz-cull.sh) for the culling workflow. The script includes a fuzz function that you may need to adjust for your target program (for example, to set the correct input/output placeholders or execution wrapper).
You can also enable the random culling strategy (that we introduce in Appendix D of our paper) by toggling the RANDOM_CULLING environment variable inside the script.
We provide a script to automate the installation and environment configuration process: setup.sh. The script is tasked with downloading the required dependencies, installing the fuzzer and the other tools we use in our evaluation (like AFLTriage), and configuring the required environment variables.
The script should be invoked with:
source setup.shUpon completion, your current shell will be configured to run our fuzzer either on the UniFuzz subjects from unifuzz/ (you can use the provided start_session.sh scripts), or on any other program compatible with AFL++ (please refer to the "Reusability" section).
Please beware that a local installation will (most likely) only work in an exactly identical enviroment as the one in which we developed our system (an x86-64 machine running Ubuntu version 20.04).
The entire system can also be extended and customized to test any chosen program with our path-aware instrumentation (and, optionally, our queue culling procedure), as long as it is compatible with the AFL++ fuzzer.
The first step to that end is creating the build script for the program to test. You can refer to those we provide to build the UniFuzz subjects (e.g. unifuzz/objdump/path/build_path.sh). The crucial details to take care of are:
- Enabling our path-aware instrumentation
export AFL_PATH_PROFILING="1"
export AFL_LLVM_INSTRUMENT="classic"- Setting AFL++ as the default compiler:
export AFL_PATH="/path/to/afl-fuzz/" # Set this to point to the root directory of this repository
export CC="${AFL_PATH}/afl-clang-fast"
export CXX="${AFL_PATH}/afl-clang-fast++"- Enabling the ASan sanitizer to uncover silent memory-related bugs:
export CFLAGS="${COMMON_OPTS} -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=address"
export CXXFLAGS="-stdlib=libc++ ${CFLAGS}"- Adding AFL++'s compiler runtime to the
LDFLAGS
export LDFLAGS="${AFL_PATH}afl-compiler-rt.o -Wl,--allow-multiple-definition"-
Building the target binary by using its default building system (
make,cmake,automake, etc.) -
[Optional] Building the
cmplogversion of the binary by setting the related environment variable
export AFL_LLVM_CMPLOG=1Now you can start the fuzzing campaign with AFL++ as shown in "Standalone usage" - step #3.