Fuzz Test Random Bytes Input Crash in Python
A simple fuzz test generates random byte inputs and runs a parser to find unexpected crashes.
Python code
45 linesimport random
def parse_header(data: bytes) -> dict:
"""Parse a fake binary header format."""
if len(data) < 8:
raise ValueError("header too short")
magic = data[:4]
if magic != b'PARS':
raise ValueError("bad magic")
version = data[4]
if version != 1:
raise ValueError("unsupported version")
flags = data[5]
if flags & 0x80:
raise ValueError("invalid flag bit")
payload_len = int.from_bytes(data[6:8], "big")
if payload_len > 1000:
raise ValueError("payload too large")
return {"version": version, "flags": flags, "payload_len": payload_len}
def fuzz(num_trials: int = 10000) -> None:
"""Randomly generate inputs and look for crashes."""
for _ in range(num_trials):
length = random.randint(0, 20)
data = bytes(random.getrandbits(8) for _ in range(length))
try:
parse_header(data)
except ValueError:
pass # expected for malformed input
except Exception as e:
print(f"CRASH: input={data!r} -> {type(e).__name__}: {e}")
return
print(f"All {num_trials} fuzz trials passed without crashes.")
if __name__ == "__main__":
fuzz(5000)
Output
All 5000 fuzz trials passed without crashes.
How it works
The fuzz test randomly generates byte strings of varying lengths and feeds them to parse_header. The function expects a specific binary format: 4 magic bytes, a version byte, a flags byte, and a 2-byte payload length. Expected malformed inputs raise ValueError, which is caught and ignored. Any other exception is treated as a crash and printed, ending the loop. This approach quickly uncovers assumptions in parsers that can lead to unexpected exceptions.
Common mistakes
- Forgetting to catch `ValueError` and accidentally counting expected failures as crashes
- Not seeding the random number generator for reproducible runs
- Generating inputs that are too uniform, missing edge cases like empty or very long data
Variations
- Use a fixed tuple of edge-case inputs like `b''` and `b'PARS'` mixed with random data
- Use `hypothesis` or `property-based testing` frameworks for more structured fuzzing
Real-world use cases
- Testing a binary protocol parser in a network service for robustness against malformed packets.
- Validating that a file format parser handles corrupted files without crashing in a data ingestion pipeline.
- Checking security-sensitive parsers (like deserializers) for vulnerabilities before deployment.
Sponsored
More from Testing & modern typing
- Capture stdout and stderr with pytest capsys easy
- Characterization Test for Legacy Python Code medium
- Dataclass with Type Hints Fields in Python easy
- Dependency Injection in Python for Testability easy
- Design Data Helpers with Python TypedDict and Literal easy
- Fix and Test a Regression Bug in Python with Unit Tests easy
Keep learning
Related tutorials and quizzes for this topic.