Lab 01 — Bounded Binary Parser and Fuzz Target
Difficulty: 4/5 | Time: 6–8 hours
Pairs with the Phase 01 WARMUP Chapters 4–6 (integer arithmetic, secure parsers, parser differentials) and the HITCHHIKERS-GUIDE ("A Parser Review, Field by Field").
Why This Lab Exists (Purpose & Goal)
A parser is the front door of a program: it is the first code to touch attacker-controlled bytes, and every length, offset, identity, and privileged action downstream inherits whatever the parser decided. A huge fraction of the worst vulnerabilities in history — Heartbleed, the endless stream of image/font/protocol CVEs — are parser bugs. The goal of this lab is to make you write a parser that cannot be tricked: one that bounds everything, validates before it trusts, and consumes exactly one canonical message. This is the single most reusable secure-coding skill in the field.
The Concept, In the Weeds
You parse an untrusted TLV (Tag-Length-Value) message:
magic "SG" | version:u8 | record_count:u8
record := type:u8 | flags:u8 | length:u16-be | value:length
Types 1 (username) and 2 (tenant) are UTF-8 singleton fields; type 3 is opaque payload;
unknown fields with the critical flag 0x01 must fail. The lab forces the eight rules of a secure
parser, and two of them are subtle and security-critical:
length <= maxis not enough — you also needlength <= remaining. The first check alone lets a field claim a legal size but run past the end of the buffer (an out-of-bounds read); the second alone lets a field fit the buffer but exceed the protocol limit (resource abuse). You need both.- Reject duplicate singletons and trailing bytes — these are not tidiness, they are the
parser-differential attack surface. Two
tenantfields let one component pick identity A while another picks identity B. A hidden second message after the first lets a proxy stop at message 1 while the backend reads message 2. The same byte stream interpreted two ways is how request-smuggling, SAML XML-signature-wrapping, and certificate-confusion attacks all work.
Most "length bugs" actually begin in integer arithmetic before any copy: count * size wraps,
the check validates the wrapped value, and the copy uses the intended huge value. You validate
operands before the operation, not the result after it.
Why This Matters for Protecting the Company
Every service that ingests data from the network or from users runs a parser, and a single lenient parser is a foothold. When you can write — and review — a parser that bounds input, validates integers before allocating, and refuses ambiguity, you can protect any data-ingestion surface the company owns: an API gateway, a file-upload pipeline, a protocol implementation, a certificate validator. Equally important, you learn to spot the missing check in code review, which is where most of these bugs are cheapest to kill.
How This Is Used on the Job
This is the muscle behind protocol/format security reviews and fuzzing harness design. You will fuzz
this exact parser (fuzz_smoke.py), then — the real exercise — port it to C and Rust and run both
under sanitizers, comparing their accept/reject behavior. Any disagreement between the two
implementations is a finding: a parser differential in miniature, discovered exactly the way a
vulnerability researcher discovers them in production code (Phase 08).
Attack / Failure Cases You Must Handle
Truncation; integer/length confusion (multiply-before-allocate overflow); duplicate singleton fields; unknown critical fields; allocation abuse (record/value/text-length ceilings); embedded NUL and invalid UTF-8; a valid message plus a trailing second message; exactly-at-limit and one-over-limit inputs.
Run
pytest test_lab.py -v
LAB_MODULE=solution pytest test_lab.py -v
python fuzz_smoke.py
Validation — What You Should Be Able to Do Now
- Explain why checking
length <= maxwithoutlength <= remainingis insufficient — and write the correct two-clause check from memory. - Show how an integer overflow before a copy defeats a size check, and write the operand check
(
count > SIZE_MAX / size) that prevents it. - Articulate what a parser differential is and name three real exploit classes built on one (request smuggling, XML signature wrapping, certificate name confusion).
- Drive a fuzzer + sanitizer loop and turn a crash into a minimized regression test.
The Broader Perspective
You just internalized the maxim that quietly governs all of input security: "be strict in what you accept." The robustness folklore ("be liberal in what you accept") has cost the industry thousands of CVEs, because leniency in a parser is the attack surface. Carry forward the instinct that one canonical interpretation, enforced everywhere on the path, is the cure — it is the same fix you will apply to HTTP framing (Phase 02 request smuggling), to TLV capability tokens (Lab 05), and to any two components that must agree on what a message means.
Interview Angle
- "Design a safe parser for an untrusted length-prefixed format." — Bound total input; require the
full fixed header before indexing; check
len <= field_max AND len <= remaining; reject duplicate singleton/security fields and unknown-critical fields; decode text strictly; consume exactly one message; structured errors. Implement it as an explicit cursor state machine and fuzz it with sanitizers. - "How would you find a parser differential?" — Run two independent implementations of the same format on the same inputs and flag disagreements; any divergence is a candidate smuggling bug.
References
- Phase 01 WARMUP Chapters 4–6; The Fuzzing Book; LangSec / "recognizer" literature.
- "The Heartbleed Bug" (CVE-2014-0160) — the canonical missing-bounds-check disclosure.