CVE-2026-93564: Io.netty/netty-codec-haproxy: netty: haproxy proxy-v2 nested-tlv grandchild bytebuf reference-count leak (incomplete fix of pr #16881)

Published Sep 18, 2026
·
Updated

A flaw was found in Netty. A reference-count leak in the HAProxy PROXY-v2 message decoder allows a remote, unauthenticated attacker to send specially crafted PROXY-protocol v2 headers. This can lead to memory exhaustion, resulting in a Denial of Service (DoS) for the affected system.

Other sources

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

— Red Hat

Affected Software

2 affected components
Netty netty-codec-haproxy<=4.2.17.Final
Netty netty-codec-haproxy<=4.1.137.Final

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

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

    Fixed in 4.1.137.Final
  2. Upgrade

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

    Fixed in 4.2.17.Final
  3. Configuration

    In HAProxyMessage.java, in the inner catch of readNextTLV (referenced as HAProxyMessage.java:340 in the text), replace the current error-path call to releaseTlvs(encapsulatedTlvs) with releaseDeep(encapsulatedTlvs) so the nested SSL TLV tree (grandchildren) is recursively released on exception.

    io.netty handler codec haproxy (HAProxyMessage.readNextTLV / HAProxyMessage.java error path) releaseDeep vs releaseTlvs on inner catch for encapsulatedTlvs = releaseDeep(encapsulatedTlvs)
  4. Compensating control

    Because the leak is triggered by remote, unauthenticated crafted PROXY-protocol v2 headers processed at the pipeline edge by HAProxyMessageDecoder (ByteToMessageDecoder), place PROXY-protocol listeners behind trusted upstream infrastructure / restrict access at the network boundary to reduce exposure to attackers who can send malformed headers.

Event History

Sep 18, 2026
Data Sourced
via Red Hat·10:00 AM
DescriptionSeverityAffected Software
CVE Published
via MITRE·02:13 PM
Data Sourced
via MITRE·02:13 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·03:17 PM
DescriptionSeverityWeakness

Frequently Asked Questions

1

What traffic is required to trigger the leak?

An attacker needs to send a HAProxy PROXY-v2 message containing nested TLVs in which an SSL TLV child has grandchildren, followed by a malformed sibling TLV that causes the inner error-handling path in readNextTLV to execute. The malformed sibling causes cleanup to miss the nested grandchildren.

2

Who is exposed to this issue?

Deployments using Netty's netty-codec-haproxy component to parse HAProxy PROXY-v2 messages are exposed when they process the affected nested-TLV structure. The supplied severity vector indicates the issue is reachable over the network with low attack complexity and does not require privileges or user interaction.

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