Fedora Account System
Red Hat Associate
Red Hat Customer
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