Rejects WINDOW_UPDATE frames that would cause a window overflow, as per Section 6.9.1 of RFC 9113. PiperOrigin-RevId: 484541291
diff --git a/quiche/http2/adapter/oghttp2_adapter_test.cc b/quiche/http2/adapter/oghttp2_adapter_test.cc index 2ccde27..70daec0 100644 --- a/quiche/http2/adapter/oghttp2_adapter_test.cc +++ b/quiche/http2/adapter/oghttp2_adapter_test.cc
@@ -3894,6 +3894,72 @@ adapter->Send(); } +TEST(OgHttp2AdapterTest, WindowUpdateCausesWindowOverflow) { + 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, std::numeric_limits<int>::max()) + .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::FLOW_CONTROL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, _)); + + adapter->Send(); + + const std::string window_update = + TestFrameSequence() + .WindowUpdate(0, std::numeric_limits<int>::max()) + .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::FLOW_CONTROL_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 bcec1cd..52c0a12 100644 --- a/quiche/http2/adapter/oghttp2_session.cc +++ b/quiche/http2/adapter/oghttp2_session.cc
@@ -1429,6 +1429,7 @@ void OgHttp2Session::OnWindowUpdate(spdy::SpdyStreamId stream_id, int delta_window_size) { + constexpr int kMaxWindowValue = 2147483647; // (1 << 31) - 1 if (stream_id == 0) { if (delta_window_size == 0) { // A PROTOCOL_ERROR, according to RFC 9113 Section 6.9. @@ -1436,6 +1437,13 @@ ConnectionError::kFlowControlError); return; } + if (connection_send_window_ > 0 && + delta_window_size > (kMaxWindowValue - connection_send_window_)) { + // Window overflow is a FLOW_CONTROL_ERROR. + LatchErrorAndNotify(Http2ErrorCode::FLOW_CONTROL_ERROR, + ConnectionError::kFlowControlError); + return; + } connection_send_window_ += delta_window_size; } else { if (delta_window_size == 0) { @@ -1459,6 +1467,13 @@ if (streams_reset_.contains(stream_id)) { return; } + if (it->second.send_window > 0 && + delta_window_size > (kMaxWindowValue - it->second.send_window)) { + // Window overflow is a FLOW_CONTROL_ERROR. + EnqueueFrame(std::make_unique<spdy::SpdyRstStreamIR>( + stream_id, spdy::ERROR_CODE_FLOW_CONTROL_ERROR)); + return; + } const bool was_blocked = (it->second.send_window <= 0); it->second.send_window += delta_window_size; if (was_blocked && it->second.send_window > 0) {