Skip to content

http/2: bound incoming header blocks with max-header-list-size - #1247

Open
pjfanning wants to merge 4 commits into
apache:1.4.xfrom
pjfanning:continuation-1.4
Open

http/2: bound incoming header blocks with max-header-list-size#1247
pjfanning wants to merge 4 commits into
apache:1.4.xfrom
pjfanning:continuation-1.4

Conversation

@pjfanning

Copy link
Copy Markdown
Member

cherry pick dfd5b18 #1216

pjfanning and others added 3 commits August 28, 2026 21:24
…e#1216)

Motivation:
The HTTP/2 header decompression stage had no upper bound on the incoming
side. The HPACK decoder was constructed with
Http2Protocol.InitialMaxHeaderListSize (Int.MaxValue) and the header block
fragments of a HEADERS frame and its CONTINUATION frames were accumulated
until END_HEADERS was seen, so the memory used for a single header block
was limited only by what the peer chose to send. Neither endpoint
advertised SETTINGS_MAX_HEADER_LIST_SIZE, so a peer had no way of knowing
what it may send either.

Modification:
Add a `max-header-list-size` setting (64 KiB by default) to
`pekko.http.server.http2` and `pekko.http.client.http2` and pass it to
`HeaderDecompression`, which now

* constructs the HPACK decoder with that limit and checks the truncation
  result of `Decoder.endHeaderBlock()`, which was previously ignored,
* applies the same limit to the accumulated header block fragments,
  accounting each fragment with its frame header size so that the number
  of empty CONTINUATION frames per header block is bounded as well,
* fails the connection with GOAWAY(ENHANCE_YOUR_CALM) when the limit is
  exceeded.

The configured value is advertised to the peer in the initial SETTINGS
frame.

Result:
The memory used for a single incoming header block is bounded by the
configured limit on both the server and the client side, and peers are
told about the limit up front.

Tests:
- sbt "http2-tests/testOnly org.apache.pekko.http.impl.engine.http2.Http2ServerSpec" - pass, 5 new tests
- sbt http2-tests/test - pass
- sbt http-core/test - pass (HostConnectionPoolSpec flaked in the full run, passes on its own)
- sbt +http-core/mimaReportBinaryIssues - pass
- sbt http-core/scalafmt http2-tests/Test/scalafmt - clean
- sbt http-core/headerCreateAll - no changes

References:
None - bounds the memory used for incoming HTTP/2 header blocks
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pjfanning pjfanning added this to the 1.4.1 milestone Aug 28, 2026
`ParsedHeadersFrame` has no error-info field on 1.4.x and the parsing
exception is not routed through it, so the header decompression stage
keeps 1.4.x's four-argument frame and its IOException-only handling,
along with `ByteStringInputStream`. Only the header list size checks are
new. `RequestParsingSpec` keeps unpacking `futureValue` exceptions, since
a malformed request still fails the stream here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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