Close QUIC+TLS connection if decrypted a 0-RTT packet with higher packet number than 1-RTT packet number Protected by FLAGS_quic_reloadable_flag_quic_close_connection_on_0rtt_packet_number_higher_than_1rtt. PiperOrigin-RevId: 346385500 Change-Id: Id1baf34e93dc5f1ee68e0dce7523bd1bb265111f
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc index 941bd21..5d26ab0 100644 --- a/quic/core/quic_connection_test.cc +++ b/quic/core/quic_connection_test.cc
@@ -12861,6 +12861,119 @@ retry_token); } +TEST_P(QuicConnectionTest, + ServerReceivedZeroRttWithHigherPacketNumberThanOneRttAndFlagDisabled) { + SetQuicRestartFlag(quic_server_temporarily_retain_tls_zero_rtt_keys, true); + SetQuicReloadableFlag( + quic_close_connection_on_0rtt_packet_number_higher_than_1rtt, false); + if (!connection_.version().UsesTls()) { + return; + } + + // The code that checks for this error piggybacks on some book-keeping state + // kept for key update, so enable key update for the test. + std::string error_details; + TransportParameters params; + params.key_update_not_yet_supported = false; + QuicConfig config; + EXPECT_THAT(config.ProcessTransportParameters( + params, /* is_resumption = */ false, &error_details), + IsQuicNoError()); + config.SetKeyUpdateSupportedLocally(); + QuicConfigPeer::SetNegotiated(&config, true); + QuicConfigPeer::SetReceivedOriginalConnectionId(&config, + connection_.connection_id()); + QuicConfigPeer::SetReceivedInitialSourceConnectionId( + &config, connection_.connection_id()); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_.SetFromConfig(config); + + set_perspective(Perspective::IS_SERVER); + SetDecrypter(ENCRYPTION_ZERO_RTT, + std::make_unique<NullDecrypter>(Perspective::IS_SERVER)); + + EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1); + ProcessDataPacketAtLevel(1, !kHasStopWaiting, ENCRYPTION_ZERO_RTT); + + // Finish handshake. + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + notifier_.NeuterUnencryptedData(); + connection_.NeuterUnencryptedPackets(); + connection_.OnHandshakeComplete(); + EXPECT_CALL(visitor_, GetHandshakeState()) + .WillRepeatedly(Return(HANDSHAKE_COMPLETE)); + + // Decrypt a 1-RTT packet. + EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1); + ProcessDataPacketAtLevel(2, !kHasStopWaiting, ENCRYPTION_FORWARD_SECURE); + EXPECT_TRUE(connection_.GetDiscardZeroRttDecryptionKeysAlarm()->IsSet()); + + // 0-RTT packet with higher packet number than a 1-RTT packet is invalid, but + // accepted as the + // quic_close_connection_on_0rtt_packet_number_higher_than_1rtt + // flag is disabled. + EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1); + ProcessDataPacketAtLevel(3, !kHasStopWaiting, ENCRYPTION_ZERO_RTT); + EXPECT_TRUE(connection_.connected()); +} + +TEST_P(QuicConnectionTest, + ServerReceivedZeroRttWithHigherPacketNumberThanOneRtt) { + SetQuicRestartFlag(quic_server_temporarily_retain_tls_zero_rtt_keys, true); + SetQuicReloadableFlag( + quic_close_connection_on_0rtt_packet_number_higher_than_1rtt, true); + if (!connection_.version().UsesTls()) { + return; + } + + // The code that checks for this error piggybacks on some book-keeping state + // kept for key update, so enable key update for the test. + std::string error_details; + TransportParameters params; + params.key_update_not_yet_supported = false; + QuicConfig config; + EXPECT_THAT(config.ProcessTransportParameters( + params, /* is_resumption = */ false, &error_details), + IsQuicNoError()); + config.SetKeyUpdateSupportedLocally(); + QuicConfigPeer::SetNegotiated(&config, true); + QuicConfigPeer::SetReceivedOriginalConnectionId(&config, + connection_.connection_id()); + QuicConfigPeer::SetReceivedInitialSourceConnectionId( + &config, connection_.connection_id()); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_.SetFromConfig(config); + + set_perspective(Perspective::IS_SERVER); + SetDecrypter(ENCRYPTION_ZERO_RTT, + std::make_unique<NullDecrypter>(Perspective::IS_SERVER)); + + EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1); + ProcessDataPacketAtLevel(1, !kHasStopWaiting, ENCRYPTION_ZERO_RTT); + + // Finish handshake. + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + notifier_.NeuterUnencryptedData(); + connection_.NeuterUnencryptedPackets(); + connection_.OnHandshakeComplete(); + EXPECT_CALL(visitor_, GetHandshakeState()) + .WillRepeatedly(Return(HANDSHAKE_COMPLETE)); + + // Decrypt a 1-RTT packet. + EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1); + ProcessDataPacketAtLevel(2, !kHasStopWaiting, ENCRYPTION_FORWARD_SECURE); + EXPECT_TRUE(connection_.GetDiscardZeroRttDecryptionKeysAlarm()->IsSet()); + + // 0-RTT packet with higher packet number than a 1-RTT packet is invalid and + // should cause the connection to be closed. + EXPECT_CALL(visitor_, BeforeConnectionCloseSent()); + EXPECT_CALL(visitor_, OnConnectionClosed(_, _)); + ProcessDataPacketAtLevel(3, !kHasStopWaiting, ENCRYPTION_ZERO_RTT); + EXPECT_FALSE(connection_.connected()); + TestConnectionCloseQuicErrorCode( + QUIC_INVALID_0RTT_PACKET_NUMBER_OUT_OF_ORDER); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc index 65db041..1aa9760 100644 --- a/quic/core/quic_error_codes.cc +++ b/quic/core/quic_error_codes.cc
@@ -108,6 +108,7 @@ RETURN_STRING_LITERAL(QUIC_TOO_MANY_OPEN_STREAMS); RETURN_STRING_LITERAL(QUIC_PUBLIC_RESET); RETURN_STRING_LITERAL(QUIC_INVALID_VERSION); + RETURN_STRING_LITERAL(QUIC_INVALID_0RTT_PACKET_NUMBER_OUT_OF_ORDER); RETURN_STRING_LITERAL(QUIC_INVALID_HEADER_ID); RETURN_STRING_LITERAL(QUIC_INVALID_NEGOTIATED_VALUE); RETURN_STRING_LITERAL(QUIC_DECOMPRESSION_FAILURE); @@ -384,6 +385,8 @@ return {true, static_cast<uint64_t>(INTERNAL_ERROR)}; case QUIC_INVALID_VERSION: return {true, static_cast<uint64_t>(PROTOCOL_VIOLATION)}; + case QUIC_INVALID_0RTT_PACKET_NUMBER_OUT_OF_ORDER: + return {true, static_cast<uint64_t>(PROTOCOL_VIOLATION)}; case QUIC_INVALID_HEADER_ID: return {true, static_cast<uint64_t>(INTERNAL_ERROR)}; case QUIC_INVALID_NEGOTIATED_VALUE:
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h index a1cd7a6..20ab087 100644 --- a/quic/core/quic_error_codes.h +++ b/quic/core/quic_error_codes.h
@@ -563,8 +563,11 @@ // timeout. QUIC_MAX_AGE_TIMEOUT = 191, + // Decrypted a 0-RTT packet with a higher packet number than a 1-RTT packet. + QUIC_INVALID_0RTT_PACKET_NUMBER_OUT_OF_ORDER = 192, + // No error. Used as bound while iterating. - QUIC_LAST_ERROR = 192, + QUIC_LAST_ERROR = 193, }; // QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC, // or a varint62 when doing IETF QUIC. Ensure that its value does not exceed
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h index 4f435a6..d5f27e6 100644 --- a/quic/core/quic_flags_list.h +++ b/quic/core/quic_flags_list.h
@@ -15,6 +15,7 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_fewer_startup_round_trips, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_use_bytes_delivered, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_can_send_ack_frequency, true) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_close_connection_on_0rtt_packet_number_higher_than_1rtt, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_bursts, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_enable_5rto_blackhole_detection2, true)
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc index 6b29cae..7803b59 100644 --- a/quic/core/quic_framer.cc +++ b/quic/core/quic_framer.cc
@@ -4781,6 +4781,22 @@ decrypted_buffer, decrypted_length, buffer_length); if (success) { visitor_->OnDecryptedPacket(udp_packet_length, level); + if (GetQuicReloadableFlag( + quic_close_connection_on_0rtt_packet_number_higher_than_1rtt)) { + QUIC_RELOADABLE_FLAG_COUNT( + quic_close_connection_on_0rtt_packet_number_higher_than_1rtt); + if (level == ENCRYPTION_ZERO_RTT && + current_key_phase_first_received_packet_number_.IsInitialized() && + header.packet_number > + current_key_phase_first_received_packet_number_) { + set_detailed_error(absl::StrCat( + "Decrypted a 0-RTT packet with a packet number ", + header.packet_number.ToString(), + " which is higher than a 1-RTT packet number ", + current_key_phase_first_received_packet_number_.ToString())); + return RaiseError(QUIC_INVALID_0RTT_PACKET_NUMBER_OUT_OF_ORDER); + } + } *decrypted_level = level; potential_peer_key_update_attempt_count_ = 0; if (attempt_key_update) {