Rejects WINDOW_UPDATE frames with zero deltas, as per RFC 9113 Section 6.9.
PiperOrigin-RevId: 484536770
diff --git a/quiche/http2/adapter/oghttp2_adapter_test.cc b/quiche/http2/adapter/oghttp2_adapter_test.cc
index 673ea23..2ccde27 100644
--- a/quiche/http2/adapter/oghttp2_adapter_test.cc
+++ b/quiche/http2/adapter/oghttp2_adapter_test.cc
@@ -3833,6 +3833,67 @@
EXPECT_EQ(peer_window, kInitialFlowControlWindowSize);
}
+TEST(OgHttp2AdapterTest, WindowUpdateZeroDelta) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options;
+ options.perspective = Perspective::kServer;
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ const std::string data_chunk(kDefaultFramePayloadSizeLimit, 'a');
+ const std::string request =
+ TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/"}},
+ /*fin=*/false)
+ .WindowUpdate(1, 0)
+ .Data(1, "Subsequent frames on stream 1 are not delivered.")
+ .Serialize();
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // Stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream).Times(4);
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+
+ EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0));
+
+ adapter->ProcessBytes(request);
+
+ EXPECT_TRUE(adapter->want_write());
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0));
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(RST_STREAM, 1, _, 0x0,
+ static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+ EXPECT_CALL(visitor, OnCloseStream(1, _));
+
+ adapter->Send();
+
+ const std::string window_update =
+ TestFrameSequence().WindowUpdate(0, 0).Serialize();
+ EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0));
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kFlowControlError));
+ adapter->ProcessBytes(window_update);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(GOAWAY, 0, _, 0x0,
+ static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+ adapter->Send();
+}
+
TEST(OgHttp2AdapterTest, WindowUpdateRaisesFlowControlWindowLimit) {
DataSavingVisitor visitor;
OgHttp2Adapter::Options options;
diff --git a/quiche/http2/adapter/oghttp2_session.cc b/quiche/http2/adapter/oghttp2_session.cc
index 75d160a..bcec1cd 100644
--- a/quiche/http2/adapter/oghttp2_session.cc
+++ b/quiche/http2/adapter/oghttp2_session.cc
@@ -1430,8 +1430,20 @@
void OgHttp2Session::OnWindowUpdate(spdy::SpdyStreamId stream_id,
int delta_window_size) {
if (stream_id == 0) {
+ if (delta_window_size == 0) {
+ // A PROTOCOL_ERROR, according to RFC 9113 Section 6.9.
+ LatchErrorAndNotify(Http2ErrorCode::PROTOCOL_ERROR,
+ ConnectionError::kFlowControlError);
+ return;
+ }
connection_send_window_ += delta_window_size;
} else {
+ if (delta_window_size == 0) {
+ // A PROTOCOL_ERROR, according to RFC 9113 Section 6.9.
+ EnqueueFrame(std::make_unique<spdy::SpdyRstStreamIR>(
+ stream_id, spdy::ERROR_CODE_PROTOCOL_ERROR));
+ return;
+ }
auto it = stream_map_.find(stream_id);
if (it == stream_map_.end()) {
QUICHE_VLOG(1) << "Stream " << stream_id << " not found!";