Internal change

PiperOrigin-RevId: 444933854
diff --git a/quiche/quic/core/http/quic_spdy_session_test.cc b/quiche/quic/core/http/quic_spdy_session_test.cc
index 06b2959..283fdf5 100644
--- a/quiche/quic/core/http/quic_spdy_session_test.cc
+++ b/quiche/quic/core/http/quic_spdy_session_test.cc
@@ -195,6 +195,10 @@
   void OnConnectionClosed(QuicErrorCode /*error*/,
                           ConnectionCloseSource /*source*/) override {}
   SSL* GetSsl() const override { return nullptr; }
+  bool IsCryptoFrameExpectedForEncryptionLevel(
+      EncryptionLevel level) const override {
+    return level != ENCRYPTION_ZERO_RTT;
+  }
 
   bool ExportKeyingMaterial(absl::string_view /*label*/,
                             absl::string_view /*context*/,
diff --git a/quiche/quic/core/http/quic_spdy_stream_test.cc b/quiche/quic/core/http/quic_spdy_stream_test.cc
index 33b0588..281f359 100644
--- a/quiche/quic/core/http/quic_spdy_stream_test.cc
+++ b/quiche/quic/core/http/quic_spdy_stream_test.cc
@@ -187,6 +187,11 @@
 
   SSL* GetSsl() const override { return nullptr; }
 
+  bool IsCryptoFrameExpectedForEncryptionLevel(
+      EncryptionLevel level) const override {
+    return level != ENCRYPTION_ZERO_RTT;
+  }
+
  private:
   using QuicCryptoStream::session;
 
diff --git a/quiche/quic/core/quic_crypto_client_handshaker.cc b/quiche/quic/core/quic_crypto_client_handshaker.cc
index aa963d5..6a9fa6c 100644
--- a/quiche/quic/core/quic_crypto_client_handshaker.cc
+++ b/quiche/quic/core/quic_crypto_client_handshaker.cc
@@ -144,6 +144,11 @@
   return encryption_established_;
 }
 
+bool QuicCryptoClientHandshaker::IsCryptoFrameExpectedForEncryptionLevel(
+    EncryptionLevel /*level*/) const {
+  return true;
+}
+
 bool QuicCryptoClientHandshaker::one_rtt_keys_available() const {
   return one_rtt_keys_available_;
 }
diff --git a/quiche/quic/core/quic_crypto_client_handshaker.h b/quiche/quic/core/quic_crypto_client_handshaker.h
index 45d8016..9c19f89 100644
--- a/quiche/quic/core/quic_crypto_client_handshaker.h
+++ b/quiche/quic/core/quic_crypto_client_handshaker.h
@@ -43,6 +43,8 @@
   int num_scup_messages_received() const override;
   std::string chlo_hash() const override;
   bool encryption_established() const override;
+  bool IsCryptoFrameExpectedForEncryptionLevel(
+      EncryptionLevel level) const override;
   bool one_rtt_keys_available() const override;
   const QuicCryptoNegotiatedParameters& crypto_negotiated_params()
       const override;
diff --git a/quiche/quic/core/quic_crypto_client_stream.cc b/quiche/quic/core/quic_crypto_client_stream.cc
index 60d49ed..a241ecb 100644
--- a/quiche/quic/core/quic_crypto_client_stream.cc
+++ b/quiche/quic/core/quic_crypto_client_stream.cc
@@ -164,4 +164,9 @@
   return tls_handshaker_ == nullptr ? nullptr : tls_handshaker_->ssl();
 }
 
+bool QuicCryptoClientStream::IsCryptoFrameExpectedForEncryptionLevel(
+    EncryptionLevel level) const {
+  return handshaker_->IsCryptoFrameExpectedForEncryptionLevel(level);
+}
+
 }  // namespace quic
diff --git a/quiche/quic/core/quic_crypto_client_stream.h b/quiche/quic/core/quic_crypto_client_stream.h
index 96abfec..d3da4db 100644
--- a/quiche/quic/core/quic_crypto_client_stream.h
+++ b/quiche/quic/core/quic_crypto_client_stream.h
@@ -169,6 +169,10 @@
     // for the connection.
     virtual bool encryption_established() const = 0;
 
+    // Returns true if receiving CRYPTO_FRAME at encryption `level` is expected.
+    virtual bool IsCryptoFrameExpectedForEncryptionLevel(
+        EncryptionLevel level) const = 0;
+
     // Returns true once 1RTT keys are available.
     virtual bool one_rtt_keys_available() const = 0;
 
@@ -283,6 +287,8 @@
       override;
   std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override;
   SSL* GetSsl() const override;
+  bool IsCryptoFrameExpectedForEncryptionLevel(
+      EncryptionLevel level) const override;
   bool ExportKeyingMaterial(absl::string_view label, absl::string_view context,
                             size_t result_len, std::string* result) override;
   std::string chlo_hash() const;
diff --git a/quiche/quic/core/quic_crypto_server_stream.cc b/quiche/quic/core/quic_crypto_server_stream.cc
index cca4890..11a5728 100644
--- a/quiche/quic/core/quic_crypto_server_stream.cc
+++ b/quiche/quic/core/quic_crypto_server_stream.cc
@@ -12,6 +12,7 @@
 #include "openssl/sha.h"
 #include "quiche/quic/platform/api/quic_flag_utils.h"
 #include "quiche/quic/platform/api/quic_testvalue.h"
+#include "quiche/common/platform/api/quiche_logging.h"
 #include "quiche/common/quiche_text_utils.h"
 
 namespace quic {
@@ -526,4 +527,9 @@
 
 SSL* QuicCryptoServerStream::GetSsl() const { return nullptr; }
 
+bool QuicCryptoServerStream::IsCryptoFrameExpectedForEncryptionLevel(
+    EncryptionLevel /*level*/) const {
+  return true;
+}
+
 }  // namespace quic
diff --git a/quiche/quic/core/quic_crypto_server_stream.h b/quiche/quic/core/quic_crypto_server_stream.h
index 2f7fb46..5bdfb54 100644
--- a/quiche/quic/core/quic_crypto_server_stream.h
+++ b/quiche/quic/core/quic_crypto_server_stream.h
@@ -72,6 +72,8 @@
       override;
   std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override;
   SSL* GetSsl() const override;
+  bool IsCryptoFrameExpectedForEncryptionLevel(
+      EncryptionLevel level) const override;
 
   // From QuicCryptoHandshaker
   void OnHandshakeMessage(const CryptoHandshakeMessage& message) override;
diff --git a/quiche/quic/core/quic_crypto_stream.cc b/quiche/quic/core/quic_crypto_stream.cc
index a669982..33eb543 100644
--- a/quiche/quic/core/quic_crypto_stream.cc
+++ b/quiche/quic/core/quic_crypto_stream.cc
@@ -6,10 +6,12 @@
 
 #include <string>
 
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "absl/types/optional.h"
 #include "quiche/quic/core/crypto/crypto_handshake.h"
 #include "quiche/quic/core/crypto/crypto_utils.h"
+#include "quiche/quic/core/frames/quic_crypto_frame.h"
 #include "quiche/quic/core/quic_connection.h"
 #include "quiche/quic/core/quic_session.h"
 #include "quiche/quic/core/quic_types.h"
@@ -74,6 +76,12 @@
               !QuicVersionUsesCryptoFrames(session()->transport_version()))
       << "Versions less than 47 shouldn't receive CRYPTO frames";
   EncryptionLevel level = session()->connection()->last_decrypted_level();
+  if (!IsCryptoFrameExpectedForEncryptionLevel(level)) {
+    OnUnrecoverableError(
+        IETF_QUIC_PROTOCOL_VIOLATION,
+        absl::StrCat("CRYPTO_FRAME is unexpectedly received at level ", level));
+    return;
+  }
   substreams_[level].sequencer.OnCryptoFrame(frame);
   EncryptionLevel frame_level = level;
   if (substreams_[level].sequencer.NumBytesBuffered() >
diff --git a/quiche/quic/core/quic_crypto_stream.h b/quiche/quic/core/quic_crypto_stream.h
index 9e9b870..be61a82 100644
--- a/quiche/quic/core/quic_crypto_stream.h
+++ b/quiche/quic/core/quic_crypto_stream.h
@@ -240,6 +240,11 @@
     return &substreams_[level].sequencer;
   }
 
