Fuzzing

On this page 7

Fuzzing is an automated security testing technique that feeds a program large numbers of unexpected, malformed or random inputs and monitors it for crashes, hangs, memory errors and assertion failures. Each failure points to a defect, and many of those defects, such as buffer overflows and use-after-free errors, are exploitable security vulnerabilities.

In cybersecurity and security testing, fuzzing is valued because it finds bugs that developers did not think to test for. Modern coverage-guided fuzzers learn from the program’s behavior as they run, so they can explore deep code paths in parsers, network protocols, file formats and system interfaces without any prior knowledge of the code’s logic.

How fuzzing finds vulnerabilities

A fuzzing campaign has four parts: an input generator, a harness that delivers each input to the target, a monitor that detects abnormal behavior and a triage step that groups and minimizes the crashing inputs. The loop repeats thousands to millions of times per second on fast in-process targets.

Plain crashes reveal only part of the problem. Memory corruption often goes unnoticed unless the target is built with sanitizers such as AddressSanitizer, which aborts the program on out-of-bounds access or use of freed memory. Fuzzing combined with sanitizers turns silent corruption into a clear, reproducible failure.

ComponentFunctionExample
Seed corpusValid sample inputs used as a starting pointSmall images, packets, documents
Mutator or generatorProduces new test inputsBit flips, splicing, grammar rules
HarnessFeeds inputs to the code under testA function that parses one buffer
InstrumentationReports code coverage back to the fuzzerCompiler-inserted edge counters
Sanitizers and monitorsDetect memory errors, leaks, undefined behaviorAddressSanitizer, UBSan, MemorySanitizer
Components of a fuzzing setup

Types of fuzzers

Fuzzers differ in how they create inputs and how much they know about the target. The choice depends on whether source code is available and how strictly the input format is structured.

ApproachHow inputs are createdBest suited forLimitation
Mutation-basedRandom changes to valid seedsBinary file formats, quick startsStruggles with checksums and strict syntax
Generation-basedBuilt from a grammar or specificationProtocols, languages, structured dataWriting the model takes effort
Coverage-guided greyboxMutations kept when they reach new codeLibraries and parsers with sourceRequires instrumentation
BlackboxNo feedback from inside the targetClosed devices and remote servicesShallow exploration
Whitebox or concolicSolves path constraints symbolicallyHard-to-reach branchesSlow and limited by path explosion
Main fuzzing approaches

Coverage-guided tools such as AFL++ and libFuzzer dominate practical work. Their key idea is simple: an input that reaches new code is saved and mutated further, so the corpus gradually evolves toward the program’s deeper logic.

Fuzzing compared with other security testing methods

Fuzzing is a form of dynamic testing, but unlike DAST it typically targets code units and parsers rather than complete web applications. It complements rather than replaces static analysis and manual testing.

MethodWhat it examinesTypical findings
FuzzingProgram behavior under unexpected inputMemory corruption, crashes, hangs
SASTSource code without executing itInsecure patterns, tainted data flows
DASTRunning web application from outsideInjection, misconfiguration, missing headers
Penetration testingWhole systems with human creativityChained and logic vulnerabilities
Bug bountyProduction systems by external researchersWide variety, uneven depth
Fuzzing and neighboring security testing methods

A practical strength of fuzzing is that every finding comes with a reproducing input, which makes false positives rare compared with static analysis. Its weakness is that it reveals only bugs with observable symptoms, so logic flaws and authorization errors usually stay hidden.

Running an effective fuzzing campaign

Results depend more on harness quality and corpus design than on raw computing time. A harness should exercise one parsing path deterministically, avoid slow input and output operations and reset state between runs.

DecisionRecommended practiceReason
Seed selectionSmall, diverse, valid samplesFaster execution, broader coverage
DictionariesAdd magic values and keywordsHelps pass format checks
Campaign lengthHours to days, continuous for critical codeCoverage often grows slowly after plateaus
ParallelismOne instance per core with shared corpusScales throughput nearly linearly
CI integrationShort fuzz runs on each changeCatches regressions early
Practical fuzzing decisions

Teams should track coverage over time rather than raw executions. When coverage stops growing, the next improvement usually comes from a better harness, a dictionary or a structure-aware mutator, not from more machines.

Limitations and common mistakes

  • Checksums and cryptographic checks block random mutations unless the harness disables or recomputes them.
  • Nondeterministic harnesses produce crashes that cannot be reproduced and waste triage time.
  • Skipping sanitizers hides most memory-safety bugs that never cause an immediate crash.
  • Ignoring timeouts and memory exhaustion misses denial-of-service vulnerabilities.
  • Treating no findings as proof of security is wrong; fuzzing shows the presence of bugs, never their absence.

The simple explanation

Fuzzing is like testing a vending machine by pressing every button in strange orders, inserting bent coins and shaking it, thousands of times without a break. If the machine ever jams or hands out free snacks, you have found a flaw.

Software is tested the same way. A tool keeps feeding a program odd and broken data and watches what happens. Every crash is a clue to a weakness that attackers might otherwise find first.

How it is used in practice

A team maintaining an open-source image library writes a libFuzzer harness that passes each generated buffer to its PNG decoding function. They build it with AddressSanitizer, seed it with fifty small sample images and run it on a sixteen-core server overnight.

Within six hours the fuzzer reports a heap buffer overflow triggered by an image whose declared width does not match its compressed data size. The saved crashing input reproduces the bug instantly. Developers add a bounds check, include the input in the regression suite and enroll the project in continuous fuzzing, which later catches two more parsing bugs before they reach a release.