fix(http2): enforce payload limit on HTTP/2 server request body to prevent heap exhaustion DoS - #1576
Open
EvenLjj wants to merge 2 commits into
Open
fix(http2): enforce payload limit on HTTP/2 server request body to prevent heap exhaustion DoS#1576EvenLjj wants to merge 2 commits into
EvenLjj wants to merge 2 commits into
Conversation
…event heap exhaustion DoS HTTP/2 (h2/h2c) server path accumulated DATA frames in Http2ServerChannelHandler.onDataRead without comparing the accumulated size against ServerTransportConfig.getPayload(). The 8 MiB default payload limit was only applied to the HTTP/1.1 pipeline via HttpObjectAggregator; the HTTP/2/h2c pipelines had no equivalent limit. An unauthenticated client could keep a request stream open and continuously send legal DATA frames (never sending END_STREAM); since onDataRead returns processed bytes, flow control issues WINDOW_UPDATE, so HTTP/2 flow control does NOT cap the application-layer request body. The server retained attacker data before routing/auth/ deserialization/RPC invocation until the JVM heap was exhausted (CWE-770, CVSS 7.5). Fix: - Http2ServerChannelHandler: accept maxContentLength (from transport.payload.max, same source as HttpObjectAggregator). In onDataRead, before writeBytes, check accumulated + frame size against maxContentLength; on overflow release the accumulated ByteBuf, reset the stream via writeRstStream(PROTOCOL_ERROR), and do NOT call handleRequest. Return processed bytes to keep flow control correct. - Release the accumulated msg after sync handleRequest (fixes a pre-existing per-request ByteBuf leak: AbstractHttpServerTask never released the request buffer). - Register an Http2ConnectionAdapter to release any residual buffer on stream close (connection drop / RST_STREAM / normal end) as a backstop. - Http2ChannelHandlerBuilder / Http2ServerChannelInitializer: thread maxHttpContentLength through to the handler for all h2/h2c paths (TLS, prior-knowledge, http-upgrade). - Add regression test Http2ClearTextPayloadLimitTest: a valid small request is served (200, method invoked); an oversized request is reset/no 200 and the service method is NOT invoked. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR hardens the HTTP/2 (h2/h2c) server transport by enforcing the existing transport.payload.max request-body limit on the HTTP/2 DATA-frame accumulation path, preventing heap exhaustion DoS scenarios and improving ByteBuf lifecycle cleanup.
Changes:
- Enforce a per-stream max accumulated request-body size in
Http2ServerChannelHandler.onDataRead, resetting the stream when exceeded and releasing accumulated buffers. - Thread
maxHttpContentLengththrough the HTTP/2 server pipeline construction for TLS negotiation, prior-knowledge, and HTTP/1.1 upgrade paths. - Add an h2c integration regression test validating that oversized payloads do not produce a 200 response and do not invoke the service method.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| test/test-integration/src/test/java/com/alipay/sofa/rpc/transport/http/Http2ClearTextPayloadLimitTest.java | Adds regression coverage for enforcing payload on h2c request bodies. |
| remoting/remoting-http/src/main/java/com/alipay/sofa/rpc/transport/http/Http2ServerChannelInitializer.java | Passes maxHttpContentLength into all HTTP/2 handler build paths to apply the limit consistently. |
| remoting/remoting-http/src/main/java/com/alipay/sofa/rpc/transport/http/Http2ServerChannelHandler.java | Implements accumulated DATA-frame size checks, stream reset behavior, and ByteBuf cleanup on end-of-stream/stream-close. |
| remoting/remoting-http/src/main/java/com/alipay/sofa/rpc/transport/http/Http2ChannelHandlerBuilder.java | Propagates the configured max content length into the HTTP/2 server channel handler. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…romise Address Copilot review on PR sofastack#1576: - Http2ChannelHandlerBuilder: restore the single-arg constructor as an overload delegating to the 2-arg form with the default transport.payload.max, to preserve source/binary compatibility for external callers. - Http2ClearTextPayloadLimitTest: capture the requestId returned by sendHttpRequest and removePromise() after awaiting, so the reset/no-response path does not leak a pending entry in Http2ClientChannelHandler.streamIdPromiseMap. Co-Authored-By: Claude <noreply@anthropic.com>
sunhailin-Leo
approved these changes
Jul 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
HTTP/2 (h2/h2c) server path accumulated DATA frames in
Http2ServerChannelHandler.onDataReadwithout comparing the accumulated size againstServerTransportConfig.getPayload().The default 8 MiB payload limit (
transport.payload.max) was only applied to the HTTP/1.1 pipeline viaHttpObjectAggregator; the HTTP/2 / h2c pipelines had no equivalent limit.An unauthenticated client that can reach an h2 or h2c listening port can keep a request stream open and continuously send legal DATA frames without sending
END_STREAM. BecauseonDataReadreturns the processed byte count, flow control issuesWINDOW_UPDATE, so HTTP/2 flow control does not cap the application-layer request body. The server retains attacker data before routing / auth / deserialization / RPC invocation until the JVM heap is exhausted.AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)Reproduction (loopback only)
A stdlib Python client sending up to 80 MiB of DATA frames on a single stream (respecting
WINDOW_UPDATE, never sendingEND_STREAM) against an h2c target running with-Xmx64m -XX:+ExitOnOutOfMemoryErrorproduced:12,599,142 bytes exceeds the default 8 MiB (8,388,608) limit, confirming the HTTP/2 path did not enforce it.
Fix
Http2ServerChannelHandler: acceptmaxContentLength(fromtransport.payload.max— same source asHttpObjectAggregator). InonDataRead, beforewriteBytes, checkaccumulated + frameSize > maxContentLength; on overflow release the accumulatedByteBuf, reset the stream viawriteRstStream(PROTOCOL_ERROR), and do not callhandleRequest. Return processed bytes so flow control stays correct.msgafter the synchronoushandleRequest(this also fixes a pre-existing per-requestByteBufleak:AbstractHttpServerTasknever released the request buffer, so every h2 request leakedmsg).Http2ConnectionAdapterto release any residual buffer on stream close (connection drop /RST_STREAM/ normal end) as a backstop.Http2ChannelHandlerBuilder/Http2ServerChannelInitializer: threadmaxHttpContentLengththrough to the handler for all h2/h2c paths (TLS NPN, prior-knowledge, HTTP/1.1 upgrade).Http2ClearTextPayloadLimitTest: withpayload=1024, a valid small request is served (200, method invoked); an oversized request is reset / no 200 and the service method is not invoked.Verification
remoting-httpmain +test-integrationtest code compiles.HTTP/2 request body exceeded max payload 1024 bytes, reset stream ....Http2ClearText*,H2c*, deadline) — 9 tests, 0 failures, no regression.Compatibility
Behavior now matches the HTTP/1.1
HttpObjectAggregator(maxContentLength)path. Defaulttransport.payload.max(8 MiB) is unchanged; HTTP/2 simply enforces it too. Users can still override via-Dtransport.payload.max,META-INF/sofa-rpc/rpc-config.json, orServerConfig.setPayload(...).Operational note
Before releasing this fix, do not expose h2/h2c listening ports directly to untrusted networks; consider a fronting proxy with request-body limits and conservative JVM memory / OOM policy.
🤖 Generated with Claude Code