REDHAT-BUG-2536953: High severity Netty netty-codec-haproxy vulnerability
HAProxy PROXY-v2 nested-TLV grandchild ByteBuf reference-count leak (incomplete fix of PR #16881)
A public GitHub Security Advisory (GHSA-j58c-g352-8h4p) describes the following issue:
Summary PR #16881 introduced releaseDeep(...) (recursive release of a TLV tree) and converted the flattened-list call sites to release nested PP2TYPESSL TLVs correctly. However the inner catch inside readNextTLV (HAProxyMessage.java:340) still calls the flatten-unaware releaseTlvs(encapsulatedTlvs). There, encapsulatedTlvs is a non-flattened list of a single SSL TLV's direct children, where a child may itself be an HAProxySSLTLV holding grandchildren. releaseTlvs' skip-counter (HAProxyMessage.java:277) is designed only for the flattened top-level list; on this tree-shaped list it treats a child SSL TLV's grandchild-count as a skip over the following siblings and never releases the grandchildren. When a later sibling TLV is malformed and the inner catch fires, the retained grandchild slice (e.g. an ALPN TLV under a nested SSL TLV) is leaked, keeping the underlying header ByteBuf retained.
Reachability / trust boundary Remote, unauthenticated: HAProxyMessageDecoder (a ByteToMessageDecoder) parses PROXY-protocol v2 header bytes from the peer/upstream at the pipeline edge. The decoder documents no trusted-input assumption. Reference-count leaks while parsing nested PP2 TLVs from attacker-supplied bytes are an accepted Netty vulnerability class with a direct High precedent (GHSA-h2qv-fj59-j46j).
Impact Each crafted header leaks the grandchild slice, pinning the underlying pooled buffer. Sustained malformed headers accumulate leaked/pinned memory → memory-exhaustion DoS.
Honest caveat: demonstrated impact is exactly one pinned buffer per crafted connection (refCnt 2 vs 1); the DoS is reached by repetition/flooding (no per-message amplification). PROXY-protocol listeners are conventionally fronted by trusted upstream infrastructure, which narrows the realistic attacker population — but Netty as a library makes no such trust assumption.
Fix One-line change on the error path: use the existing releaseDeep(encapsulatedTlvs) instead of releaseTlvs(encapsulatedTlvs) in the readNextTLV inner catch. It runs only on the exception path, releases each tree node exactly once (the list is only ever populated via add, never addAll of an SSL child's list, so it is strictly tree-shaped — no double-free), and cannot affect valid PROXY traffic.
Proof of Concept Minimal 52-byte PROXY-v2 header: signature + ver/cmd=0x21 + TCP4 + 12 address/port bytes, then an outer PP2TYPESSL TLV containing client+verify and a nested SSL child holding a 1-byte ALPN grandchild, followed by a malformed sibling TLV that forces the inner catch.
java / Copyright 2024 The Netty Project The Netty Project licenses this file to you under the Apache License, version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. / package io.netty.handler.codec.haproxy;
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue;
/ F001 - grandchild {@link ByteBuf} refCnt leak on the PROXY-v2 nested-SSL-TLV error path (module codec-haproxy, {@code HAProxyMessage.readNextTLV}). <p>When {@code readNextTLV} parses an SSL TLV it collects its immediate children into a NON-flattened list ({@code encapsulatedTlvs}). If a later sibling is malformed the inner catch releases that list with {@code releaseTlvs}, whose skip-counter assumes a FLATTENED list. A child that is itself an SSL TLV therefore has its grandchildren skipped and never released, leaking the grandchild's retained slice of the shared header buffer.</p> <p>Oracle: after the error-path decode throws, {@code header.refCnt()} is {@code 2} (grandchild slice still retained = leaked) on the vulnerable tree, and {@code 1} (grandchild released) once the inner catch releases recursively.</p> / public class HAProxyTLVGrandchildLeakF001Test {
/ A minimal (52-byte) PROXY-v2 header whose top-level SSL TLV encapsulates a child SSL TLV (holding one ALPN grandchild) followed by a malformed sibling SSL TLV that forces the error path. Every byte below is load-bearing; see the F001 evidence bundle. / private static byte[] malformedHeader() { return new byte[] { // -- 12-byte v2 signature (decodeHeader only skipBytes(12); contents unchecked) -- 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A, 0x21, // verCmd: version 2, PROXY command 0x11, // protFam: AFIPv4 + STREAM (TCP4) 0x00, 0x0C, // addressInfoLen = 12 (min for IPv4; does not bound the TLV region) 0x00, 0x00, 0x00, 0x00, // src addr 0.0.0.0 0x00, 0x00, 0x00, 0x00, // dst addr 0.0.0.0 0x00, 0x00, // src port 0x00, 0x00, // dst port // -- outer SSL TLV: type 0x20, len 21 -- 0x20, 0x00, 0x15, 0x00, // client 0x00, 0x00, 0x00, 0x00, // verify // ---- child SSL TLV: type 0x20, len 9 (a grandchild-holder) ---- 0x20, 0x00, 0x09, 0x00, // client 0x00, 0x00, 0x00, 0x00, // verify // ------ ALPN grandchild
[truncated]
Affected: - maven:io.netty:netty-codec-haproxy affected <= 4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-haproxy affected <= 4.1.137.Final; fixed unknown
Fixed versions: see advisory
Advisory: https://github.com/netty/netty/security/advisories/GHSA-j58c-g352-8h4p
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Patch GHSA-j58c-g352-8h4p
Event History
Frequently Asked Questions
Which deployments are exposed to this issue?
Deployments are exposed where HAProxyMessageDecoder processes remotely supplied, unauthenticated HAProxy PROXY protocol v2 messages containing TLVs. The issue is in Netty's netty-codec-haproxy component.
What does an attacker need to send to trigger the leak?
The attacker needs to send a crafted nested TLV structure in which an SSL TLV child contains grandchildren, followed by a malformed sibling TLV that causes the inner error-handling path in readNextTLV to run. An ALPN TLV beneath a nested SSL TLV is one example of retained data.
What is the practical impact of successful exploitation?
The affected error path can fail to release grandchild TLV slices, retaining the underlying PROXY protocol header ByteBuf. Repeated crafted messages can therefore cause memory to remain retained.