Fix crasher in QpackProgressiveDecoder destructor if QpackHeaderTable is already destroyed. gfe-relnote: n/a, change to QUIC v99-only code. Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99. PiperOrigin-RevId: 272892449 Change-Id: Ib79e6c1227e4f4cc2fc2480c22126f647dbac95f
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc index 3e0a6f8..931924f 100644 --- a/quic/core/http/quic_spdy_session_test.cc +++ b/quic/core/http/quic_spdy_session_test.cc
@@ -2290,6 +2290,39 @@ session_.qpack_decoder()->OnInsertWithoutNameReference("foo", "bar"); } +// Regression test for https://crbug.com/1011294. +TEST_P(QuicSpdySessionTestServer, SessionDestroyedWhileHeaderDecodingBlocked) { + if (!VersionUsesQpack(transport_version())) { + return; + } + + session_.qpack_decoder()->OnSetDynamicTableCapacity(1024); + + QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0); + TestStream* stream = session_.CreateIncomingStream(stream_id); + + // HEADERS frame referencing first dynamic table entry. + std::string headers_payload = QuicTextUtils::HexDecode("020080"); + std::unique_ptr<char[]> headers_buffer; + HttpEncoder encoder; + QuicByteCount headers_frame_header_length = + encoder.SerializeHeadersFrameHeader(headers_payload.length(), + &headers_buffer); + QuicStringPiece headers_frame_header(headers_buffer.get(), + headers_frame_header_length); + std::string headers = QuicStrCat(headers_frame_header, headers_payload); + stream->OnStreamFrame(QuicStreamFrame(stream_id, false, 0, headers)); + + // Decoding is blocked because dynamic table entry has not been received yet. + EXPECT_FALSE(stream->headers_decompressed()); + + // |session_| gets destoyed. That destroys QpackDecoder, a member of + // QuicSpdySession (derived class), which destroys QpackHeaderTable. + // Then |*stream|, owned by QuicSession (base class) get destroyed, which + // destroys QpackProgessiveDecoder, a registered Observer of QpackHeaderTable. + // This must not cause a crash. +} + TEST_P(QuicSpdySessionTestClient, ResetAfterInvalidIncomingStreamType) { if (!VersionHasStreamType(transport_version())) { return;
diff --git a/quic/core/qpack/qpack_header_table.cc b/quic/core/qpack/qpack_header_table.cc index 7274ce4..ff9be9a 100644 --- a/quic/core/qpack/qpack_header_table.cc +++ b/quic/core/qpack/qpack_header_table.cc
@@ -19,7 +19,11 @@ max_entries_(0), dropped_entry_count_(0) {} -QpackHeaderTable::~QpackHeaderTable() = default; +QpackHeaderTable::~QpackHeaderTable() { + for (auto& entry : observers_) { + entry.second->Cancel(); + } +} const QpackEntry* QpackHeaderTable::LookupEntry(bool is_static, uint64_t index) const {
diff --git a/quic/core/qpack/qpack_header_table.h b/quic/core/qpack/qpack_header_table.h index 34dfd1c..78d85f6 100644 --- a/quic/core/qpack/qpack_header_table.h +++ b/quic/core/qpack/qpack_header_table.h
@@ -48,6 +48,10 @@ // registered with. After this call the Observer automatically gets // deregistered. virtual void OnInsertCountReachedThreshold() = 0; + + // Called when QpackHeaderTable is destroyed to let the Observer know that + // it must not call UnregisterObserver(). + virtual void Cancel() = 0; }; QpackHeaderTable(); @@ -99,9 +103,11 @@ // gets unregistered. Each observer must only be registered at most once. void RegisterObserver(uint64_t required_insert_count, Observer* observer); - // Unregister previously registered observer. Must be called before an - // observer still waiting for notification is destroyed. Must be called with - // the same |required_insert_count| value that |observer| was registered with. + // Unregister previously registered observer. Must be called with the same + // |required_insert_count| value that |observer| was registered with. Must be + // called before an observer still waiting for notification is destroyed, + // unless QpackHeaderTable already called Observer::Cancel(), in which case + // this method must not be called. void UnregisterObserver(uint64_t required_insert_count, Observer* observer); // Used on request streams to encode and decode Required Insert Count.
diff --git a/quic/core/qpack/qpack_header_table_test.cc b/quic/core/qpack/qpack_header_table_test.cc index 907c2db..96e4d0a 100644 --- a/quic/core/qpack/qpack_header_table_test.cc +++ b/quic/core/qpack/qpack_header_table_test.cc
@@ -23,6 +23,7 @@ ~MockObserver() override = default; MOCK_METHOD0(OnInsertCountReachedThreshold, void()); + MOCK_METHOD0(Cancel, void()); }; class QpackHeaderTableTest : public QuicTest { @@ -519,6 +520,15 @@ EXPECT_EQ(4u, table.draining_index(1.0)); } +TEST_F(QpackHeaderTableTest, Cancel) { + StrictMock<MockObserver> observer; + auto table = std::make_unique<QpackHeaderTable>(); + table->RegisterObserver(1, &observer); + + EXPECT_CALL(observer, Cancel); + table.reset(); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/core/qpack/qpack_progressive_decoder.cc b/quic/core/qpack/qpack_progressive_decoder.cc index 7037f15..34eb2b3 100644 --- a/quic/core/qpack/qpack_progressive_decoder.cc +++ b/quic/core/qpack/qpack_progressive_decoder.cc
@@ -36,10 +36,11 @@ prefix_decoded_(false), blocked_(false), decoding_(true), - error_detected_(false) {} + error_detected_(false), + cancelled_(false) {} QpackProgressiveDecoder::~QpackProgressiveDecoder() { - if (blocked_) { + if (blocked_ && !cancelled_) { header_table_->UnregisterObserver(required_insert_count_, this); } } @@ -133,6 +134,10 @@ } } +void QpackProgressiveDecoder::Cancel() { + cancelled_ = true; +} + bool QpackProgressiveDecoder::DoIndexedHeaderFieldInstruction() { if (!instruction_decoder_.s_bit()) { uint64_t absolute_index;
diff --git a/quic/core/qpack/qpack_progressive_decoder.h b/quic/core/qpack/qpack_progressive_decoder.h index dbb4093..91b1e8c 100644 --- a/quic/core/qpack/qpack_progressive_decoder.h +++ b/quic/core/qpack/qpack_progressive_decoder.h
@@ -89,6 +89,7 @@ // QpackHeaderTable::Observer implementation. void OnInsertCountReachedThreshold() override; + void Cancel() override; private: bool DoIndexedHeaderFieldInstruction(); @@ -145,6 +146,10 @@ // True if a decoding error has been detected. bool error_detected_; + + // True if QpackHeaderTable has been destroyed + // while decoding is still blocked. + bool cancelled_; }; } // namespace quic