Lion Cipher Lab

Write a strict UTF-8 decoder from scratch, and catch overlong and surrogate encodings

Why it matters

Every program that reads text from outside first turns BYTES into CHARACTERS. That step is decoding, and it is a security boundary that almost nobody looks at. UTF-8 has one rule that matters more than the rest: every character has exactly ONE correct spelling in bytes. A decoder that also accepts a second, longer spelling (an OVERLONG encoding) or a piece of UTF-16 that is not a character (a SURROGATE) gives an attacker a way to write '/' or '.' that does not look like '/' or '.' to anything checking the raw bytes. Around 2000 that exact gap let attackers read files outside the web root of…

What you'll learn

Steps in this lab

  1. Bytes are not characters: what a decoder does, and the one rule UTF-8 adds
  2. Set up: a terminal, Python, one folder
  3. Write utf8.py: an encoder, a naive decoder, and a strict decoder from Table 3-7
  4. The classic malformed inputs, one row each
  5. Differential testing: prove your decoder right on 1.7 million inputs, and count the naive one's holes
  6. Why it matters: a toy file server, a byte-level check, and a naive decode
  7. Honest negative 1: strict decoding does not stop look-alike characters
  8. Honest negative 2: every error handler costs something -- measured
  9. Honest negative 3: your decoder is correct, and far too slow to ship
  10. Optional: run Markus Kuhn's public stress test through both decoders
  11. Do it better: validate at the boundary, decide on the final form, then clean up

Open the full interactive lab →