Skip to content

fix: count repeated Connection headers towards max-header-count - #1255

Open
pjfanning wants to merge 1 commit into
apache:mainfrom
pjfanning:bound-connection-header-merge
Open

fix: count repeated Connection headers towards max-header-count#1255
pjfanning wants to merge 1 commit into
apache:mainfrom
pjfanning:bound-connection-header-merge

Conversation

@pjfanning

Copy link
Copy Markdown
Member

Motivation

HttpMessageParser.parseHeaderLines merges repeated Connection headers into one accumulated header (Some(x) => ...Some(x.append(h.tokens))...), but that merge branch re-entered the loop with headerCount unchanged:

case h: Connection => ch match {
    case None =>
      parseHeaderLines(input, lineEnd, headers += h, headerCount + 1, Some(h), ...)
    case Some(x) => parseHeaderLines(input, lineEnd, headers, headerCount, Some(x.append(h.tokens)), ...) // headerCount not incremented
  }

So the headerCount < settings.maxHeaderCount guard at the top of the loop never trips on additional Connection headers, and a single message can carry an unbounded number of them. Connection.append is Connection(this.tokens ++ tokens) — an O(n) copy of the accumulated token list per header — and there is no total-header-block byte limit, so N repeated Connection headers force ~N²/2 token copies plus an N-element list from one request. Every other non-deduplicated header increments the count via the generic branch; only this accumulating path did not, making it a max-header-count bypass with quadratic amplification.

The Content-Length/Content-Type duplicate branches also skip the increment, but they require a structurally identical value and do not accumulate, so they cannot be grown — Connection is the only unbounded merge.

Modification

Increment headerCount in the Connection merge branch so each repeated Connection header counts towards max-header-count, matching the generic header path. Once the limit is reached the existing guard rejects the message with the standard "more than the configured limit" error, bounding the number of merges to max-header-count (default 64).

Result

A flood of Connection headers is rejected at max-header-count instead of accumulating unboundedly; the parser's work on this path is bounded by the configured limit like every other header. Legitimate use (one or a few Connection headers) is unaffected — they were already counted the same way once the merge is charged.

Tests

  • sbt "http-core/testOnly org.apache.pekko.http.impl.engine.parsing.RequestParserCRLFSpec org.apache.pekko.http.impl.engine.parsing.RequestParserLFSpec" — pass (116 tests). Two new tests assert that exceeding max-header-count is rejected both for distinct headers and for repeated Connection headers. Verified the Connection test fails with the fix stashed (the flood is never caught, no error is emitted).
  • sbt http-core/mimaReportBinaryIssues — pass (internal impl.engine.parsing change, no public API).
  • Native scalafmt on the two changed files — clean.

References

None - bounds repeated Connection header accumulation to max-header-count

🤖 Generated with Claude Code

Motivation:
`parseHeaderLines` merges repeated `Connection` headers into a single
accumulated header via `x.append(h.tokens)`, but the merge branch
re-entered the loop with `headerCount` unchanged. The
`headerCount < settings.maxHeaderCount` guard therefore never tripped
on `Connection` headers, so a single message could carry an unbounded
number of them. Because `Connection.append` is `Connection(this.tokens
++ tokens)` — an O(n) copy of the accumulated token list on each
header — and there is no total-header-block byte limit, N repeated
`Connection` headers drive ~N²/2 token copies and an N-element list
from one request. Every other non-deduplicated header increments the
count; only this accumulating path did not.

Modification:
Increment `headerCount` in the `Connection` merge branch so each
repeated `Connection` header counts towards `max-header-count`, exactly
as the generic header path does. Once the limit is reached the existing
guard rejects the message with the standard "more than the configured
limit" error, bounding the number of merges to `max-header-count`.

Result:
A flood of `Connection` headers is rejected at `max-header-count`
(default 64) instead of accumulating unboundedly, so the parser's
work is bounded by the configured limit like every other header.

Tests:
- sbt "http-core/testOnly org.apache.pekko.http.impl.engine.parsing.RequestParserCRLFSpec org.apache.pekko.http.impl.engine.parsing.RequestParserLFSpec" - pass (116 tests); two new tests assert that exceeding max-header-count is rejected both for distinct headers and for repeated Connection headers. Verified the Connection test fails with the fix stashed (the flood is never caught).
- sbt http-core/mimaReportBinaryIssues - pass

References:
None - bounds repeated Connection header accumulation to max-header-count
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