Map
โ†‘Supply Chain Security

Differential Fuzzing

Wiki conceptfuzzingsecuritytesting โ†ณ show in map Markdown
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
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.