The Role of Fuzz Testing in Software Security Part 1
PUBLISHED:
April 23, 2025
|
BY:
Debarshi Das
Ideal for
Pentester
Developer
Picture this: You’ve written a C library to parse complex file formats. You’ve meticulously reviewed the code, run it through static analyzers like Coverity and PVS Studio, and fixed every warning.
Confident in its robustness, you deploy it. Months later, a malformed input triggers a heap overflow, leading to a critical remote code execution vulnerability.
This scenario is not hypothetical. It’s a recurring theme in software built with memory-unsafe languages like C/C++.
Static Application Security Testing (SAST) tools are indispensable, but they operate in a vacuum, analyzing code without executing it. To uncover vulnerabilities that lurk in runtime behavior, you need a dynamic approach: fuzzing.
Table of Contents
Why Static Analysis isn’t Enough
What is Fuzzing?
Evidence that Fuzzing Works
How To Start Fuzzing C/C++ Codebases
Embracing the Chaos
Why Static Analysis isn’t Enough
SAST tools are great! They’re like spellcheck for code. They’ll tell you that scanf() might be unsafe, or that your malloc() is missing a free().
But here’s the problem: they can’t see runtime behavior. Besides this they suffer from two burdening issues:
Flood of false positives: SAST tools are infamous for flooding vulnerability management systems with false positives of unsafe functions or, blatantly ignoring safety checks.
False negatives in complex logic: Bugs often arise from interactions between functions, modules, or even threads scenarios which SAST struggles to model. For example, it might flag a safe strcpy(dest, src) as risky but may not always determine if strncpy(dest, src, len) is supplied arbitrary length.
Take FreeRadius, the most widely used RADIUS server in the world powering major Internet Service Providers and telecommunications companies. For years, they relied on secure coding standards and industry-standard SAST tools like Coverity, PVS Studio, cpp-check, and others.
Then, in 2017, they got fuzzed. The result? A parade of vulnerabilities they never knew existed like buffer overflows triggered by malformed packets. SAST had missed them because the bugs only surfaced when the code was executed with specific inputs.
SAST is like checking if your car has seatbelts. Fuzzing is like crashing it into a wall to see if the airbags work.
What is Fuzzing?
Fuzzing or fuzz testing is an automated dynamic analysis technique designed to discover bugs by injecting invalid, unexpected, or random inputs into a program.
Unlike SAST which just inspects source code, fuzzing tracks how the software behaves during execution.
Modern fuzzers operate in three key phases:
Input Generation:
Mutation-Based Fuzzers (e.g., AFL, libFuzzer): Start with valid seed inputs (e.g., a PNG file) and mutate them using strategies like bit-flipping, arithmetic increments, or block splicing.
Generational Fuzzers (e.g., Peach Fuzzer): Use models of the input format (e.g., protocol specifications) to generate structured test cases.
Execution and Monitoring: The target program processes each input while the fuzzer monitors for crashes, memory leaks, or undefined behavior using sanitizers (e.g., ASan, UBSan).
Feedback-Driven Optimization: Coverage-guided fuzzers (e.g., AFL++) map code coverage to prioritize inputs that explore new execution paths, creating a feedback loop to maximize the attack surface.
Fuzzing is not random. It’s a systematic exploration of your program’s attack surface, which uncovers edge cases that human auditors and SAST tools overlook.
“But My Code is Perfect!” – John, A Developer From a Major Company Currently Experiencing a Zero-Day Exploit
While John is being promoted from developer to customer by his manager, let’s crush denialism with some statistics and evidence:
OpenSSL: While Heartbleed remains infamous, OpenSSL continues to rely on fuzzing to prevent regressions.
In 2022, OSS-Fuzz discovered CVE-2022-3602 and CVE-2022-3786, two critical buffer overflow vulnerabilities in OpenSSL’s punycode parsing logic. These flaws could have enabled remote code execution but were caught before they reached production, thanks to continuous fuzzing.
Microsoft’s Windows:
In 2021, fuzzing uncovered a critical remote code execution (RCE) bug in the Windows TCP/IP driver (CVE-2021-24086), which could have allowed attackers to take over unpatched systems via malicious network packets.
In 2023, Azure’s Kubernetes ingress-nginx component fixed a memory corruption flaw (CVE-2023-39418) found through libFuzzer.
Curl: The ubiquitous Curl, used by billions of devices, has integrated fuzzing via OSS-Fuzz since 2017.
CVE-2021-22901: A use-after-free vulnerability in Curl’s TLS handshake logic, found by OSS-Fuzz. Exploitable for RCE.
CVE-2023-38545: A heap-based buffer overflow in Curl’s SOCKS5 proxy handling, discovered by a custom fuzzing harness.
Google’s OSS-Fuzz: Since its launch in 2016, OSS-Fuzz (Google’s free fuzzing service for open-source projects) has:
Run and Iterate: Let the fuzzer run for hours or days. Triage the crashes, fix the bugs, and expand the seed corpus.
This is an overview of a standard fuzzing campaign. It’s not always this straightforward. The real challenge in a fuzzing campaign is writing the harness and ensuring best coverage.
In the next series of posts, we’ll explore the internals of a fuzzer and practise fuzzing hands-on by writing some harnesses for prominent and complex open-source software.
Embrace the Chaos
Fuzzing isn’t a silver bullet or a replacement for SAST or code reviews. It’s a complementary force multiplier.
By dynamically probing your code’s execution paths, it exposes vulnerabilities that static analysis cannot see. For projects in memory-unsafe languages like C/C++, it’s a critical line of defense against exploits that could lead to breaches, data loss, or regulatory penalties.
In Part 2, we’ll explore the internals of a fuzzer and some history of fuzzing. In part 3 we’ll get hands-on with advanced techniques, writing effective harnesses for stateful systems, integrating fuzzing into CI/CD, and interpreting crash reports to diagnose root causes.
Remember that fuzzing is your strongest weapon in the fight against memory corruption, but it's not a universal solution. Security begins with developer education and a commitment to secure-by-design principles, and AppSecEngineer is your best-friend in this endeavour.
Hey, I’m Debarshi—vulnerability researcher, reverse engineer, and part-time digital detective. My life’s mission? Breaking things (ethically, of course) and figuring out how they tick. If there’s a sneaky bug hiding in your code, I’ll hunt it down like a cybersecurity bloodhound.