REDHAT-BUG-2536898: High severity Netty netty-codec-stomp vulnerability

Published Sep 18, 2026
·
Updated

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

Affected Software

2 affected components
Netty netty-codec-stomp<=4.1.137.Final
maven/io.netty/netty-codec-stomp>=4.2.0.Final<=4.2.17.Final

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade maven:io.netty:netty-codec-stomp to a version that resolves this vulnerability.

    Fixed in 4.2.17.Final
  2. Upgrade

    Upgrade maven:io.netty:netty-codec-stomp to a version that resolves this vulnerability.

    Fixed in 4.1.137.Final
  3. Compensating control

    Use a network-level control to limit inbound STOMP connections (e.g., firewall/ACL/WAF/rate limiting on the STOMP port) to reduce the attack’s ability to rapidly create and terminate connections that trigger the allocator buffer leak.

Event History

Sep 18, 2026
Data Sourced
via Red Hat·07:28 AM
DescriptionSeverityAffected Software

Frequently Asked Questions

1

Who can exploit this issue?

A remote peer that can establish a connection to a service using StompSubframeDecoder can trigger the leak. The peer sends a complete, well-formed frame body with a declared content length, then omits the required terminating NUL byte.

2

What is the operational impact?

One allocator buffer is leaked per malicious connection. With the default pooled allocator, the memory is not returned to the pool on disconnect or reclaimed by garbage collection, so leaked memory accumulates for the lifetime of the process.

3

Does disconnecting the offending client recover the leaked memory?

No. The decoder retains the buffer after the connection that created it ends, and the leaked buffer is not reclaimed by garbage collection when the default pooled allocator is used.

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