Differential Fuzzing
- title
- Differential Fuzzing
- type
- concept
- summary
- Run the same input through two implementations of the same spec and treat disagreement as a bug; finds spec-conformance issues neither implementation would catch alone
- parent
- supply-chain-security
- tags
- fuzzing, security, testing
- created
- 2026-05-12
- updated
- 2026-05-12
Most coverage-guided fuzzing looks for crashes โ segfaults, panics, asserts, sanitizer trips. Differential fuzzing looks for disagreement: feed the same input to two independent implementations of the same specification, compare their outputs, treat any divergence as a bug. The implementations cross-validate each other; neither has to crash for the test to fail.
The technique is the right one when:
- The spec is the contract under test, not any single implementation
- The cost of inter-implementation divergence is high โ consensus systems, cryptographic protocols, compilers producing reproducible binaries
- There are at least two reasonably-mature implementations in the ecosystem to compare
Where it lives in practice
- Ethereum L1/L2 clients โ geth vs nethermind vs reth, op-node vs Kona, EVM vs Revm. A state-root mismatch on the same block is a chain split.
- TLS โ OpenSSL vs BoringSSL vs Rustls, sometimes also vs the wire-format spec
- JSON / serialization โ encoders' acceptance of "exotic" inputs (duplicate keys, leading zeros, NaN) often diverges, sometimes exploitably
- Compilers โ Csmith and Yarpgen drive differential fuzzing across GCC/Clang/MSVC
- SQL engines โ SQLite as oracle vs Postgres/MySQL for the same query
- Protocol parsers โ HTTP request smuggling research used differential parsing between front-end proxies and back-end servers
Combination with grammar-based fuzzing
grammar-based-fuzzing is what makes differential fuzzing practical at scale: both implementations need to actually parse the input far enough to produce output, so the fuzzer can't waste cycles on inputs both reject at byte zero. A grammar that produces "things that look like a valid transaction" exercises the post-parse semantic disagreements, which is where consensus bugs live.
Cases from gosentry's early campaigns
gosentry's blog post lists four early findings, all consensus-shaped:
- Optimism / Kona vs op-node disagreed on Brotli channel handling
- Frame parsing diverged from the Optimism spec
- Revm's failed-deposit handler didn't bump the nonce โ state-root mismatch against other Ethereum clients
- Optimism / Kona panicked on unknown batch types instead of rejecting
Each one is a spec interpretation gap, not an implementation crash.