From 948a6a9fa623b0528f8c43b566b41c1245e32f8e Mon Sep 17 00:00:00 2001 From: jeffhuang <3848358+parasol-aser@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:43:16 +0000 Subject: [PATCH] Cap proxy protocol TLV vector entry counts --- .../proxy_protocol_header_parser.cpp | 6 ++ .../transport/proxy_protocol_header_parser.h | 7 ++ .../proxy_protocol_header_parser_test.cpp | 65 ++++++++++++++++++- 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/mongo/transport/proxy_protocol_header_parser.cpp b/src/mongo/transport/proxy_protocol_header_parser.cpp index 9cc1f58320a75..d991f6df8e83a 100644 --- a/src/mongo/transport/proxy_protocol_header_parser.cpp +++ b/src/mongo/transport/proxy_protocol_header_parser.cpp @@ -222,6 +222,7 @@ void parseSubTLVVectors(StringData buffer, boost::optional& sslT void parseTLVVectors(StringData buffer, std::vector& tlvs, boost::optional& sslTlvs) { + size_t tlvCount = 0; while (buffer.size()) { static constexpr size_t kTLVHeaderSize = 3; uassert(ErrorCodes::FailedToParse, @@ -230,6 +231,11 @@ void parseTLVVectors(StringData buffer, buffer.size(), kTLVHeaderSize), buffer.size() > kTLVHeaderSize); + uassert(ErrorCodes::FailedToParse, + fmt::format("Proxy Protocol Version 2 TLV entry count exceeds {}", + kMaxProxyProtocolTLVEntriesPerVector), + tlvCount < kMaxProxyProtocolTLVEntriesPerVector); + ++tlvCount; auto type = extract(buffer); uassert(ErrorCodes::FailedToParse, diff --git a/src/mongo/transport/proxy_protocol_header_parser.h b/src/mongo/transport/proxy_protocol_header_parser.h index b97d05cbaa44c..071189556348e 100644 --- a/src/mongo/transport/proxy_protocol_header_parser.h +++ b/src/mongo/transport/proxy_protocol_header_parser.h @@ -78,6 +78,13 @@ constexpr uint8_t kProxyProtocolTypeAuthority = 0x02; */ constexpr uint8_t kProxyProtocolSSLTlvType = 0x20; +/** + * Maximum TLV entries parsed from a single proxy protocol TLV vector, including SSL sub-TLV + * vectors. This bounds per-connection allocations on the proxy unix-socket path while remaining + * well above expected production metadata usage. + */ +constexpr size_t kMaxProxyProtocolTLVEntriesPerVector = 64; + /** * MongoDB custom PP2 TLV type as per MongoDB Proxy Protocol Technical Design document. * The kProxyProtocolSSLTlvDN TLV is used to indicate the distinguished name (DN) from the client's diff --git a/src/mongo/transport/proxy_protocol_header_parser_test.cpp b/src/mongo/transport/proxy_protocol_header_parser_test.cpp index 426f3f8394b99..056276eca146a 100644 --- a/src/mongo/transport/proxy_protocol_header_parser_test.cpp +++ b/src/mongo/transport/proxy_protocol_header_parser_test.cpp @@ -631,6 +631,19 @@ std::string buildTLV(uint8_t type, const std::string& data) { return tlv; } +std::string buildRepeatedTLVs(size_t count, uint8_t type = 0xE0, StringData data = StringData()) { + std::string tlvs; + tlvs.reserve(count * (3 + data.size())); + for (size_t i = 0; i < count; ++i) { + tlvs += buildTLV(type, std::string{data}); + } + return tlvs; +} + +std::string buildSSLTLVPayload(uint8_t clientFlags, + uint32_t verify, + const std::string& subTlvData = ""); + class ProxyProtocolParameterizedTestFixture : public testing::TestWithParam {}; INSTANTIATE_TEST_SUITE_P(ProxyProtocolHeaderParser, @@ -680,6 +693,56 @@ TEST_P(ProxyProtocolParameterizedTestFixture, TLVParsingManyTLVs) { ASSERT_EQ(result->tlvs[4].data, "custom"); } +TEST(ProxyProtocolHeaderParser, TLVParsingAcceptsBoundedEntryCounts) { + for (const auto tlvCount : + {size_t{1}, size_t{16}, kMaxProxyProtocolTLVEntriesPerVector}) { + auto result = parseWithTLV(AddressFamily::TCP4, buildRepeatedTLVs(tlvCount, 0xE0, "x"_sd)); + ASSERT_TRUE(result) << tlvCount; + ASSERT_TRUE(result->endpoints) << tlvCount; + ASSERT_EQ(result->tlvs.size(), tlvCount) << tlvCount; + ASSERT_FALSE(result->sslTlvs) << tlvCount; + } +} + +TEST(ProxyProtocolHeaderParser, TLVParsingRejectsExcessiveEntryCounts) { + ASSERT_THROWS_WITH_CHECK( + parseWithTLV(AddressFamily::TCP4, + buildRepeatedTLVs(kMaxProxyProtocolTLVEntriesPerVector + 1, 0xE0, "x"_sd)), + DBException, + [](const DBException& ex) { + ASSERT_THAT(ex.toStatus(), + StatusIs(Eq(ErrorCodes::FailedToParse), + ContainsRegex("TLV entry count exceeds"))); + }); +} + +TEST(ProxyProtocolHeaderParser, TLVParsingRejectsDenseEntriesAfterSingleSslTLV) { + auto sslTlv = buildTLV(kProxyProtocolSSLTlvType, buildSSLTLVPayload(0x07, 0)); + auto denseTlvs = buildRepeatedTLVs(kMaxProxyProtocolTLVEntriesPerVector, 0xE0, "x"_sd); + + ASSERT_THROWS_WITH_CHECK(parseWithTLV(AddressFamily::TCP4, sslTlv + denseTlvs), + DBException, + [](const DBException& ex) { + ASSERT_THAT(ex.toStatus(), + StatusIs(Eq(ErrorCodes::FailedToParse), + ContainsRegex("TLV entry count exceeds"))); + }); +} + +TEST(ProxyProtocolHeaderParser, ParseSubTLVVectorsRejectsExcessiveEntryCounts) { + auto subTlvs = + buildRepeatedTLVs(kMaxProxyProtocolTLVEntriesPerVector + 1, 0x21, "x"_sd); + auto sslTlv = buildTLV(kProxyProtocolSSLTlvType, buildSSLTLVPayload(0x07, 0, subTlvs)); + + ASSERT_THROWS_WITH_CHECK(parseWithTLV(AddressFamily::TCP4, sslTlv), + DBException, + [](const DBException& ex) { + ASSERT_THAT(ex.toStatus(), + StatusIs(Eq(ErrorCodes::FailedToParse), + ContainsRegex("TLV entry count exceeds"))); + }); +} + TEST_P(ProxyProtocolParameterizedTestFixture, TLVParsingFails) { auto type = GetParam(); @@ -736,7 +799,7 @@ TEST_P(ProxyProtocolParameterizedTestFixture, UnixProxySocketWithV1ProtocolIsRej // The payload format is: clientFlags (1 byte) + verify (4 bytes big-endian) + optional sub-TLVs. std::string buildSSLTLVPayload(uint8_t clientFlags, uint32_t verify, - const std::string& subTlvData = "") { + const std::string& subTlvData) { std::string payload; payload += static_cast(clientFlags); // verify in big-endian.