gfe-relnote: In QUIC, only do minimum validation of coalesced packets (only validate connection IDs). Protected by gfe2_reloadable_flag_quic_minimum_validation_of_coalesced_packets.
Also do not consider unprocessable coalesced packet as QUIC_PEER_BUG, because garbage content could be padding outside QUIC packet..
PiperOrigin-RevId: 297414444
Change-Id: Ifc6359e62a561746012fc718aaf24a821a7cfedf
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index 280e464..6e7e589 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -1580,23 +1580,39 @@
QuicPacketHeader coalesced_header;
if (!ProcessIetfPacketHeader(&coalesced_reader, &coalesced_header)) {
- QUIC_PEER_BUG << ENDPOINT
- << "Failed to parse received coalesced header of length "
- << coalesced_data_length << " with error: " << detailed_error_
- << ": "
- << quiche::QuicheTextUtils::HexEncode(coalesced_data,
- coalesced_data_length)
- << " previous header was " << header;
+ // Some implementations pad their INITIAL packets by sending random invalid
+ // data after the INITIAL, and that is allowed by the specification. If we
+ // fail to parse a subsequent coalesced packet, simply ignore it.
+ QUIC_DLOG(INFO) << ENDPOINT
+ << "Failed to parse received coalesced header of length "
+ << coalesced_data_length
+ << " with error: " << detailed_error_ << ": "
+ << quiche::QuicheTextUtils::HexEncode(coalesced_data,
+ coalesced_data_length)
+ << " previous header was " << header;
return;
}
- if (coalesced_header.destination_connection_id !=
- header.destination_connection_id ||
- (coalesced_header.form != IETF_QUIC_SHORT_HEADER_PACKET &&
- coalesced_header.version != header.version)) {
- QUIC_PEER_BUG << ENDPOINT << "Received mismatched coalesced header "
- << coalesced_header << " previous header was " << header;
- return;
+ if (GetQuicReloadableFlag(quic_minimum_validation_of_coalesced_packets)) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_minimum_validation_of_coalesced_packets);
+ if (coalesced_header.destination_connection_id !=
+ header.destination_connection_id) {
+ // Drop coalesced packets with mismatched connection IDs.
+ QUIC_DLOG(INFO) << ENDPOINT << "Received mismatched coalesced header "
+ << coalesced_header << " previous header was " << header;
+ QUIC_CODE_COUNT(
+ quic_received_coalesced_packets_with_mismatched_connection_id);
+ return;
+ }
+ } else {
+ if (coalesced_header.destination_connection_id !=
+ header.destination_connection_id ||
+ (coalesced_header.form != IETF_QUIC_SHORT_HEADER_PACKET &&
+ coalesced_header.version != header.version)) {
+ QUIC_PEER_BUG << ENDPOINT << "Received mismatched coalesced header "
+ << coalesced_header << " previous header was " << header;
+ return;
+ }
}
QuicEncryptedPacket coalesced_packet(coalesced_data, coalesced_data_length,
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index beac097..ec45837 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -12282,6 +12282,272 @@
CheckStreamFrameData("HELLO_WORLD?", visitor_.stream_frames_[1].get());
}
+TEST_P(QuicFramerTest, CoalescedPacketWithUdpPadding) {
+ if (!framer_.version().HasLongHeaderLengths()) {
+ return;
+ }
+ SetDecrypterLevel(ENCRYPTION_ZERO_RTT);
+ // clang-format off
+ unsigned char packet[] = {
+ // first coalesced packet
+ // public flags (long header with packet type ZERO_RTT_PROTECTED and
+ // 4-byte packet number)
+ 0xD3,
+ // version
+ QUIC_VERSION_BYTES,
+ // destination connection ID length
+ 0x08,
+ // destination connection ID
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ // source connection ID length
+ 0x00,
+ // long header packet length
+ 0x1E,
+ // packet number
+ 0x12, 0x34, 0x56, 0x78,
+ // frame type (stream frame with fin)
+ 0xFE,
+ // stream id
+ 0x02, 0x03, 0x04,
+ // offset
+ 0x3A, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54,
+ // data length
+ 0x00, 0x0c,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ // padding
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ unsigned char packet99[] = {
+ // first coalesced packet
+ // public flags (long header with packet type ZERO_RTT_PROTECTED and
+ // 4-byte packet number)
+ 0xD3,
+ // version
+ QUIC_VERSION_BYTES,
+ // destination connection ID length
+ 0x08,
+ // destination connection ID
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ // source connection ID length
+ 0x00,
+ // long header packet length
+ 0x1E,
+ // packet number
+ 0x12, 0x34, 0x56, 0x78,
+ // frame type (IETF_STREAM frame with FIN, LEN, and OFFSET bits set)
+ 0x08 | 0x01 | 0x02 | 0x04,
+ // stream id
+ kVarInt62FourBytes + 0x00, 0x02, 0x03, 0x04,
+ // offset
+ kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+ 0x32, 0x10, 0x76, 0x54,
+ // data length
+ kVarInt62OneByte + 0x0c,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ // padding
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ // clang-format on
+
+ unsigned char* p = packet;
+ size_t p_length = QUICHE_ARRAYSIZE(packet);
+ if (framer_.version().HasIetfQuicFrames()) {
+ p = packet99;
+ p_length = QUICHE_ARRAYSIZE(packet99);
+ }
+
+ QuicEncryptedPacket encrypted(AsChars(p), p_length, false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_THAT(framer_.error(), IsQuicNoError());
+ ASSERT_TRUE(visitor_.header_.get());
+
+ ASSERT_EQ(1u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0u, visitor_.ack_frames_.size());
+
+ // Stream ID should be the last 3 bytes of kStreamId.
+ EXPECT_EQ(0x00FFFFFF & kStreamId, visitor_.stream_frames_[0]->stream_id);
+ EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+ EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+ CheckStreamFrameData("hello world!", visitor_.stream_frames_[0].get());
+
+ EXPECT_EQ(visitor_.coalesced_packets_.size(), 0u);
+}
+
+TEST_P(QuicFramerTest, CoalescedPacketWithDifferentVersion) {
+ if (!QuicVersionHasLongHeaderLengths(framer_.transport_version())) {
+ return;
+ }
+ SetQuicReloadableFlag(quic_minimum_validation_of_coalesced_packets, true);
+ SetDecrypterLevel(ENCRYPTION_ZERO_RTT);
+ // clang-format off
+ unsigned char packet[] = {
+ // first coalesced packet
+ // public flags (long header with packet type ZERO_RTT_PROTECTED and
+ // 4-byte packet number)
+ 0xD3,
+ // version
+ QUIC_VERSION_BYTES,
+ // destination connection ID length
+ 0x08,
+ // destination connection ID
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ // source connection ID length
+ 0x00,
+ // long header packet length
+ 0x1E,
+ // packet number
+ 0x12, 0x34, 0x56, 0x78,
+ // frame type (stream frame with fin)
+ 0xFE,
+ // stream id
+ 0x02, 0x03, 0x04,
+ // offset
+ 0x3A, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54,
+ // data length
+ 0x00, 0x0c,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ // second coalesced packet
+ // public flags (long header with packet type ZERO_RTT_PROTECTED and
+ // 4-byte packet number)
+ 0xD3,
+ // garbage version
+ 'G', 'A', 'B', 'G',
+ // destination connection ID length
+ 0x08,
+ // destination connection ID
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ // source connection ID length
+ 0x00,
+ // long header packet length
+ 0x1E,
+ // packet number
+ 0x12, 0x34, 0x56, 0x79,
+ // frame type (stream frame with fin)
+ 0xFE,
+ // stream id
+ 0x02, 0x03, 0x04,
+ // offset
+ 0x3A, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54,
+ // data length
+ 0x00, 0x0c,
+ // data
+ 'H', 'E', 'L', 'L',
+ 'O', '_', 'W', 'O',
+ 'R', 'L', 'D', '?',
+ };
+ unsigned char packet99[] = {
+ // first coalesced packet
+ // public flags (long header with packet type ZERO_RTT_PROTECTED and
+ // 4-byte packet number)
+ 0xD3,
+ // version
+ QUIC_VERSION_BYTES,
+ // destination connection ID length
+ 0x08,
+ // destination connection ID
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ // source connection ID length
+ 0x00,
+ // long header packet length
+ 0x1E,
+ // packet number
+ 0x12, 0x34, 0x56, 0x78,
+ // frame type (IETF_STREAM frame with FIN, LEN, and OFFSET bits set)
+ 0x08 | 0x01 | 0x02 | 0x04,
+ // stream id
+ kVarInt62FourBytes + 0x00, 0x02, 0x03, 0x04,
+ // offset
+ kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+ 0x32, 0x10, 0x76, 0x54,
+ // data length
+ kVarInt62OneByte + 0x0c,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ // second coalesced packet
+ // public flags (long header with packet type ZERO_RTT_PROTECTED and
+ // 4-byte packet number)
+ 0xD3,
+ // garbage version
+ 'G', 'A', 'B', 'G',
+ // destination connection ID length
+ 0x08,
+ // destination connection ID
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ // source connection ID length
+ 0x00,
+ // long header packet length
+ 0x1E,
+ // packet number
+ 0x12, 0x34, 0x56, 0x79,
+ // frame type (IETF_STREAM frame with FIN, LEN, and OFFSET bits set)
+ 0x08 | 0x01 | 0x02 | 0x04,
+ // stream id
+ kVarInt62FourBytes + 0x00, 0x02, 0x03, 0x04,
+ // offset
+ kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+ 0x32, 0x10, 0x76, 0x54,
+ // data length
+ kVarInt62OneByte + 0x0c,
+ // data
+ 'H', 'E', 'L', 'L',
+ 'O', '_', 'W', 'O',
+ 'R', 'L', 'D', '?',
+ };
+ // clang-format on
+
+ unsigned char* p = packet;
+ size_t p_length = QUICHE_ARRAYSIZE(packet);
+ if (framer_.version().HasIetfQuicFrames()) {
+ p = packet99;
+ p_length = QUICHE_ARRAYSIZE(packet99);
+ }
+
+ QuicEncryptedPacket encrypted(AsChars(p), p_length, false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_THAT(framer_.error(), IsQuicNoError());
+ ASSERT_TRUE(visitor_.header_.get());
+
+ ASSERT_EQ(1u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0u, visitor_.ack_frames_.size());
+
+ // Stream ID should be the last 3 bytes of kStreamId.
+ EXPECT_EQ(0x00FFFFFF & kStreamId, visitor_.stream_frames_[0]->stream_id);
+ EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+ EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+ CheckStreamFrameData("hello world!", visitor_.stream_frames_[0].get());
+
+ ASSERT_EQ(visitor_.coalesced_packets_.size(), 1u);
+ EXPECT_TRUE(framer_.ProcessPacket(*visitor_.coalesced_packets_[0].get()));
+
+ EXPECT_THAT(framer_.error(), IsQuicNoError());
+ ASSERT_TRUE(visitor_.header_.get());
+
+ ASSERT_EQ(1u, visitor_.stream_frames_.size());
+ // Verify version mismatch gets reported.
+ EXPECT_EQ(1, visitor_.version_mismatch_);
+}
+
TEST_P(QuicFramerTest, UndecryptablePacketWithoutDecrypter) {
QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
@@ -12798,8 +13064,12 @@
QuicEncryptedPacket encrypted(AsChars(p), p_length, false);
- EXPECT_QUIC_PEER_BUG(EXPECT_TRUE(framer_.ProcessPacket(encrypted)),
- "Server: Received mismatched coalesced header.*");
+ if (GetQuicReloadableFlag(quic_minimum_validation_of_coalesced_packets)) {
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+ } else {
+ EXPECT_QUIC_PEER_BUG(EXPECT_TRUE(framer_.ProcessPacket(encrypted)),
+ "Server: Received mismatched coalesced header.*");
+ }
EXPECT_THAT(framer_.error(), IsQuicNoError());
ASSERT_TRUE(visitor_.header_.get());
@@ -12904,8 +13174,7 @@
QuicEncryptedPacket encrypted(AsChars(p), p_length, false);
- EXPECT_QUIC_PEER_BUG(EXPECT_TRUE(framer_.ProcessPacket(encrypted)),
- "Server: Failed to parse received coalesced header.*");
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
EXPECT_THAT(framer_.error(), IsQuicNoError());
ASSERT_TRUE(visitor_.header_.get());
@@ -12975,9 +13244,8 @@
false);
// Make sure we discard the subsequent zeroes.
- EXPECT_QUIC_PEER_BUG(EXPECT_TRUE(framer_.ProcessPacket(encrypted)),
- "Server: (Failed to parse received|Received mismatched) "
- "coalesced header.*");
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+ EXPECT_TRUE(visitor_.coalesced_packets_.empty());
}
TEST_P(QuicFramerTest, ClientReceivesInvalidVersion) {