← Back to Research Index

CVE-2026-33871: Netty's Uncapped HTTP/2 CONTINUATION Frames Leads to Denial of Service (DoS)

Vulnerability Research | March 24, 2026
CVSS 4.0 Score 8.7 (High)
CWE Identification CWE-770
CVSS Vector String 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
CVE CVE-2026-33871
Affected Component Netty DefaultHttp2FrameReader

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

  1. 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.
  2. 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.
  3. 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.

// DefaultHttp2FrameReader.java, lines 381–393
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

HeadersBlockBuilder.addFragment() — line 710 if (maxHeaderListSizeGoAway - len < headerBlock.readableBytes()) { headerSizeExceeded(); } 1-byte CONTINUATION (len = 1) 10240 - 1 < readableBytes(1)? 10239 < 1 → FALSE (initially) …triggers at frame ~10240 ✓ GOAWAY sent — limit enforced 0-byte CONTINUATION (len = 0) 10240 - 0 < readableBytes(0)? 10240 < 0 → FALSE forever …never accumulates → never fires ❌ No GOAWAY — frames accepted forever
// HeadersBlockBuilder.addFragment() — lines 710–711
// 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

Attacker Netty Server HEADERS frame (1 byte, no END_HEADERS) step 1 CONTINUATION × N (0 bytes each, 9 bytes wire) step 2 verifyContinuationFrame() per frame — no limit all other frames BLOCKED (HTTP/2 protocol) Connection monopolized and 50,000+ frames accepted with no error

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

I would like to thank the Netty team for their prompt response and effective mititgations to address the finding.