Fuzzing
Meridian’s parsers are continuously fuzz-tested using cargo-fuzz with LLVM’s libFuzzer. The fuzzing harness uses coverage-guided mutation, structure-aware input generation, and security invariant assertions.
Running the Fuzzer
# Requires nightly Rust
rustup toolchain install nightly
# Run a specific target (runs until stopped)
cargo +nightly fuzz run http1_request
# Run with HTTP dictionary (faster exploration)
cargo +nightly fuzz run http1_request -- -dict=fuzz/dictionaries/http.dict
# Run for a fixed time
cargo +nightly fuzz run http1_request -- -max_total_time=3600
# Run all targets
for t in http1_request http1_structured body_framing header_smuggling \
path_normalize config_toml chunked_body; do
cargo +nightly fuzz run $t -- -max_total_time=3600
done
Fuzz Targets
| Target | Technique | Speed | What It Tests |
|---|---|---|---|
http1_request | Raw bytes | ~200K/sec | HTTP/1.1 parser never panics |
http1_structured | Structure-aware | ~79K/sec | Deep parser states via arbitrary |
body_framing | Structure-aware | ~67K/sec | CL+TE smuggling detection |
header_smuggling | Differential | ~62K/sec | Adversarial header combos with invariant checks |
path_normalize | Raw bytes | ~142K/sec | Idempotence, no traversal, no double slashes |
config_toml | Raw UTF-8 | ~48K/sec | TOML deserializer never panics |
chunked_body | Raw bytes (async) | ~26K/sec | Dechunker respects size limits |
Techniques
Coverage-Guided Mutation
libFuzzer tracks which code branches are covered by each input. Inputs that reach new branches are kept and mutated further. This systematically explores the parser’s state space rather than generating random noise.
Structure-Aware Fuzzing
The http1_structured and body_framing targets use the arbitrary crate to generate structured HTTP requests. The fuzzer mutates structured fields (methods, headers, versions) independently while maintaining HTTP-like syntax, reaching deeper parser states faster than raw byte mutation.
Security Invariant Assertions
The header_smuggling and body_framing targets embed security invariants as assertions:
#![allow(unused)]
fn main() {
// If both Content-Length and Transfer-Encoding are present, MUST reject
if has_cl && has_te {
assert!(result.is_err(), "SMUGGLING: CL+TE accepted!");
}
}
If the fuzzer finds an input that violates these invariants, it’s a real security bug.
HTTP Dictionary
The fuzz/dictionaries/http.dict file contains HTTP protocol tokens (methods, headers, delimiters, smuggling payloads) that seed the fuzzer’s mutation engine for faster exploration of HTTP-specific code paths.
Bugs Found
The fuzzer has found and fixed:
- Integer overflow in chunked body size check — A chunk with hex size
ffffffffffffffffcausedbody.len() + chunk_sizeto wrap aroundusize, bypassing the max body size limit. Fixed with overflow-safe arithmetic.
Crash Artifacts
When a crash is found, the input is saved to fuzz/artifacts/<target>/crash-<hash>. To reproduce:
cargo +nightly fuzz run chunked_body fuzz/artifacts/chunked_body/crash-<hash>