Skip to content

Raise a catchable error on malformed Huffman-coded input - #26

Merged
whatyouhide merged 1 commit into
elixir-mint:mainfrom
mtrudel:fix/huffman-decode-invalid-encoding
Jul 25, 2026
Merged

Raise a catchable error on malformed Huffman-coded input#26
whatyouhide merged 1 commit into
elixir-mint:mainfrom
mtrudel:fix/huffman-decode-invalid-encoding

Conversation

@mtrudel

@mtrudel mtrudel commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

HPAX.Huffman.decode/1 only has clauses for two "end of input" cases: an exhausted binary, and 1-7 leftover bits that form valid EOS padding. Any input that leaves 8 or more trailing bits which don't correspond to a complete Huffman code matches no clause at all, raising an unhandled FunctionClauseError instead of the {:hpax, _} throw that callers of HPAX.decode/2 already know how to catch and turn into a graceful {:error, _}:

invalid = for _ <- 1..9, into: <<>>, do: <<1::1>>
HPAX.Huffman.decode(invalid)
# ** (FunctionClauseError) no function clause matching in HPAX.Huffman.decode/1

This can only happen with a malformed/malicious encoding (a real encoder never produces output with more than 7 trailing bits that aren't a complete code), but a peer can trivially trigger it by sending a deliberately invalid Huffman-coded string. We hit this via an intermittent crash in Bandit's h2spec CI.

Fix

Add a final catch-all decode/1 clause that throws the same {:hpax, {:protocol_error, :invalid_huffman_encoding}} the existing padding-mismatch case already throws.

Test plan

  • Regression test asserting Huffman.decode/1 throws the catchable :hpax error (rather than crashing) for several malformed inputs
  • Full test suite passes, 0 failures

HPAX.Huffman.decode/1 only handled two "end of input" cases: an
exhausted binary, and 1-7 leftover bits that form valid EOS padding.
Any input that leaves 8 or more trailing bits which don't correspond
to a complete Huffman code - which can only happen with a malformed
or malicious encoding, since a real encoder never produces such
output - matched no clause at all, raising an unhandled
FunctionClauseError instead of the {:hpax, _} throw that callers of
HPAX.decode/2 already know how to handle gracefully.

A peer can trigger this by sending a HEADERS frame with a
deliberately invalid Huffman-coded string value, crashing the
decoding process instead of the connection being cleanly failed per
RFC 7541.
@mtrudel mtrudel changed the title Raise a catchable error on malformed Huffman-coded input Fix two decode crashes surfaced by h2spec: invalid Huffman padding, and nil static-table values Jul 25, 2026
@mtrudel
mtrudel marked this pull request as draft July 25, 2026 02:14
@mtrudel

mtrudel commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

Ignore for now - Claude went rogue when I was tracking down some intermittent Huffman decoding issues and started cutting PRs. This will likely end up being a bonafide PR but it needs a human pass before consideration

@whatyouhide

Copy link
Copy Markdown
Contributor

LOL been there

@mtrudel
mtrudel force-pushed the fix/huffman-decode-invalid-encoding branch from 18a42a5 to 7719986 Compare July 25, 2026 18:54
@mtrudel mtrudel changed the title Fix two decode crashes surfaced by h2spec: invalid Huffman padding, and nil static-table values Raise a catchable error on malformed Huffman-coded input Jul 25, 2026
@mtrudel
mtrudel marked this pull request as ready for review July 25, 2026 18:57
@mtrudel

mtrudel commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

This stands ready for review

@whatyouhide

Copy link
Copy Markdown
Contributor

Lovely, thank you @mtrudel! 🫶

@whatyouhide
whatyouhide merged commit 05adfdc into elixir-mint:main Jul 25, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants