Reject HTTP/2 only frames in HTTP/3.
Protected by quic_reloadable_flag_quic_reject_spdy_frames.
PiperOrigin-RevId: 334660786
Change-Id: Id0362f27f34e8a0f10caf3dfb6c8f4554cb56dda
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc
index 3e06703..d142201 100644
--- a/quic/core/http/http_decoder.cc
+++ b/quic/core/http/http_decoder.cc
@@ -6,8 +6,10 @@
#include <cstdint>
+#include "net/third_party/quiche/src/http2/http2_constants.h"
#include "net/third_party/quiche/src/quic/core/http/http_frames.h"
#include "net/third_party/quiche/src/quic/core/quic_data_reader.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
#include "net/third_party/quiche/src/quic/core/quic_types.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_fallthrough.h"
@@ -93,7 +95,7 @@
switch (state_) {
case STATE_READING_FRAME_TYPE:
- ReadFrameType(&reader);
+ continue_processing = ReadFrameType(&reader);
break;
case STATE_READING_FRAME_LENGTH:
continue_processing = ReadFrameLength(&reader);
@@ -114,7 +116,7 @@
return len - reader.BytesRemaining();
}
-void HttpDecoder::ReadFrameType(QuicDataReader* reader) {
+bool HttpDecoder::ReadFrameType(QuicDataReader* reader) {
DCHECK_NE(0u, reader->BytesRemaining());
if (current_type_field_length_ == 0) {
// A new frame is coming.
@@ -124,7 +126,7 @@
// Buffer a new type field.
remaining_type_field_length_ = current_type_field_length_;
BufferFrameType(reader);
- return;
+ return true;
}
// The reader has all type data needed, so no need to buffer.
bool success = reader->ReadVarInt62(¤t_frame_type_);
@@ -134,14 +136,34 @@
BufferFrameType(reader);
// The frame is still not buffered completely.
if (remaining_type_field_length_ != 0) {
- return;
+ return true;
}
QuicDataReader type_reader(type_buffer_.data(), current_type_field_length_);
bool success = type_reader.ReadVarInt62(¤t_frame_type_);
DCHECK(success);
}
+ if (GetQuicReloadableFlag(quic_reject_spdy_frames)) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_reject_spdy_frames);
+ // https://tools.ietf.org/html/draft-ietf-quic-http-31#section-7.2.8
+ // specifies that the following frames are treated as errors.
+ if (current_frame_type_ ==
+ static_cast<uint64_t>(http2::Http2FrameType::PRIORITY) ||
+ current_frame_type_ ==
+ static_cast<uint64_t>(http2::Http2FrameType::PING) ||
+ current_frame_type_ ==
+ static_cast<uint64_t>(http2::Http2FrameType::WINDOW_UPDATE) ||
+ current_frame_type_ ==
+ static_cast<uint64_t>(http2::Http2FrameType::CONTINUATION)) {
+ RaiseError(
+ QUIC_HTTP_RECEIVE_SPDY_FRAME,
+ quiche::QuicheStrCat("HTTP/2 frame received in a HTTP/3 connection: ",
+ current_frame_type_));
+ return false;
+ }
+ }
state_ = STATE_READING_FRAME_LENGTH;
+ return true;
}
bool HttpDecoder::ReadFrameLength(QuicDataReader* reader) {
diff --git a/quic/core/http/http_decoder.h b/quic/core/http/http_decoder.h
index 1551aa7..82535f7 100644
--- a/quic/core/http/http_decoder.h
+++ b/quic/core/http/http_decoder.h
@@ -158,8 +158,9 @@
// Reads the type of a frame from |reader|. Sets error_ and error_detail_
// if there are any errors. Also calls OnDataFrameStart() or
- // OnHeadersFrameStart() for appropriate frame types.
- void ReadFrameType(QuicDataReader* reader);
+ // OnHeadersFrameStart() for appropriate frame types. Returns whether the
+ // processing should continue.
+ bool ReadFrameType(QuicDataReader* reader);
// Reads the length of a frame from |reader|. Sets error_ and error_detail_
// if there are any errors. Returns whether processing should continue.
diff --git a/quic/core/http/http_decoder_test.cc b/quic/core/http/http_decoder_test.cc
index 01f0a5f..fb29d7b 100644
--- a/quic/core/http/http_decoder_test.cc
+++ b/quic/core/http/http_decoder_test.cc
@@ -192,7 +192,7 @@
const QuicByteCount payload_lengths[] = {0, 14, 100};
const uint64_t frame_types[] = {
0x21, 0x40, 0x5f, 0x7e, 0x9d, // some reserved frame types
- 0x06, 0x6f, 0x14 // some unknown, not reserved frame types
+ 0x6f, 0x14 // some unknown, not reserved frame types
};
for (auto payload_length : payload_lengths) {
@@ -791,6 +791,21 @@
EXPECT_EQ("Frame is too large.", decoder_.error_detail());
}
+TEST_F(HttpDecoderTest, Http2Frame) {
+ SetQuicReloadableFlag(quic_reject_spdy_frames, true);
+ std::string input = quiche::QuicheTextUtils::HexDecode(
+ "06" // PING in HTTP/2 but not supported in HTTP/3.
+ "05" // length
+ "15"); // random payload
+
+ // Process the full frame.
+ EXPECT_CALL(visitor_, OnError(&decoder_));
+ EXPECT_EQ(1u, ProcessInput(input));
+ EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_RECEIVE_SPDY_FRAME));
+ EXPECT_EQ("HTTP/2 frame received in a HTTP/3 connection: 6",
+ decoder_.error_detail());
+}
+
TEST_F(HttpDecoderTest, HeadersPausedThenData) {
InSequence s;
std::string input = quiche::QuicheStrCat(
diff --git a/quic/core/http/quic_server_session_base_test.cc b/quic/core/http/quic_server_session_base_test.cc
index 61303de..a0ac44a 100644
--- a/quic/core/http/quic_server_session_base_test.cc
+++ b/quic/core/http/quic_server_session_base_test.cc
@@ -305,7 +305,7 @@
QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
quiche::QuicheStringPiece("\1\0\0\0\0\0\0\0HT"));
QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(1), false, 0,
- quiche::QuicheStringPiece("\2\0\0\0\0\0\0\0HT"));
+ quiche::QuicheStringPiece("\3\0\0\0\0\0\0\0HT"));
session_->OnStreamFrame(frame1);
session_->OnStreamFrame(frame2);
EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index 8a07598..63d96f1 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -209,6 +209,7 @@
RETURN_STRING_LITERAL(QUIC_HTTP_GOAWAY_INVALID_STREAM_ID);
RETURN_STRING_LITERAL(QUIC_HTTP_GOAWAY_ID_LARGER_THAN_PREVIOUS);
RETURN_STRING_LITERAL(QUIC_HTTP_RECEIVE_SPDY_SETTING);
+ RETURN_STRING_LITERAL(QUIC_HTTP_RECEIVE_SPDY_FRAME);
RETURN_STRING_LITERAL(QUIC_HPACK_INDEX_VARINT_ERROR);
RETURN_STRING_LITERAL(QUIC_HPACK_NAME_LENGTH_VARINT_ERROR);
RETURN_STRING_LITERAL(QUIC_HPACK_VALUE_LENGTH_VARINT_ERROR);
@@ -590,6 +591,9 @@
return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::ID_ERROR)};
case QUIC_HTTP_RECEIVE_SPDY_SETTING:
return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::SETTINGS_ERROR)};
+ case QUIC_HTTP_RECEIVE_SPDY_FRAME:
+ return {false,
+ static_cast<uint64_t>(QuicHttp3ErrorCode::FRAME_UNEXPECTED)};
case QUIC_HPACK_INDEX_VARINT_ERROR:
return {true, static_cast<uint64_t>(INTERNAL_ERROR)};
case QUIC_HPACK_NAME_LENGTH_VARINT_ERROR:
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index 180dff7..85fb7f2 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -445,6 +445,8 @@
// HTTP/3 session received SETTINGS frame which contains HTTP/2 specific
// settings.
QUIC_HTTP_RECEIVE_SPDY_SETTING = 169,
+ // HTTP/3 session received an HTTP/2 only frame.
+ QUIC_HTTP_RECEIVE_SPDY_FRAME = 171,
// HPACK header block decoding errors.
// Index varint beyond implementation limit.
@@ -499,7 +501,7 @@
QUIC_MISSING_WRITE_KEYS = 170,
// No error. Used as bound while iterating.
- QUIC_LAST_ERROR = 171,
+ QUIC_LAST_ERROR = 172,
};
// QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC,
// or a varint62 when doing IETF QUIC. Ensure that its value does not exceed
diff --git a/quic/tools/quic_simple_server_session_test.cc b/quic/tools/quic_simple_server_session_test.cc
index 780ff9d..c591f81 100644
--- a/quic/tools/quic_simple_server_session_test.cc
+++ b/quic/tools/quic_simple_server_session_test.cc
@@ -395,7 +395,7 @@
QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
quiche::QuicheStringPiece("\1\0\0\0\0\0\0\0HT"));
QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(1), false, 0,
- quiche::QuicheStringPiece("\2\0\0\0\0\0\0\0HT"));
+ quiche::QuicheStringPiece("\3\0\0\0\0\0\0\0HT"));
session_->OnStreamFrame(frame1);
session_->OnStreamFrame(frame2);
EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));