Signal error in HttpDecoder on empty PUSH_PROMISE frame. Currently on an empty, invalid PUSH_PROMISE frame HttpDecoder transitions from STATE_READING_FRAME_LENGTH directly to STATE_FINISH_PARSING, skipping STATE_READING_FRAME_PAYLOAD, which results in calling Visitor::OnPushPromiseFrameEnd() without calling Visitor::OnPushPromiseFrameStart(). This is wrong and can cause QuicSpdyStream to crash. This was caught by ClusterFuzz at https://crbug.com/1001823. Also add tests for other empty frames, and sanity DCHECKs in QuicSpdyStream. gfe-relnote: n/a, change to QUIC v99-only code. Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99. PiperOrigin-RevId: 270386637 Change-Id: I0c1944d1df300136d27367679e3128dd45e9bfd3
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc index c9519fe..e1fa36e 100644 --- a/quic/core/http/http_decoder.cc +++ b/quic/core/http/http_decoder.cc
@@ -153,6 +153,10 @@ continue_processing = visitor_->OnSettingsFrameStart(header_length); break; case static_cast<uint64_t>(HttpFrameType::PUSH_PROMISE): + if (current_frame_length_ == 0) { + RaiseError(QUIC_INVALID_FRAME_DATA, "Corrupt PUSH_PROMISE frame."); + return false; + } break; case static_cast<uint64_t>(HttpFrameType::GOAWAY): break;
diff --git a/quic/core/http/http_decoder_test.cc b/quic/core/http/http_decoder_test.cc index 6847ecb..3abc8be 100644 --- a/quic/core/http/http_decoder_test.cc +++ b/quic/core/http/http_decoder_test.cc
@@ -919,7 +919,78 @@ EXPECT_EQ(test_data.error_message, decoder.error_detail()); } } -} // namespace test +} + +TEST_F(HttpDecoderTest, EmptyCancelPushFrame) { + std::string input = QuicTextUtils::HexDecode( + "03" // type (CANCEL_PUSH) + "00"); // frame length + + EXPECT_CALL(visitor_, OnError(&decoder_)); + EXPECT_EQ(input.size(), ProcessInput(input)); + EXPECT_EQ(QUIC_INVALID_FRAME_DATA, decoder_.error()); + EXPECT_EQ("Unable to read push_id", decoder_.error_detail()); +} + +TEST_F(HttpDecoderTest, EmptySettingsFrame) { + std::string input = QuicTextUtils::HexDecode( + "04" // type (SETTINGS) + "00"); // frame length + + EXPECT_CALL(visitor_, OnSettingsFrameStart(2)); + + SettingsFrame empty_frame; + EXPECT_CALL(visitor_, OnSettingsFrame(empty_frame)); + + EXPECT_EQ(input.size(), ProcessInput(input)); + EXPECT_EQ(QUIC_NO_ERROR, decoder_.error()); + EXPECT_EQ("", decoder_.error_detail()); +} + +// Regression test for https://crbug.com/1001823. +TEST_F(HttpDecoderTest, EmptyPushPromiseFrame) { + std::string input = QuicTextUtils::HexDecode( + "05" // type (PUSH_PROMISE) + "00"); // frame length + + EXPECT_CALL(visitor_, OnError(&decoder_)); + EXPECT_EQ(input.size(), ProcessInput(input)); + EXPECT_EQ(QUIC_INVALID_FRAME_DATA, decoder_.error()); + EXPECT_EQ("Corrupt PUSH_PROMISE frame.", decoder_.error_detail()); +} + +TEST_F(HttpDecoderTest, EmptyGoAwayFrame) { + std::string input = QuicTextUtils::HexDecode( + "07" // type (GOAWAY) + "00"); // frame length + + EXPECT_CALL(visitor_, OnError(&decoder_)); + EXPECT_EQ(input.size(), ProcessInput(input)); + EXPECT_EQ(QUIC_INVALID_FRAME_DATA, decoder_.error()); + EXPECT_EQ("Unable to read GOAWAY stream_id", decoder_.error_detail()); +} + +TEST_F(HttpDecoderTest, EmptyMaxPushIdFrame) { + std::string input = QuicTextUtils::HexDecode( + "0d" // type (MAX_PUSH_ID) + "00"); // frame length + + EXPECT_CALL(visitor_, OnError(&decoder_)); + EXPECT_EQ(input.size(), ProcessInput(input)); + EXPECT_EQ(QUIC_INVALID_FRAME_DATA, decoder_.error()); + EXPECT_EQ("Unable to read push_id", decoder_.error_detail()); +} + +TEST_F(HttpDecoderTest, EmptyDuplicatePushFrame) { + std::string input = QuicTextUtils::HexDecode( + "0e" // type (DUPLICATE_PUSH) + "00"); // frame length + + EXPECT_CALL(visitor_, OnError(&decoder_)); + EXPECT_EQ(input.size(), ProcessInput(input)); + EXPECT_EQ(QUIC_INVALID_FRAME_DATA, decoder_.error()); + EXPECT_EQ("Unable to read push_id", decoder_.error_detail()); +} } // namespace test
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc index a407d5c..a089345 100644 --- a/quic/core/http/quic_spdy_stream.cc +++ b/quic/core/http/quic_spdy_stream.cc
@@ -127,7 +127,7 @@ bool OnPushPromiseFrameStart(PushId push_id, QuicByteCount header_length, QuicByteCount push_id_length) override { - if (!VersionHasStreamType(stream_->transport_version())) { + if (!VersionUsesQpack(stream_->transport_version())) { CloseConnectionOnWrongFrame("Push Promise"); return false; } @@ -880,6 +880,7 @@ bool QuicSpdyStream::OnHeadersFramePayload(QuicStringPiece payload) { DCHECK(VersionUsesQpack(transport_version())); + DCHECK(qpack_decoded_headers_accumulator_); if (headers_decompressed_) { trailers_payload_length_ += payload.length(); @@ -904,6 +905,7 @@ bool QuicSpdyStream::OnHeadersFrameEnd() { DCHECK(VersionUsesQpack(transport_version())); + DCHECK(qpack_decoded_headers_accumulator_); auto result = qpack_decoded_headers_accumulator_->EndHeaderBlock();