Implement restrictions at https://tools.ietf.org/html/draft-ietf-quic-transport-27#section-7.3.1. This prepares for 0-rtt on the client side. This change should be no-op for current deployment in gfe because config is currently only set once. gfe-relnote: unused code. not protected. PiperOrigin-RevId: 308861599 Change-Id: I0c8182f3d11330f40024dca76de9fed6d383f8d1
diff --git a/quic/core/quic_flow_controller.h b/quic/core/quic_flow_controller.h index e627c8c..29c3c02 100644 --- a/quic/core/quic_flow_controller.h +++ b/quic/core/quic_flow_controller.h
@@ -91,6 +91,8 @@ QuicByteCount bytes_consumed() const { return bytes_consumed_; } + QuicStreamOffset send_window_offset() const { return send_window_offset_; } + QuicStreamOffset highest_received_byte_offset() const { return highest_received_byte_offset_; }
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc index 4862d50..3524242 100644 --- a/quic/core/quic_session.cc +++ b/quic/core/quic_session.cc
@@ -1119,6 +1119,18 @@ QUIC_DVLOG(1) << ENDPOINT << "Setting Bidirectional outgoing_max_streams_ to " << max_streams; + if (perspective_ == Perspective::IS_CLIENT && + max_streams < + v99_streamid_manager_.max_outgoing_bidirectional_streams()) { + connection_->CloseConnection( + QUIC_MAX_STREAMS_ERROR, + quiche::QuicheStrCat( + "new bidirectional limit ", max_streams, + " decreases the current limit: ", + v99_streamid_manager_.max_outgoing_bidirectional_streams()), + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return; + } if (v99_streamid_manager_.MaybeAllowNewOutgoingBidirectionalStreams( max_streams)) { OnCanCreateNewOutgoingStream(/*unidirectional = */ false); @@ -1128,6 +1140,8 @@ if (config_.HasReceivedMaxUnidirectionalStreams()) { max_streams = config_.ReceivedMaxUnidirectionalStreams(); } + // TODO(b/153726130): remove this check and + // num_expected_unidirectional_static_streams_. if (max_streams < num_expected_unidirectional_static_streams_) { QUIC_DLOG(ERROR) << "Received unidirectional stream limit of " << max_streams << " < " @@ -1138,6 +1152,18 @@ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); return; } + if (perspective_ == Perspective::IS_CLIENT && + max_streams < + v99_streamid_manager_.max_outgoing_unidirectional_streams()) { + connection_->CloseConnection( + QUIC_MAX_STREAMS_ERROR, + quiche::QuicheStrCat( + "new unidirectional limit ", max_streams, + " decreases the current limit: ", + v99_streamid_manager_.max_outgoing_unidirectional_streams()), + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return; + } QUIC_DVLOG(1) << ENDPOINT << "Setting Unidirectional outgoing_max_streams_ to " << max_streams; @@ -1310,6 +1336,7 @@ } void QuicSession::OnNewStreamFlowControlWindow(QuicStreamOffset new_window) { + DCHECK_EQ(connection_->version().handshake_protocol, PROTOCOL_QUIC_CRYPTO); QUIC_DVLOG(1) << ENDPOINT << "OnNewStreamFlowControlWindow " << new_window; if (new_window < kMinimumFlowControlSendWindow && !connection_->version().AllowsLowFlowControlLimits()) { @@ -1326,19 +1353,22 @@ for (auto const& kv : stream_map_) { QUIC_DVLOG(1) << ENDPOINT << "Informing stream " << kv.first << " of new stream flow control window " << new_window; - kv.second->UpdateSendWindowOffset(new_window); + if (!kv.second->ConfigSendWindowOffset(new_window)) { + return; + } } if (!QuicVersionUsesCryptoFrames(transport_version())) { QUIC_DVLOG(1) << ENDPOINT << "Informing crypto stream of new stream flow control window " << new_window; - GetMutableCryptoStream()->UpdateSendWindowOffset(new_window); + GetMutableCryptoStream()->ConfigSendWindowOffset(new_window); } } void QuicSession::OnNewStreamUnidirectionalFlowControlWindow( QuicStreamOffset new_window) { + DCHECK_EQ(connection_->version().handshake_protocol, PROTOCOL_TLS1_3); QUIC_DVLOG(1) << ENDPOINT << "OnNewStreamUnidirectionalFlowControlWindow " << new_window; // Inform all existing outgoing unidirectional streams about the new window. @@ -1353,12 +1383,15 @@ } QUIC_DVLOG(1) << ENDPOINT << "Informing unidirectional stream " << id << " of new stream flow control window " << new_window; - kv.second->UpdateSendWindowOffset(new_window); + if (!kv.second->ConfigSendWindowOffset(new_window)) { + return; + } } } void QuicSession::OnNewStreamOutgoingBidirectionalFlowControlWindow( QuicStreamOffset new_window) { + DCHECK_EQ(connection_->version().handshake_protocol, PROTOCOL_TLS1_3); QUIC_DVLOG(1) << ENDPOINT << "OnNewStreamOutgoingBidirectionalFlowControlWindow " << new_window; @@ -1374,12 +1407,15 @@ } QUIC_DVLOG(1) << ENDPOINT << "Informing outgoing bidirectional stream " << id << " of new stream flow control window " << new_window; - kv.second->UpdateSendWindowOffset(new_window); + if (!kv.second->ConfigSendWindowOffset(new_window)) { + return; + } } } void QuicSession::OnNewStreamIncomingBidirectionalFlowControlWindow( QuicStreamOffset new_window) { + DCHECK_EQ(connection_->version().handshake_protocol, PROTOCOL_TLS1_3); QUIC_DVLOG(1) << ENDPOINT << "OnNewStreamIncomingBidirectionalFlowControlWindow " << new_window; @@ -1395,19 +1431,36 @@ } QUIC_DVLOG(1) << ENDPOINT << "Informing incoming bidirectional stream " << id << " of new stream flow control window " << new_window; - kv.second->UpdateSendWindowOffset(new_window); + if (!kv.second->ConfigSendWindowOffset(new_window)) { + return; + } } } void QuicSession::OnNewSessionFlowControlWindow(QuicStreamOffset new_window) { QUIC_DVLOG(1) << ENDPOINT << "OnNewSessionFlowControlWindow " << new_window; - if (new_window < kMinimumFlowControlSendWindow && - !connection_->version().AllowsLowFlowControlLimits()) { + bool close_connection = false; + if (!connection_->version().AllowsLowFlowControlLimits()) { + if (new_window < kMinimumFlowControlSendWindow) { + close_connection = true; + QUIC_LOG_FIRST_N(ERROR, 1) + << "Peer sent us an invalid session flow control send window: " + << new_window << ", below default: " << kMinimumFlowControlSendWindow; + } + } else if (perspective_ == Perspective::IS_CLIENT && + new_window < flow_controller_.send_window_offset()) { + // The client receives a lower limit than remembered, violating + // https://tools.ietf.org/html/draft-ietf-quic-transport-27#section-7.3.1 QUIC_LOG_FIRST_N(ERROR, 1) << "Peer sent us an invalid session flow control send window: " - << new_window << ", below default: " << kMinimumFlowControlSendWindow; + << new_window + << ", below current: " << flow_controller_.send_window_offset(); + close_connection = true; + } + if (close_connection) { connection_->CloseConnection( - QUIC_FLOW_CONTROL_INVALID_WINDOW, "New connection window too low", + QUIC_FLOW_CONTROL_INVALID_WINDOW, + quiche::QuicheStrCat("New connection window too low: ", new_window), ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); return; }
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc index 372fa07..db24eca 100644 --- a/quic/core/quic_session_test.cc +++ b/quic/core/quic_session_test.cc
@@ -1776,21 +1776,6 @@ session_.OnConfigNegotiated(); } -TEST_P(QuicSessionTestServer, InvalidSessionFlowControlWindowInHandshake) { - // Test that receipt of an invalid (< default) session flow control window - // from the peer results in the connection being torn down. - const uint32_t kInvalidWindow = kMinimumFlowControlSendWindow - 1; - QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(session_.config(), - kInvalidWindow); - if (!connection_->version().AllowsLowFlowControlLimits()) { - EXPECT_CALL(*connection_, - CloseConnection(QUIC_FLOW_CONTROL_INVALID_WINDOW, _, _)); - } else { - EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0); - } - session_.OnConfigNegotiated(); -} - // Test negotiation of custom server initial flow control window. TEST_P(QuicSessionTestServer, CustomFlowControlWindow) { QuicTagVector copt; @@ -2053,6 +2038,57 @@ &session_, GetNthClientInitiatedBidirectionalId(1))); } +TEST_P(QuicSessionTestClient, InvalidSessionFlowControlWindowInHandshake) { + // Test that receipt of an invalid (< default for gQUIC, < current for TLS) + // session flow control window from the peer results in the connection being + // torn down. + const uint32_t kInvalidWindow = kMinimumFlowControlSendWindow - 1; + QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(session_.config(), + kInvalidWindow); + EXPECT_CALL(*connection_, + CloseConnection(QUIC_FLOW_CONTROL_INVALID_WINDOW, _, _)); + session_.OnConfigNegotiated(); +} + +TEST_P(QuicSessionTestClient, InvalidBidiStreamLimitInHandshake) { + // IETF QUIC only feature. + if (!VersionHasIetfQuicFrames(transport_version())) { + return; + } + QuicConfigPeer::SetReceivedMaxBidirectionalStreams( + session_.config(), kDefaultMaxStreamsPerConnection - 1); + EXPECT_CALL(*connection_, CloseConnection(QUIC_MAX_STREAMS_ERROR, _, _)); + session_.OnConfigNegotiated(); +} + +TEST_P(QuicSessionTestClient, InvalidUniStreamLimitInHandshake) { + // IETF QUIC only feature. + if (!VersionHasIetfQuicFrames(transport_version())) { + return; + } + QuicConfigPeer::SetReceivedMaxUnidirectionalStreams( + session_.config(), kDefaultMaxStreamsPerConnection - 1); + EXPECT_CALL(*connection_, CloseConnection(QUIC_MAX_STREAMS_ERROR, _, _)); + session_.OnConfigNegotiated(); +} + +TEST_P(QuicSessionTestClient, InvalidStreamFlowControlWindowInHandshake) { + // IETF QUIC only feature. + if (!VersionHasIetfQuicFrames(transport_version())) { + return; + } + session_.CreateOutgoingBidirectionalStream(); + session_.CreateOutgoingBidirectionalStream(); + QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesOutgoingBidirectional( + session_.config(), kMinimumFlowControlSendWindow - 1); + + EXPECT_CALL(*connection_, CloseConnection(_, _, _)) + .WillOnce( + Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); + EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _)); + session_.OnConfigNegotiated(); +} + TEST_P(QuicSessionTestClient, OnMaxStreamFrame) { if (!VersionUsesHttp3(transport_version())) { return;
diff --git a/quic/core/quic_stream.cc b/quic/core/quic_stream.cc index 15212f9..c43d914 100644 --- a/quic/core/quic_stream.cc +++ b/quic/core/quic_stream.cc
@@ -904,11 +904,22 @@ } } -void QuicStream::UpdateSendWindowOffset(QuicStreamOffset new_window) { - if (flow_controller_->UpdateSendWindowOffset(new_window)) { +bool QuicStream::ConfigSendWindowOffset(QuicStreamOffset new_offset) { + if (perspective_ == Perspective::IS_CLIENT && + session()->version().AllowsLowFlowControlLimits() && + new_offset < flow_controller_->send_window_offset()) { + OnUnrecoverableError( + QUIC_FLOW_CONTROL_INVALID_WINDOW, + quiche::QuicheStrCat("New stream max data ", new_offset, + " decreases current limit: ", + flow_controller_->send_window_offset())); + return false; + } + if (flow_controller_->UpdateSendWindowOffset(new_offset)) { // Let session unblock this stream. session_->MarkConnectionLevelWriteBlocked(id_); } + return true; } void QuicStream::AddRandomPaddingAfterFin() {
diff --git a/quic/core/quic_stream.h b/quic/core/quic_stream.h index 90dac69..cc7ce4d 100644 --- a/quic/core/quic_stream.h +++ b/quic/core/quic_stream.h
@@ -244,9 +244,8 @@ // Returns true if the highest offset did increase. bool MaybeIncreaseHighestReceivedOffset(QuicStreamOffset new_offset); - // Updates the flow controller's send window offset and calls OnCanWrite if - // it was blocked before. - void UpdateSendWindowOffset(QuicStreamOffset new_offset); + // Set the flow controller's send window offset from session config. + bool ConfigSendWindowOffset(QuicStreamOffset new_offset); // Returns true if the stream has received either a RST_STREAM or a FIN - // either of which gives a definitive number of bytes which the peer has