Skip to content

Bound buffered header blocks by the encoded size of the list limit - #531

Open
ericmj wants to merge 1 commit into
mainfrom
http2-header-block-bound
Open

ericmj wants to merge 1 commit into
mainfrom
http2-header-block-bound

Conversation

@ericmj

@ericmj ericmj commented Oct 16, 2026

Copy link
Copy Markdown
Member

While waiting for CONTINUATION frames, the client compared the size of the buffered compressed header block with its advertised SETTINGS_MAX_HEADER_LIST_SIZE, assuming a compressed block is never larger than the header list it decodes to. Huffman coding can make a string longer than its octets (RFC 7541 5.2), so a valid block whose decoded list is under the limit could exceed it on the wire. With a limit of 1,000 bytes, a 475-byte header list that encodes to 1,307 bytes was accepted in a single HEADERS frame and closed the connection with PROTOCOL_ERROR when split over HEADERS and CONTINUATION.

The limit is enforced on the decoded header list, and the fragments buffered before END_HEADERS are bounded by an upper bound on the encoded size of a header list within the limit, assuming minimal integer representations (RFC 7541 5.1) and at most two dynamic table size updates (RFC 7541 4.2). HPACK decoders accept redundant zero continuation bytes in integers and any number of size updates, so no finite bound covers every block that decodes to such a list. The fragment that carries END_HEADERS isn't counted, and the complete block is then judged by its decoded size.

A Huffman-coded octet takes at most 30 bits (RFC 7541 Appendix B). A minimal integer below 2^32 takes at most 6 bytes, so a field has at most 13 bytes of prefixes and integers (a representation byte, the name length and the value length) and less than 2 bytes of Huffman padding, 15 bytes in total. That's less than the 120 bytes its 32-byte overhead (RFC 9113 6.5.2) contributes at the 30/8 ratio. The two size updates take at most 6 bytes each. The bound is max_header_list_size * 30 / 8 + 12 bytes, and a peer that buffers more than that gets a connection error.

While waiting for CONTINUATION frames, the client compared the size of
the buffered compressed header block with its advertised
SETTINGS_MAX_HEADER_LIST_SIZE, assuming a compressed block is never
larger than the header list it decodes to. Huffman coding can make a
string longer than its octets (RFC 7541 5.2), so a valid block whose
decoded list is under the limit could exceed it on the wire. With a
limit of 1,000 bytes, a 475-byte header list that encodes to 1,307
bytes was accepted in a single HEADERS frame and closed the connection
with PROTOCOL_ERROR when split over HEADERS and CONTINUATION.

The limit is enforced on the decoded header list, and the fragments
buffered before END_HEADERS are bounded by an upper bound on the encoded
size of a header list within the limit, assuming minimal integer
representations (RFC 7541 5.1) and at most two dynamic table size
updates (RFC 7541 4.2). HPACK decoders accept redundant zero
continuation bytes in integers and any number of size updates, so no
finite bound covers every block that decodes to such a list. The
fragment that carries END_HEADERS isn't counted, and the complete block
is then judged by its decoded size.

A Huffman-coded octet takes at most 30 bits (RFC 7541 Appendix B). A
minimal integer below 2^32 takes at most 6 bytes, so a field has at
most 13 bytes of prefixes and integers (a representation byte, the name
length and the value length) and less than 2 bytes of Huffman padding,
15 bytes in total. That's less than the 120 bytes its 32-byte overhead
(RFC 9113 6.5.2) contributes at the 30/8 ratio. The two size updates
take at most 6 bytes each. The bound is
max_header_list_size * 30 / 8 + 12 bytes, and a peer that buffers more
than that gets a connection error.
@github-actions

Copy link
Copy Markdown

Coverage Report for CI Build 81963866

Coverage increased (+0.02%) to 90.527%

Details

  • Coverage increased (+0.02%) from the base build.
  • Patch coverage: 7 of 7 lines across 1 file are fully covered (100%).
  • 1 coverage regression across 1 file.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

1 previously-covered line in 1 file lost coverage.

File Lines Losing Coverage Coverage
lib/mint/http1/response.ex 1 96.05%

Coverage Stats

Coverage Status
Relevant Lines: 1974
Covered Lines: 1787
Line Coverage: 90.53%
Coverage Strength: 722.33 hits per line

馃挍 - Coveralls

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.

1 participant