Tips for more effective fuzz testing with AFL++
Fuzz testing, particularly with AFL++, is an incredibly effective yet often underutilized method for uncovering software defects, with the author claiming "everything is broken" and bugs are everywhere. This article provides eight practical, deep-dive tips for C/C++ developers to significantly enhance the effectiveness of their fuzzing efforts. It offers actionable advice to catch even subtle bugs missed by standard approaches, making it a must-read for anyone serious about software security and stability.
The Lowdown
The author, having fuzzed roughly 1,000 projects over six years, asserts that fuzz testing is a powerful, yet neglected, tool for finding software defects. Using AFL++ on source-available C/C++ targets on glibc-based Linux, the author presents a checklist of eight expert tips to improve fuzzing efficacy, complementing official documentation. These techniques can reveal bugs even in programs previously fuzzed.
- Configure Sanitizers and Assertions: Employ Address Sanitizer (ASan) and Undefined Behavior Sanitizer (UBSan) with specific
ASAN_OPTIONSlikedetect_stack_use_after_return=1andmax_malloc_fill_size=$((1<<30))to increase sensitivity to defects beyond default settings. For C++, consider-D_GLIBCXX_DEBUG. - Prefer Persistent Mode: Utilize AFL++'s persistent mode (
afl-gcc-fast,afl-clang-fast) for up to an order of magnitude speed increase, using a custom template (fuzztmpl.c) for efficiency. - Fuzz Any Function: Include target source files directly in the fuzzer's translation unit (TU) to simplify build configurations, bypass public interfaces, and test arbitrary internal or static functions, even allowing hacks to remove
mainfunctions or substitute symbols. - Isolate Fuzz Tests: Ensure tests are independent by resetting all global state between runs, including
strtokbuffers, and filling uninitialized heap memory with patterns to prevent interference and aid reproducibility. - Do Not Test Directly on the Fuzz Test Buffer: Avoid passing
__AFL_FUZZ_TESTCASE_BUFdirectly to the target; instead, copy it into a sized buffer to properly detect read overflows with ASan and handle null termination. - Don't Bother Freeing Memory: Generally, skip explicit memory freeing in fuzz tests as AFL++ resets the target process, aiding isolation and reducing unnecessary work. This applies unless the target is memory-hungry or non-memory resources (like file descriptors) are exhausted.
- Use a Memory File Descriptor to Back Named Paths: For interfaces requiring named file paths, use
memfd_createand its/procpath (e.g.,/proc/self/fd/3) to provide input. This enables parallel fuzzing, prevents disk wear, and avoids cleanup, or usefdmemopenforFILE *interfaces. - Configure the Target for Smaller Buffers: Reduce