Fix coalesced packet processing

Make sure we correctly process all coalesced packets without modifying the collection while we're iterating on it.

This issue was found by clusterfuzz:
https://bugs.chromium.org/p/chromium/issues/detail?id=990001

I've confirmed that the new test fails with the old code and passes with the fix, and that the fuzzer no longer crashes.

gfe-relnote: fix coalesced packet processing, protected by disabled v99 flag
PiperOrigin-RevId: 261433275
Change-Id: Iea1edf70fc84873fc7fe2f05c749759b2c5a6c9b
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 0b648e2..0c33604 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -2620,15 +2620,12 @@
 
 void QuicConnection::MaybeProcessCoalescedPackets() {
   bool processed = false;
-  for (const auto& packet : coalesced_packets_) {
-    if (!connected_) {
-      return;
-    }
+  while (connected_ && !coalesced_packets_.empty()) {
+    std::unique_ptr<QuicEncryptedPacket> packet =
+        std::move(coalesced_packets_.front());
+    coalesced_packets_.pop_front();
 
-    // }
-    // while (connected_ && !coalesced_packets_.empty()) {
     QUIC_DVLOG(1) << ENDPOINT << "Processing coalesced packet";
-    // QuicEncryptedPacket* packet = coalesced_packets_.front().get();
     if (framer_.ProcessPacket(*packet)) {
       processed = true;
     } else {
@@ -2644,9 +2641,7 @@
         }
       }
     }
-    // coalesced_packets_.pop_front();
   }
-  coalesced_packets_.clear();
   if (processed) {
     MaybeProcessUndecryptablePackets();
   }
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index a3ab06f..773db66 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -8562,6 +8562,53 @@
   EXPECT_FALSE(ack_alarm->IsSet());
 }
 
+// Verify that a packet containing three coalesced packets is parsed correctly.
+TEST_P(QuicConnectionTest, CoalescedPacket) {
+  if (!QuicVersionHasLongHeaderLengths(connection_.transport_version())) {
+    // Coalesced packets can only be encoded using long header lengths.
+    return;
+  }
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_TRUE(connection_.connected());
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(3);
+  } else {
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(3);
+  }
+
+  uint64_t packet_numbers[3] = {1, 2, 3};
+  EncryptionLevel encryption_levels[3] = {
+      ENCRYPTION_INITIAL, ENCRYPTION_INITIAL, ENCRYPTION_FORWARD_SECURE};
+  char buffer[kMaxOutgoingPacketSize] = {};
+  size_t total_encrypted_length = 0;
+  for (int i = 0; i < 3; i++) {
+    QuicPacketHeader header =
+        ConstructPacketHeader(packet_numbers[i], encryption_levels[i]);
+    QuicFrames frames;
+    if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+      frames.push_back(QuicFrame(&crypto_frame_));
+    } else {
+      frames.push_back(QuicFrame(frame1_));
+    }
+    std::unique_ptr<QuicPacket> packet = ConstructPacket(header, frames);
+    peer_creator_.set_encryption_level(encryption_levels[i]);
+    size_t encrypted_length = peer_framer_.EncryptPayload(
+        encryption_levels[i], QuicPacketNumber(packet_numbers[i]), *packet,
+        buffer + total_encrypted_length,
+        sizeof(buffer) - total_encrypted_length);
+    EXPECT_GT(encrypted_length, 0u);
+    total_encrypted_length += encrypted_length;
+  }
+  connection_.ProcessUdpPacket(
+      kSelfAddress, kPeerAddress,
+      QuicReceivedPacket(buffer, total_encrypted_length, clock_.Now(), false));
+  if (connection_.GetSendAlarm()->IsSet()) {
+    connection_.GetSendAlarm()->Fire();
+  }
+
+  EXPECT_TRUE(connection_.connected());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic