Close read side but not write side in QuicSpdyStream::OnStreamReset(). This bug was found by ClusterFuzz at https://crbug.com/1177662. Protected by FLAGS_quic_reloadable_flag_quic_fix_on_stream_reset. PiperOrigin-RevId: 357252071 Change-Id: Ie672d4d877fa44c2fd11530d6e73a600a7cfd11e
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc index aa5f7bf..83905b5 100644 --- a/quic/core/http/quic_spdy_stream.cc +++ b/quic/core/http/quic_spdy_stream.cc
@@ -725,6 +725,9 @@ } void QuicSpdyStream::OnStreamReset(const QuicRstStreamFrame& frame) { + // TODO(bnc): Merge the two blocks below when both + // quic_abort_qpack_on_stream_reset and quic_fix_on_stream_reset are + // deprecated. if (frame.error_code != QUIC_STREAM_NO_ERROR) { if (VersionUsesHttp3(transport_version()) && !fin_received() && spdy_session_->qpack_decoder()) { @@ -739,6 +742,18 @@ return; } + if (GetQuicReloadableFlag(quic_fix_on_stream_reset) && + VersionUsesHttp3(transport_version())) { + QUIC_RELOADABLE_FLAG_COUNT(quic_fix_on_stream_reset); + if (!fin_received() && spdy_session_->qpack_decoder()) { + spdy_session_->qpack_decoder()->OnStreamReset(id()); + qpack_decoded_headers_accumulator_.reset(); + } + + QuicStream::OnStreamReset(frame); + return; + } + QUIC_DVLOG(1) << ENDPOINT << "Received QUIC_STREAM_NO_ERROR, not discarding response"; set_rst_received(true);
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc index 06f779e..0e35c28 100644 --- a/quic/core/http/quic_spdy_stream_test.cc +++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -1142,10 +1142,19 @@ Initialize(kShouldProcessData); ProcessHeaders(false, headers_); + EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _)).Times(AnyNumber()); + stream_->OnStreamReset(QuicRstStreamFrame( kInvalidControlFrameId, stream_->id(), QUIC_STREAM_NO_ERROR, 0)); - EXPECT_TRUE(stream_->write_side_closed()); - EXPECT_FALSE(stream_->reading_stopped()); + + if (GetQuicReloadableFlag(quic_fix_on_stream_reset) && UsesHttp3()) { + // RESET_STREAM should close the read side but not the write side. + EXPECT_TRUE(stream_->read_side_closed()); + EXPECT_FALSE(stream_->write_side_closed()); + } else { + EXPECT_TRUE(stream_->write_side_closed()); + EXPECT_FALSE(stream_->reading_stopped()); + } } // Tests that on if the peer sends too much data (i.e. violates the flow control @@ -3029,6 +3038,39 @@ EXPECT_EQ(headers_frame_payload_length, write_headers_return_value); } +// Regression test for https://crbug.com/1177662. +// RESET_STREAM with QUIC_STREAM_NO_ERROR should not be treated in a special +// way: it should close the read side but not the write side. +TEST_P(QuicSpdyStreamTest, TwoResetStreamFrames) { + if (!UsesHttp3()) { + return; + } + + Initialize(kShouldProcessData); + + EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _)).Times(AnyNumber()); + + QuicRstStreamFrame rst_frame1(kInvalidControlFrameId, stream_->id(), + QUIC_STREAM_CANCELLED, /* bytes_written = */ 0); + stream_->OnStreamReset(rst_frame1); + EXPECT_TRUE(stream_->read_side_closed()); + EXPECT_FALSE(stream_->write_side_closed()); + + QuicRstStreamFrame rst_frame2(kInvalidControlFrameId, stream_->id(), + QUIC_STREAM_NO_ERROR, /* bytes_written = */ 0); + if (GetQuicReloadableFlag(quic_fix_on_stream_reset)) { + stream_->OnStreamReset(rst_frame2); + EXPECT_TRUE(stream_->read_side_closed()); + EXPECT_FALSE(stream_->write_side_closed()); + } else { + EXPECT_CALL(*session_, MaybeSendRstStreamFrame( + stream_->id(), QUIC_RST_ACKNOWLEDGEMENT, _)); + EXPECT_QUIC_BUG( + stream_->OnStreamReset(rst_frame2), + "The stream should've already sent RST in response to STOP_SENDING"); + } +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h index 6289379..6f809a7 100644 --- a/quic/core/quic_flags_list.h +++ b/quic/core/quic_flags_list.h
@@ -44,6 +44,7 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_version_rfcv1, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_encrypted_control_frames, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_encrypted_goaway, true) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_on_stream_reset, true) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_willing_and_able_to_write2, true) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_goaway_with_max_stream_id, true) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_parse_accept_ch_frame, true)