+  // Called by OnCryptoFrame to check if a CRYPTO frame is received at an
+  // expected `level`.
+  virtual bool IsCryptoFrameExpectedForEncryptionLevel(
+      EncryptionLevel level) const = 0;
+
  private:
   // Data sent and received in CRYPTO frames is sent at multiple encryption
   // levels. Some of the state for the single logical crypto stream is split
diff --git a/quiche/quic/core/quic_crypto_stream_test.cc b/quiche/quic/core/quic_crypto_stream_test.cc
index 6df4983..7e27d77 100644
--- a/quiche/quic/core/quic_crypto_stream_test.cc
+++ b/quiche/quic/core/quic_crypto_stream_test.cc
@@ -17,6 +17,7 @@
 #include "quiche/quic/platform/api/quic_socket_address.h"
 #include "quiche/quic/platform/api/quic_test.h"
 #include "quiche/quic/test_tools/crypto_test_utils.h"
+#include "quiche/quic/test_tools/quic_connection_peer.h"
 #include "quiche/quic/test_tools/quic_stream_peer.h"
 #include "quiche/quic/test_tools/quic_test_utils.h"
 
@@ -95,6 +96,11 @@
   }
   SSL* GetSsl() const override { return nullptr; }
 
+  bool IsCryptoFrameExpectedForEncryptionLevel(
+      EncryptionLevel level) const override {
+    return level != ENCRYPTION_ZERO_RTT;
+  }
+
  private:
   quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_;
   std::vector<CryptoHandshakeMessage> messages_;
@@ -674,6 +680,20 @@
       QuicCryptoFrame(ENCRYPTION_INITIAL, offset, large_frame));
 }
 
+TEST_F(QuicCryptoStreamTest, CloseConnectionWithZeroRttCryptoFrame) {
+  if (!QuicVersionUsesCryptoFrames(connection_->transport_version())) {
+    return;
+  }
+
+  EXPECT_CALL(*connection_,
+              CloseConnection(IETF_QUIC_PROTOCOL_VIOLATION, _, _));
+
+  test::QuicConnectionPeer::SetLastDecryptedLevel(connection_,
+                                                  ENCRYPTION_ZERO_RTT);
+  QuicStreamOffset offset = 1;
+  stream_->OnCryptoFrame(QuicCryptoFrame(ENCRYPTION_ZERO_RTT, offset, "data"));
+}
+
 TEST_F(QuicCryptoStreamTest, RetransmitCryptoFramesAndPartialWrite) {
   if (!QuicVersionUsesCryptoFrames(connection_->transport_version())) {
     return;
diff --git a/quiche/quic/core/quic_framer.cc b/quiche/quic/core/quic_framer.cc
index b0f5018..2f8c5c1 100644
--- a/quiche/quic/core/quic_framer.cc
+++ b/quiche/quic/core/quic_framer.cc
@@ -3121,6 +3121,8 @@
 // static
 bool QuicFramer::IsIetfFrameTypeExpectedForEncryptionLevel(
     uint64_t frame_type, EncryptionLevel level) {
+  // IETF_CRYPTO is allowed for any level here and is separately checked in
+  // QuicCryptoStream::OnCryptoFrame.
   switch (level) {
     case ENCRYPTION_INITIAL:
     case ENCRYPTION_HANDSHAKE:
@@ -3132,7 +3134,7 @@
     case ENCRYPTION_ZERO_RTT:
       return !(frame_type == IETF_ACK || frame_type == IETF_ACK_ECN ||
                frame_type == IETF_ACK_RECEIVE_TIMESTAMPS ||
-               frame_type == IETF_CRYPTO || frame_type == IETF_HANDSHAKE_DONE ||
+               frame_type == IETF_HANDSHAKE_DONE ||
                frame_type == IETF_NEW_TOKEN ||
                frame_type == IETF_PATH_RESPONSE ||
                frame_type == IETF_RETIRE_CONNECTION_ID);
diff --git a/quiche/quic/core/quic_session_test.cc b/quiche/quic/core/quic_session_test.cc
index ddda101..ccaa29d 100644
--- a/quiche/quic/core/quic_session_test.cc
+++ b/quiche/quic/core/quic_session_test.cc
@@ -181,6 +181,11 @@
 
   SSL* GetSsl() const override { return nullptr; }
 
+  bool IsCryptoFrameExpectedForEncryptionLevel(
+      EncryptionLevel level) const override {
+    return level != ENCRYPTION_ZERO_RTT;
+  }
+
  private:
   using QuicCryptoStream::session;
 
diff --git a/quiche/quic/core/tls_client_handshaker.cc b/quiche/quic/core/tls_client_handshaker.cc
index 90a9d74..fccdb20 100644
--- a/quiche/quic/core/tls_client_handshaker.cc
+++ b/quiche/quic/core/tls_client_handshaker.cc
@@ -357,6 +357,11 @@
   return encryption_established_;
 }
 
+bool TlsClientHandshaker::IsCryptoFrameExpectedForEncryptionLevel(
+    EncryptionLevel level) const {
+  return level != ENCRYPTION_ZERO_RTT;
+}
+
 bool TlsClientHandshaker::one_rtt_keys_available() const {
   return state_ >= HANDSHAKE_COMPLETE;
 }
diff --git a/quiche/quic/core/tls_client_handshaker.h b/quiche/quic/core/tls_client_handshaker.h
index 15727e1..d3c6312 100644
--- a/quiche/quic/core/tls_client_handshaker.h
+++ b/quiche/quic/core/tls_client_handshaker.h
@@ -54,6 +54,8 @@
 
   // From QuicCryptoClientStream::HandshakerInterface and TlsHandshaker
   bool encryption_established() const override;
+  bool IsCryptoFrameExpectedForEncryptionLevel(
+      EncryptionLevel level) const override;
   bool one_rtt_keys_available() const override;
   const QuicCryptoNegotiatedParameters& crypto_negotiated_params()
       const override;
diff --git a/quiche/quic/core/tls_server_handshaker.cc b/quiche/quic/core/tls_server_handshaker.cc
index ce63767..7a17acc 100644
--- a/quiche/quic/core/tls_server_handshaker.cc
+++ b/quiche/quic/core/tls_server_handshaker.cc
@@ -1134,4 +1134,9 @@
 
 SSL* TlsServerHandshaker::GetSsl() const { return ssl(); }
 
+bool TlsServerHandshaker::IsCryptoFrameExpectedForEncryptionLevel(
+    EncryptionLevel level) const {
+  return level != ENCRYPTION_ZERO_RTT;
+}
+
 }  // namespace quic
diff --git a/quiche/quic/core/tls_server_handshaker.h b/quiche/quic/core/tls_server_handshaker.h
index 6385eda..fde73c6 100644
--- a/quiche/quic/core/tls_server_handshaker.h
+++ b/quiche/quic/core/tls_server_handshaker.h
@@ -71,6 +71,8 @@
   bool ExportKeyingMaterial(absl::string_view label, absl::string_view context,
                             size_t result_len, std::string* result) override;
   SSL* GetSsl() const override;
+  bool IsCryptoFrameExpectedForEncryptionLevel(
+      EncryptionLevel level) const override;
 
   // From QuicCryptoServerStreamBase and TlsHandshaker
   ssl_early_data_reason_t EarlyDataReason() const override;
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h
index e074008..2a3b2b0 100644
--- a/quiche/quic/test_tools/quic_test_utils.h
+++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -855,6 +855,10 @@
     return false;
   }
   SSL* GetSsl() const override { return nullptr; }
+  bool IsCryptoFrameExpectedForEncryptionLevel(
+      quic::EncryptionLevel level) const override {
+    return level != ENCRYPTION_ZERO_RTT;
+  }
 
  private:
   quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_;