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.
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.