Fuzz testing is a powerful method for discovering software defects by providing a program with a massive volume of semi-randomized inputs. While AFL++ is a leading platform for C and C++ targets on Linux, achieving high efficiency requires deliberate configuration of sanitizers and environment variables. Using both Address Sanitizer (ASan) and Undefined Behavior Sanitizer (UBSan) is a baseline requirement, but the default ASan settings can be insufficient. For example, increasing the `max_malloc_fill_size` to a large value (such as 1GB) ensures that uninitialized memory is filled with a pattern, preventing the fuzzer from missing bugs where a program reads uninitialized data beyond a small 4KiB default buffer. Additionally, enabling `detect_stack_use_after_return` can catch more complex memory errors, even if it slightly reduces fuzzing speed.
Architectural choices significantly impact the speed and depth of a fuzzing campaign. One of the most effective optimizations is using AFL++'s persistent mode, which can be an order of magnitude faster than in-place fuzzing because it avoids the overhead of repeatedly restarting the target process. To implement this, developers can use a custom test template that wraps the target logic in a loop. A key technical nuance in this setup is avoiding the common mistake of passing the raw AFL++ buffer directly into the target. Because the AFL++ buffer is larger than the input length, ASan might fail to detect read overflows. Instead, the test template should create a size-fitted copy of the input, ensuring that the program's boundaries are strictly enforced and detectable by the sanitizer.
Beyond configuration, 'hacksaw' techniques can simplify the testing of complex, tightly-coupled components. Rather than managing complex build systems for every test, developers can include the target's source files directly into a single translation unit (TU) within the fuzzing template. This allows the fuzzer to access static functions and variables directly, enabling the testing of internal interfaces that are not exposed via public APIs. Furthermore, for programs that require file system paths, using memory file descriptors (via `memfd_create`) allows the fuzzer to provide data through the `/proc/self/fd/` path. This approach supports parallel fuzzing by ensuring each process has a unique file path, while also preventing unnecessary wear on physical storage. To maximize overall performance, it is also recommended to keep test cases small and use simpler target binaries to ensure the fuzzer spends more time exploring new execution paths rather than parsing large, non-essential data.",
