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/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 96e5aef..1f8d6cf 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -173,6 +173,14 @@
return params;
}
+void WriteHeadersOnStream(QuicSpdyStream* stream) {
+ // Since QuicSpdyStream uses QuicHeaderList::empty() to detect too large
+ // headers, it also fails when receiving empty headers.
+ SpdyHeaderBlock headers;
+ headers["foo"] = "bar";
+ stream->WriteHeaders(std::move(headers), /* fin = */ false, nullptr);
+}
+
class ServerDelegate : public PacketDroppingTestWriter::Delegate {
public:
explicit ServerDelegate(QuicDispatcher* dispatcher)
@@ -1960,6 +1968,7 @@
// Open a data stream to make sure the stream level flow control is updated.
QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+ WriteHeadersOnStream(stream);
stream->WriteOrBufferBody("hello", false);
// Client should have the right values for server's receive window.
@@ -2016,6 +2025,7 @@
// Open a data stream to make sure the stream level flow control is updated.
QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+ WriteHeadersOnStream(stream);
stream->WriteOrBufferBody("hello", false);
// Client should have the right values for server's receive window.
@@ -3564,6 +3574,7 @@
// Set a TTL which expires immediately.
stream->MaybeSetTtl(QuicTime::Delta::FromMicroseconds(1));
+ WriteHeadersOnStream(stream);
// 1 MB body.
std::string body(1024 * 1024, 'a');
stream->WriteOrBufferBody(body, true);
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index c625410..96be207 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -760,6 +760,14 @@
bool QuicSpdyStream::OnDataFrameStart(Http3FrameLengths frame_lengths) {
DCHECK(
VersionHasDataFrameHeader(session()->connection()->transport_version()));
+ if (!headers_decompressed_ || trailers_decompressed_) {
+ // TODO(b/124216424): Change error code to HTTP_UNEXPECTED_FRAME.
+ session()->connection()->CloseConnection(
+ QUIC_INVALID_HEADERS_STREAM_DATA, "Unexpected DATA frame received.",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return false;
+ }
+
body_buffer_.OnDataHeader(frame_lengths);
return true;
}
@@ -841,6 +849,15 @@
DCHECK(VersionUsesQpack(spdy_session_->connection()->transport_version()));
DCHECK(!qpack_decoded_headers_accumulator_);
+ if (trailers_decompressed_) {
+ // TODO(b/124216424): Change error code to HTTP_UNEXPECTED_FRAME.
+ session()->connection()->CloseConnection(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ "HEADERS frame received after trailing HEADERS.",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return false;
+ }
+
if (headers_decompressed_) {
trailers_length_ = frame_length;
} else {
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