Close connection on incorrect order of HEADERS and DATA frames.
While the HTTP/3 spec draft does not require this yet, this is the sane thing to
do, and my proposal to change the spec has some support:
https://github.com/quicwg/base-drafts/issues/2858.
This fixes fuzzer-found bug https://crbug.com/978733.
NO_BUG=Bug is https://crbug.com/978733 on Chromium bugtracker.
gfe-relnote: n/a, change in QUIC v99-only code.
PiperOrigin-RevId: 257169271
Change-Id: I10ed66e671f0dc03d6f24694d4c53ba3691e9c51
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index ff81ebf..b2fbc70 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -37,6 +37,7 @@
using testing::AtLeast;
using testing::ElementsAre;
using testing::Invoke;
+using testing::InvokeWithoutArgs;
using testing::Pair;
using testing::Return;
using testing::StrictMock;
@@ -2122,6 +2123,133 @@
stream_->OnStreamFrame(frame);
}
+// Close connection if a DATA frame is received before a HEADERS frame.
+TEST_P(QuicSpdyStreamTest, DataBeforeHeaders) {
+ if (!VersionUsesQpack(GetParam().transport_version)) {
+ return;
+ }
+
+ Initialize(kShouldProcessData);
+
+ // Closing the connection is mocked out in tests. Instead, simply stop
+ // reading data at the stream level to prevent QuicSpdyStream from blowing up.
+ // TODO(b/124216424): Change error code to HTTP_UNEXPECTED_FRAME.
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Unexpected DATA frame received.",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET))
+ .WillOnce(InvokeWithoutArgs([this]() { stream_->StopReading(); }));
+
+ std::string data = DataFrame(kDataFramePayload);
+ stream_->OnStreamFrame(QuicStreamFrame(stream_->id(), false, 0, data));
+}
+
+// Close connection if a HEADERS frame is received after the trailing HEADERS.
+TEST_P(QuicSpdyStreamTest, TrailersAfterTrailers) {
+ if (!VersionUsesQpack(GetParam().transport_version)) {
+ return;
+ }
+
+ Initialize(kShouldProcessData);
+
+ // Receive and consume headers, with single header field "foo: bar".
+ std::string headers =
+ HeadersFrame(QuicTextUtils::HexDecode("00002a94e703626172"));
+ QuicStreamOffset offset = 0;
+ stream_->OnStreamFrame(
+ QuicStreamFrame(stream_->id(), false, offset, headers));
+ offset += headers.size();
+
+ EXPECT_THAT(stream_->header_list(), ElementsAre(Pair("foo", "bar")));
+ stream_->ConsumeHeaderList();
+
+ // Receive data. It is consumed by TestStream.
+ std::string data = DataFrame(kDataFramePayload);
+ stream_->OnStreamFrame(QuicStreamFrame(stream_->id(), false, offset, data));
+ offset += data.size();
+
+ EXPECT_EQ(kDataFramePayload, stream_->data());
+
+ // Receive and consume trailers, with single header field
+ // "custom-key: custom-value".
+ std::string trailers1 = HeadersFrame(
+ QuicTextUtils::HexDecode("00002f0125a849e95ba97d7f8925a849e95bb8e8b4bf"));
+ stream_->OnStreamFrame(
+ QuicStreamFrame(stream_->id(), false, offset, trailers1));
+ offset += trailers1.size();
+
+ EXPECT_TRUE(stream_->trailers_decompressed());
+ EXPECT_THAT(stream_->received_trailers(),
+ ElementsAre(Pair("custom-key", "custom-value")));
+
+ // Closing the connection is mocked out in tests. Instead, simply stop
+ // reading data at the stream level to prevent QuicSpdyStream from blowing up.
+ // TODO(b/124216424): Change error code to HTTP_UNEXPECTED_FRAME.
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "HEADERS frame received after trailing HEADERS.",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET))
+ .WillOnce(InvokeWithoutArgs([this]() { stream_->StopReading(); }));
+
+ // Receive another HEADERS frame, with no header fields.
+ std::string trailers2 = HeadersFrame(QuicTextUtils::HexDecode("0000"));
+ stream_->OnStreamFrame(
+ QuicStreamFrame(stream_->id(), false, offset, trailers2));
+}
+
+// Regression test for https://crbug.com/978733.
+// Close connection if a DATA frame is received after the trailing HEADERS.
+TEST_P(QuicSpdyStreamTest, DataAfterTrailers) {
+ if (!VersionUsesQpack(GetParam().transport_version)) {
+ return;
+ }
+
+ Initialize(kShouldProcessData);
+
+ // Receive and consume headers, with single header field "foo: bar".
+ std::string headers =
+ HeadersFrame(QuicTextUtils::HexDecode("00002a94e703626172"));
+ QuicStreamOffset offset = 0;
+ stream_->OnStreamFrame(
+ QuicStreamFrame(stream_->id(), false, offset, headers));
+ offset += headers.size();
+
+ EXPECT_THAT(stream_->header_list(), ElementsAre(Pair("foo", "bar")));
+ stream_->ConsumeHeaderList();
+
+ // Receive data. It is consumed by TestStream.
+ std::string data1 = DataFrame(kDataFramePayload);
+ stream_->OnStreamFrame(QuicStreamFrame(stream_->id(), false, offset, data1));
+ offset += data1.size();
+ EXPECT_EQ(kDataFramePayload, stream_->data());
+
+ // Receive trailers, with single header field "custom-key: custom-value".
+ std::string trailers = HeadersFrame(
+ QuicTextUtils::HexDecode("00002f0125a849e95ba97d7f8925a849e95bb8e8b4bf"));
+ stream_->OnStreamFrame(
+ QuicStreamFrame(stream_->id(), false, offset, trailers));
+ offset += trailers.size();
+
+ EXPECT_THAT(stream_->received_trailers(),
+ ElementsAre(Pair("custom-key", "custom-value")));
+
+ // Closing the connection is mocked out in tests. Instead, simply stop
+ // reading data at the stream level to prevent QuicSpdyStream from blowing up.
+ // TODO(b/124216424): Change error code to HTTP_UNEXPECTED_FRAME.
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Unexpected DATA frame received.",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET))
+ .WillOnce(InvokeWithoutArgs([this]() { stream_->StopReading(); }));
+
+ // Receive more data.
+ std::string data2 = DataFrame("This payload should not be proccessed.");
+ stream_->OnStreamFrame(QuicStreamFrame(stream_->id(), false, offset, data2));
+}
+
} // namespace
} // namespace test
} // namespace quic