Where
-Infinity
0
Severity
8.7
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Rejected reason: This CVE ID has been rejected as a duplicate.

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

A flaw was found in Netty's StompSubframeDecoder component. A remote attacker can exploit this vulnerability by sending a specially crafted STOMP frame body without its terminating null byte. This causes the decoder to allocate a ByteBuf (a buffer for bytes) that is never released, leading to a permanent memory leak. Over time, this uncontrolled memory consumption can result in a Denial of Service (DoS) for the application using the affected STOMP codec.

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

A flaw was found in the Netty STOMP codec. A remote attacker could send a specially crafted STOMP frame with a content-length header exceeding the maximum integer value. This integer truncation vulnerability could lead to an infinite decode loop, causing a Denial of Service (DoS) by exhausting memory and CPU resources.

1 / 2
Source: MITRE
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

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 )

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