Validates the value of SETTINGS_MAX_FRAME_SIZE in oghttp2.
PiperOrigin-RevId: 453247197
diff --git a/quiche/http2/adapter/http2_protocol.h b/quiche/http2/adapter/http2_protocol.h
index 817d6be..02a423a 100644
--- a/quiche/http2/adapter/http2_protocol.h
+++ b/quiche/http2/adapter/http2_protocol.h
@@ -51,7 +51,11 @@
// The default value for the size of the largest frame payload, according to RFC
// 7540 Section 6.5.2 (SETTINGS_MAX_FRAME_SIZE).
-const int kDefaultFramePayloadSizeLimit = 16 * 1024;
+const uint32_t kDefaultFramePayloadSizeLimit = 16u * 1024u;
+
+// The maximum value for the size of the largest frame payload, according to RFC
+// 7540 Section 6.5.2 (SETTINGS_MAX_FRAME_SIZE).
+const uint32_t kMaximumFramePayloadSizeLimit = 16777215u;
// The default value for the initial stream and connection flow control window
// size, according to RFC 7540 Section 6.9.2.
diff --git a/quiche/http2/adapter/http2_util.cc b/quiche/http2/adapter/http2_util.cc
index 963d264..707fa00 100644
--- a/quiche/http2/adapter/http2_util.cc
+++ b/quiche/http2/adapter/http2_util.cc
@@ -101,6 +101,8 @@
return "FlowControlError";
case ConnectionError::kInvalidGoAwayLastStreamId:
return "InvalidGoAwayLastStreamId";
+ case ConnectionError::kInvalidSetting:
+ return "InvalidSetting";
}
return "UnknownConnectionError";
}
diff --git a/quiche/http2/adapter/http2_visitor_interface.h b/quiche/http2/adapter/http2_visitor_interface.h
index 2ec68c5..4e6bcb8 100644
--- a/quiche/http2/adapter/http2_visitor_interface.h
+++ b/quiche/http2/adapter/http2_visitor_interface.h
@@ -82,6 +82,8 @@
kFlowControlError,
// The peer sent a GOAWAY with an invalid last-stream-ID field.
kInvalidGoAwayLastStreamId,
+ // The peer sent an invalid SETTINGS value.
+ kInvalidSetting,
};
virtual void OnConnectionError(ConnectionError error) = 0;
diff --git a/quiche/http2/adapter/nghttp2_adapter_test.cc b/quiche/http2/adapter/nghttp2_adapter_test.cc
index 22c8f83..8647b02 100644
--- a/quiche/http2/adapter/nghttp2_adapter_test.cc
+++ b/quiche/http2/adapter/nghttp2_adapter_test.cc
@@ -5883,6 +5883,35 @@
EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS}));
}
+TEST(NgHttp2AdapterTest, InvalidMaxFrameSize) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+
+ const std::string frames =
+ TestFrameSequence().ClientPreface({{MAX_FRAME_SIZE, 3}}).Serialize();
+ testing::InSequence s;
+
+ // Client preface
+ EXPECT_CALL(visitor, OnFrameHeader(0, 6, SETTINGS, 0));
+ EXPECT_CALL(
+ visitor,
+ OnInvalidFrame(0, Http2VisitorInterface::InvalidFrameError::kProtocol));
+
+ const int64_t read_result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(static_cast<size_t>(read_result), frames.size());
+
+ EXPECT_TRUE(adapter->want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(GOAWAY, 0, _, 0x0,
+ static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::GOAWAY}));
+}
+
TEST(NgHttp2AdapterTest, ServerForbidsProtocolPseudoheaderBeforeAck) {
DataSavingVisitor visitor;
auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
diff --git a/quiche/http2/adapter/oghttp2_adapter_test.cc b/quiche/http2/adapter/oghttp2_adapter_test.cc
index f09867c..ba195bc 100644
--- a/quiche/http2/adapter/oghttp2_adapter_test.cc
+++ b/quiche/http2/adapter/oghttp2_adapter_test.cc
@@ -347,6 +347,42 @@
EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS}));
}
+TEST(OgHttp2AdapterTest, InvalidMaxFrameSize) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options;
+ options.perspective = Perspective::kServer;
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ const std::string frames =
+ TestFrameSequence().ClientPreface({{MAX_FRAME_SIZE, 3}}).Serialize();
+ testing::InSequence s;
+
+ // Client preface
+ EXPECT_CALL(visitor, OnFrameHeader(0, 6, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(
+ visitor,
+ OnInvalidFrame(0, Http2VisitorInterface::InvalidFrameError::kProtocol));
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kInvalidSetting));
+
+ const int64_t read_result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(static_cast<size_t>(read_result), frames.size());
+
+ EXPECT_TRUE(adapter->want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(GOAWAY, 0, _, 0x0,
+ static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY}));
+}
+
TEST(OgHttp2AdapterTest, ClientHandles100Headers) {
DataSavingVisitor visitor;
OgHttp2Adapter::Options options;
diff --git a/quiche/http2/adapter/oghttp2_session.cc b/quiche/http2/adapter/oghttp2_session.cc
index 0f0d6bd..8be0fe8 100644
--- a/quiche/http2/adapter/oghttp2_session.cc
+++ b/quiche/http2/adapter/oghttp2_session.cc
@@ -1219,6 +1219,16 @@
void OgHttp2Session::OnSetting(spdy::SpdySettingsId id, uint32_t value) {
switch (id) {
case MAX_FRAME_SIZE:
+ if (value < kDefaultFramePayloadSizeLimit ||
+ value > kMaximumFramePayloadSizeLimit) {
+ visitor_.OnInvalidFrame(
+ 0, Http2VisitorInterface::InvalidFrameError::kProtocol);
+ // The specification says this is a connection-level protocol error.
+ LatchErrorAndNotify(
+ Http2ErrorCode::PROTOCOL_ERROR,
+ Http2VisitorInterface::ConnectionError::kInvalidSetting);
+ return;
+ }
max_frame_payload_ = value;
break;
case MAX_CONCURRENT_STREAMS: