Ensure QUIC crypto send buffer does not exceed its size limit
Note to reviewer(s): Please double check that the new error code looks good and that it should indeed map to FLOW_CONTROL_ERROR (wasn't sure).
Protected by FLAGS_quic_reloadable_flag_quic_bounded_crypto_send_buffer.
PiperOrigin-RevId: 451167627
diff --git a/quiche/quic/core/quic_crypto_stream.cc b/quiche/quic/core/quic_crypto_stream.cc
index 3839db3..c85b9ab 100644
--- a/quiche/quic/core/quic_crypto_stream.cc
+++ b/quiche/quic/core/quic_crypto_stream.cc
@@ -13,6 +13,7 @@
#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_error_codes.h"
#include "quiche/quic/core/quic_session.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/core/quic_utils.h"
@@ -145,17 +146,41 @@
return;
}
const bool had_buffered_data = HasBufferedCryptoFrames();
- // Append |data| to the send buffer for this encryption level.
QuicStreamSendBuffer* send_buffer =
&substreams_[QuicUtils::GetPacketNumberSpace(level)].send_buffer;
QuicStreamOffset offset = send_buffer->stream_offset();
+
+ // Ensure this data does not cause the send buffer for this encryption level
+ // to exceed its size limit.
+ if (GetQuicReloadableFlag(quic_bounded_crypto_send_buffer)) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_bounded_crypto_send_buffer);
+ QUIC_BUG_IF(quic_crypto_stream_offset_lt_bytes_written,
+ offset < send_buffer->stream_bytes_written());
+ uint64_t current_buffer_size =
+ offset - std::min(offset, send_buffer->stream_bytes_written());
+ if (current_buffer_size > 0) {
+ QUIC_CODE_COUNT(quic_received_crypto_data_with_non_empty_send_buffer);
+ if (BufferSizeLimitForLevel(level) <
+ (current_buffer_size + data.length())) {
+ QUIC_BUG(quic_crypto_send_buffer_overflow)
+ << absl::StrCat("Too much data for crypto send buffer with level: ",
+ EncryptionLevelToString(level),
+ ", current_buffer_size: ", current_buffer_size,
+ ", data length: ", data.length());
+ OnUnrecoverableError(QUIC_INTERNAL_ERROR,
+ "Too much data for crypto send buffer");
+ return;
+ }
+ }
+ }
+
+ // Append |data| to the send buffer for this encryption level.
send_buffer->SaveStreamData(data);
if (kMaxStreamLength - offset < data.length()) {
QUIC_BUG(quic_bug_10322_2) << "Writing too much crypto handshake data";
- // TODO(nharper): Switch this to an IETF QUIC error code, possibly
- // INTERNAL_ERROR?
- OnUnrecoverableError(QUIC_STREAM_LENGTH_OVERFLOW,
+ OnUnrecoverableError(QUIC_INTERNAL_ERROR,
"Writing too much crypto handshake data");
+ return;
}
if (had_buffered_data) {
// Do not try to write if there is buffered data.
@@ -228,7 +253,6 @@
QuicStream::OnStreamDataConsumed(bytes_consumed);
}
-
bool QuicCryptoStream::HasPendingCryptoRetransmission() const {
if (!QuicVersionUsesCryptoFrames(session()->transport_version())) {
return false;
diff --git a/quiche/quic/core/quic_crypto_stream_test.cc b/quiche/quic/core/quic_crypto_stream_test.cc
index ea99926..f5d1f2e 100644
--- a/quiche/quic/core/quic_crypto_stream_test.cc
+++ b/quiche/quic/core/quic_crypto_stream_test.cc
@@ -13,8 +13,10 @@
#include "quiche/quic/core/crypto/crypto_handshake.h"
#include "quiche/quic/core/crypto/crypto_protocol.h"
#include "quiche/quic/core/crypto/null_encrypter.h"
+#include "quiche/quic/core/quic_error_codes.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_expect_bug.h"
#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"
@@ -652,6 +654,50 @@
}
}
+TEST_F(QuicCryptoStreamTest, WriteCryptoDataExceedsSendBufferLimit) {
+ if (!QuicVersionUsesCryptoFrames(connection_->transport_version())) {
+ return;
+ }
+ EXPECT_EQ(ENCRYPTION_INITIAL, connection_->encryption_level());
+ int32_t buffer_limit = GetQuicFlag(FLAGS_quic_max_buffered_crypto_bytes);
+
+ // Write data larger than the buffer limit, when there is no existing data in
+ // the buffer. Data is sent rather than closing the connection.
+ EXPECT_FALSE(stream_->HasBufferedCryptoFrames());
+ int32_t over_limit = buffer_limit + 1;
+ EXPECT_CALL(*connection_, SendCryptoData(ENCRYPTION_INITIAL, over_limit, 0))
+ // All the data is sent, no resulting buffer.
+ .WillOnce(Return(over_limit));
+ std::string large_data(over_limit, 'a');
+ stream_->WriteCryptoData(ENCRYPTION_INITIAL, large_data);
+
+ // Write data to the buffer up to the limit. One byte gets sent.
+ EXPECT_FALSE(stream_->HasBufferedCryptoFrames());
+ EXPECT_CALL(*connection_,
+ SendCryptoData(ENCRYPTION_INITIAL, buffer_limit, over_limit))
+ .WillOnce(Return(1));
+ std::string data(buffer_limit, 'a');
+ stream_->WriteCryptoData(ENCRYPTION_INITIAL, data);
+ EXPECT_TRUE(stream_->HasBufferedCryptoFrames());
+
+ // Write another byte that is not sent (due to there already being data in the
+ // buffer); send buffer is now full.
+ EXPECT_CALL(*connection_, SendCryptoData(_, _, _)).Times(0);
+ std::string data2(1, 'a');
+ stream_->WriteCryptoData(ENCRYPTION_INITIAL, data2);
+ EXPECT_TRUE(stream_->HasBufferedCryptoFrames());
+
+ // Writing an additional byte to the send buffer closes the connection.
+ if (GetQuicReloadableFlag(quic_bounded_crypto_send_buffer)) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_bounded_crypto_send_buffer);
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INTERNAL_ERROR, _, _));
+ EXPECT_QUIC_BUG(
+ stream_->WriteCryptoData(ENCRYPTION_INITIAL, data2),
+ "Too much data for crypto send buffer with level: ENCRYPTION_INITIAL, "
+ "current_buffer_size: 16384, data length: 1");
+ }
+}
+
TEST_F(QuicCryptoStreamTest, WriteBufferedCryptoFrames) {
if (!QuicVersionUsesCryptoFrames(connection_->transport_version())) {
return;
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index ddb7948..c4f9f91 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -31,6 +31,8 @@
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_allow_client_enabled_bbr_v2, true)
// If true, close read side but not write side in QuicSpdyStream::OnStreamReset().
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_on_stream_reset, true)
+// If true, close the connection if a crypto send buffer exceeds its size limit.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bounded_crypto_send_buffer, false)
// If true, consider original connection ID as active before handshake completes.
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_consider_original_connection_id_as_active_pre_handshake, false)
// If true, consolidate more logic into SetRetransmissionAlarm to ensure the logic is applied consistently.