CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
Summary
A Denial-of-Service vulnerability was discovered in Netty's DefaultHttp2FrameReader. The reader accepts an unbounded number of HTTP/2 CONTINUATION frames per header block, with no frame count limit enforced. The existing mitigations which included a byte-level cap via maxHeaderListSizeGoAway could be bypassed by sending 0-byte CONTINUATION frames, which are valid per RFC 9113. An unauthenticated remote user can exploit this to monopolize server threads with minimal bandwidth, causing CPU exhaustion and connection-level Denial-of-Service against any default-configured Netty HTTP/2 server.
Impact
- CPU Exhaustion: Each CONTINUATION frame triggers full parse-and-verify logic server-side at ~389ns per frame, creating a significant CPU cost asymmetry relative to the 9-byte attacker wire cost.
- Connection Monopolization: The HTTP/2 protocol requires all other frames on a connection to be blocked while a CONTINUATION sequence is pending. A single open CONTINUATION chain freezes the entire connection.
- Thread Pool Starvation: Attacking multiple connections simultaneously exhausts the server's I/O thread pool, causing complete service unavailability.
Attack Prerequisites
None. This vulnerability affects the default Netty HTTP/2 server configuration. Any user capable of opening an HTTP/2 connection over TLS (h2) or cleartext (h2c) can trigger the issue.
The attack leverages 0-byte CONTINUATION frames, which are explicitly valid under RFC 9113 Section 6.10. The server cannot reject them at the protocol layer without violating spec compliance.
Description
In HTTP/2, a HEADERS frame may be followed by any number of CONTINUATION frames to carry the remainder of a compressed header block. Netty's DefaultHttp2FrameReader enforces no limit on how many such frames it will accept before the sequence is closed with an END_HEADERS flag. The verification method responsible for validating CONTINUATION frames, verifyContinuationFrame(), checks only stream association and state, but never the frame count.
private void verifyContinuationFrame() throws Http2Exception {
verifyAssociatedWithAStream();
if (headersContinuation == null) {
throw connectionError(PROTOCOL_ERROR, "...");
}
if (streamId != headersContinuation.getStreamId()) {
throw connectionError(PROTOCOL_ERROR, "...");
}
// NO frame count limit — frames accepted indefinitely
}
Netty does implement an additional safeguard: maxHeaderListSizeGoAway which tracks accumulated header bytes and issues a GOAWAY once the threshold is exceeded. However, this protection assumes each frame contributes a nonzero byte payload. Sending 0-byte CONTINUATION frames renders it inert.
Byte Limit Bypass: Why 0-Byte Frames Evade maxHeaderListSizeGoAway
// When len = 0, this condition is NEVER true regardless of frame count
if (headersDecoder.configuration().maxHeaderListSizeGoAway() - len <
headerBlock.readableBytes()) {
headerSizeExceeded(); // 10240 - 0 < 0 → always FALSE
}
The result is a two-layer failure. The frame count check is absent entirely from verifyContinuationFrame(), and the fallback, i.e. the byte accumulator can be bypassed by sending frames with no payload.
Attack Flow: Connection Monopolization via 0-Byte CONTINUATION
Conclusion
This vulnerability underscores a critical class of HTTP/2 implementation flaws where specification compliance (accepting valid empty frames) conflicts with resource management. The Netty maintainers have since resolved this by introducing a configurable limit on the number of CONTINUATION frames allowed per header block. All users are strongly encouraged to upgrade to Netty 4.1.132.Final, 4.2.11.Final or later to address this issue.
References
- GitHub Advisory: GHSA-w9fj-cfpg-grvv
- CVE.org Record: CVE-2026-33871
- NVD Record: CVE-2026-33871
- CERT/CC VU#421644 HTTP/2 CONTINUATION FRAMES
- RFC 9113 Section 6.10 — CONTINUATION frames (no minimum payload size requirement)
I would like to thank the Netty team for their prompt response and effective mititgations to address the finding.