This is not a weird question at all — it’s a very common reaction from people coming to libFuzzer with a “random testing” mental model. The friction you’re feeling is real, and it comes from a foundational design difference, not from you misunderstanding something small.
Let me answer in three layers:
-
Why libFuzzer behaves this way (foundational principle)
-
Are there fuzzers that naturally generate fixed-length inputs?
-
What to do, depending on what you actually want
1️⃣ The key thing: libFuzzer is not a random generator
libFuzzer is not designed to generate random inputs at all.
It is an evolutionary, coverage-guided, mutational fuzzer.
That means:
-
It starts from a corpus (even if empty, it creates trivial seeds)
-
It mutates existing inputs
-
Mutations are judged only by coverage increase
-
Size is just another mutation dimension, not a constraint
In libFuzzer’s mental model:
“Input length is not semantics.
Bytes are semantics only if the target observes them.”
So the engine optimizes for:
-
coverage
-
mutation efficiency
-
minimizing useless work
A hard requirement like “always exactly 100 bytes” is outside its default optimization goals.
That’s why:
-
there is -max_len
-
but no -exact_len
This is not an omission — it’s deliberate.
2️⃣ “Why isn’t fixed-size generation a basic feature?”
Because in libFuzzer’s universe:
-
Rejecting inputs is expensive
-
Forcing exact length globally breaks many mutation strategies
-
Structure is expected to be handled by the harness or mutator
From LLVM devs’ point of view:
If you need structure (fixed size, grammar, invariants),
you must define the structure — not ask the engine to guess it.
This is why libFuzzer exposes:
They expect you to define invariants.
So yes — libFuzzer intentionally does not offer “random fixed-length generator”.
3️⃣ Are there fuzzers that do naturally generate fixed-length inputs?
✅ YES — but they are different classes of fuzzers
Here’s the landscape.
🔹 AFL / AFL++ (similar philosophy, same issue)
Notes:
So AFL does not solve your problem either.
🔹 Honggfuzz
🔹 QuickCheck / property-based testing (NOT fuzzers)
Examples:
-
RapidCheck (C++)
-
Hypothesis (Python)
-
QuickCheck (Haskell)
These:
-
generate values, not byte streams
-
fixed-size arrays are trivial
-
great for invariants
-
terrible for deep coverage fuzzing
Example mental model:
If your goal is “generate lots of random fixed-size inputs”, these are much closer to what you intuitively want.
But:
🔹 Grammar-based fuzzers (libprotobuf-mutator, Peach, etc.)
These:
-
generate structured inputs
-
often fixed-size or shape-constrained
-
require schemas / grammars
Powerful, but heavy.
4️⃣ The uncomfortable truth (but important)
What you want:
“Random inputs of fixed length”
What libFuzzer wants:
“Inputs that mutate in ways that increase coverage”
Those are orthogonal goals.
libFuzzer assumes:
-
if length matters, you will encode that in the harness or mutator
-
if suffix bytes don’t matter, they are irrelevant noise
This is why ignoring bytes after 100 is a bug in the harness, not in the fuzzer (from libFuzzer’s POV).
5️⃣ The “correct” libFuzzer mindset shift
Instead of thinking:
“libFuzzer should generate exactly 100 bytes”
Think:
“My semantic input is a fixed-size object of 100 bytes.
libFuzzer’s raw bytes are just mutation fuel.”
Then you do one of these:
✅ A) Map arbitrary bytes → fixed semantic object (most common)
You already saw this:
This is by far the most common approach in real fuzzers.
✅ B) Custom mutator (you already did this)
This is the official extension point for enforcing invariants.
✅ C) Property-based testing (if coverage isn’t the goal)
6️⃣ So… is your frustration justified?
Yes. Completely.
What feels “basic” to you:
is intentionally not a primitive in libFuzzer.
Not because it’s hard.
Not because LLVM devs didn’t think of it.
But because:
This is one of the biggest conceptual hurdles when learning modern fuzzers.
7️⃣ Straight recommendation (no ideology)
If:
👉 Your custom mutator approach is correct and idiomatic
👉 Add -max_len=100
👉 Seed with a 100-byte corpus file
👉 Ensure mutations actually change observed bytes
That’s exactly how LLVM, Chrome, and OSS-Fuzz projects do it internally.
If instead you want:
👉 use RapidCheck / Hypothesis-style testing
Final reassurance
You’re not missing something obvious.
You’ve hit a real philosophical boundary between random testing and coverage-guided fuzzing.
If you want, next I can:
-
show how LLVM itself fuzzes fixed-size crypto primitives
-
show a minimal RapidCheck vs libFuzzer comparison
-
or help you decide which tool is actually better for your specific bug class