Skip to content

PROXY Protocol v2 header generator emits "skipped" TLVs, causing 65 KB attacker-controlled spillover into the upstream application stream

Moderate
nezdolik published GHSA-wh36-hm39-mm3r Jun 23, 2026

Package

github.com/envoyproxy/envoy (Envoy Proxy)

Affected versions

>= v1.34, <1.39

Patched versions

1.35.13 1.36.9 1.37.5 1.38.3

Description

Summary

PROXY Protocol v2 header generator emits TLVs beyond the maximum length of 65535 bytes, causing a mismatch between bytes written and the length field in the header. This can result in smuggled bytes on the upstream request.

Details

In source/extensions/common/proxy_protocol/proxy_protocol_header.cc,
generateV2Header() declares a vector named final_tlvs (line 118),
populates it with TLVs that fit within the 65535-byte limit (line
175), but then the emit loop at line 193 iterates combined_tlv_vector
instead — including the TLVs that were "skipped". The PP header's
length field advertises the filtered size; the actual buffer contains
the unfiltered set.

I built a unit test against the actual generateV2Header()
(proxy_protocol_header_test, --config=local-asan, main @ ccb42a3).
With two small custom_tlvs plus one 65520-byte passthrough TLV, the
warning log fires at proxy_protocol_header.cc:170 ("Skipping TLV type
10 because adding it would exceed the 65535 limit"), the header
advertises 38 bytes, and the buffer contains 65,561 bytes. The
65,523-byte spillover begins with the "skipped" TLV's header (0a ff
f0) followed by its value — in my test, "\r\nGET /admin
HTTP/1.1\r\nHost: internal\r\n\r\n".

The upstream's PROXY-protocol parser will read 38 bytes per the header
and hand the rest to the application. Those bytes were inside a
downstream TLV that the listener filter consumed before any L7 filter
ran — they were never seen by the HTTP connection manager, RBAC,
ext_authz, JWT, or any other filter.

The reachable configuration is the documented AWS pattern: listener
proxy_protocol with pass_through_tlvs, plus cluster
upstream_proxy_protocol V2 with pass_through_tlvs and at least one
added_tlvs entry. The added_tlvs are what push the total over 65535.

The bug was introduced in PR #37591 (commit 90e9932, Feb 2025). The
existing tests for the over-limit case
(GeneratesV2WithTLVExceedingLengthLimit at
proxy_protocol_header_test.cc:209, and
GeneratesV2WithCustomTLVExceedingLengthLimit at :247) only assert
EXPECT_LOG_CONTAINS for the warning — they never inspect the buffer.

The fix is one variable name: line 193 should iterate final_tlvs.

Impact

Attacker-controlled bytes can be written directly to the beginning of the upstream connection data stream before any Envoy-controlled HTTP request.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Adjacent
Attack complexity
Low
Privileges required
High
User interaction
None
Scope
Changed
Confidentiality
None
Integrity
Low
Availability
Low

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:C/C:N/I:L/A:L

CVE ID

CVE-2026-47692

Weaknesses

No CWEs

Credits