Skip to content

Commit f1f246c

Browse files
PiotrSikoraconcaf
authored andcommitted
http2: limit the number of inbound frames. (envoyproxy#20)
This change adds protections against flooding using PRIORITY and/or WINDOW_UPDATE frames, as well as frames with an empty payload and no end stream flag. Fixes CVE-2019-9511, CVE-2019-9513 and CVE-2019-9518. Signed-off-by: Piotr Sikora <[email protected]>
1 parent b6b39a8 commit f1f246c

File tree

4 files changed

+152
-3
lines changed

4 files changed

+152
-3
lines changed

docs/root/intro/version_history.rst

+4-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ Version history
8383
Runtime feature `envoy.reloadable_features.http2_protocol_options.stream_error_on_invalid_http_messaging` overrides :ref:`stream_error_on_invalid_http_messaging config setting <envoy_api_field_core.Http2ProtocolOptions.stream_error_on_invalid_http_messaging>`.
8484
1.11.1 (Pending)
8585
================
86-
* http: added mitigation of client initiated atacks that result in flooding of the outbound queue of downstream HTTP/2 connections.
86+
* http: added mitigation of client initiated atacks that result in flooding of the downstream HTTP/2 connections.
87+
* http: added :ref:`inbound_empty_frames_flood <config_http_conn_man_stats_per_codec>` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on consecutive inbound frames with an empty payload and no end stream flag. The limit is configured by setting the :ref:`max_consecutive_inbound_frames_with_empty_payload config setting <envoy_api_field_core.Http2ProtocolOptions.max_consecutive_inbound_frames_with_empty_payload>`.
88+
* http: added :ref:`inbound_priority_frames_flood <config_http_conn_man_stats_per_codec>` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on inbound PRIORITY frames. The limit is configured by setting the :ref:`max_inbound_priority_frames_per_stream config setting <envoy_api_field_core.Http2ProtocolOptions.max_inbound_priority_frames_per_stream>`.
89+
* http: added :ref:`inbound_window_update_frames_flood <config_http_conn_man_stats_per_codec>` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on inbound WINDOW_UPDATE frames. The limit is configured by setting the :ref:`max_inbound_window_update_frames_per_data_frame_sent config setting <envoy_api_field_core.Http2ProtocolOptions.max_inbound_window_update_frames_per_data_frame_sent>`.
8790
* http: added :ref:`outbound_flood <config_http_conn_man_stats_per_codec>` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the outbound queue limit. The limit is configured by setting the :ref:`max_outbound_frames config setting <envoy_api_field_core.Http2ProtocolOptions.max_outbound_frames>`
8891
* http: added :ref:`outbound_control_flood <config_http_conn_man_stats_per_codec>` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the outbound queue limit for PING, SETTINGS and RST_STREAM frames. The limit is configured by setting the :ref:`max_outbound_control_frames config setting <envoy_api_field_core.Http2ProtocolOptions.max_outbound_control_frames>`.
8992

source/common/http/http2/codec_impl.h

+34
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,40 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log
369369
// corresponding http2_protocol_options. Default value is 1000.
370370
const uint32_t max_outbound_control_frames_;
371371
const Buffer::OwnedBufferFragmentImpl::Releasor control_frame_buffer_releasor_;
372+
// This counter keeps track of the number of consecutive inbound frames of types HEADERS,
373+
// CONTINUATION and DATA with an empty payload and no end stream flag. If this counter exceeds
374+
// the `max_consecutive_inbound_frames_with_empty_payload_` value the connection is terminated.
375+
uint32_t consecutive_inbound_frames_with_empty_payload_ = 0;
376+
// Maximum number of consecutive inbound frames of types HEADERS, CONTINUATION and DATA without
377+
// a payload. Initialized from corresponding http2_protocol_options. Default value is 1.
378+
const uint32_t max_consecutive_inbound_frames_with_empty_payload_;
379+
380+
// This counter keeps track of the number of inbound streams.
381+
uint32_t inbound_streams_ = 0;
382+
// This counter keeps track of the number of inbound PRIORITY frames. If this counter exceeds
383+
// the value calculated using this formula:
384+
//
385+
// max_inbound_priority_frames_per_stream_ * (1 + inbound_streams_)
386+
//
387+
// the connection is terminated.
388+
uint64_t inbound_priority_frames_ = 0;
389+
// Maximum number of inbound PRIORITY frames per stream. Initialized from corresponding
390+
// http2_protocol_options. Default value is 100.
391+
const uint32_t max_inbound_priority_frames_per_stream_;
392+
393+
// This counter keeps track of the number of inbound WINDOW_UPDATE frames. If this counter exceeds
394+
// the value calculated using this formula:
395+
//
396+
// 1 + 2 * (inbound_streams_ +
397+
// max_inbound_window_update_frames_per_data_frame_sent_ * outbound_data_frames_)
398+
//
399+
// the connection is terminated.
400+
uint64_t inbound_window_update_frames_ = 0;
401+
// This counter keeps track of the number of outbound DATA frames.
402+
uint64_t outbound_data_frames_ = 0;
403+
// Maximum number of inbound WINDOW_UPDATE frames per outbound DATA frame sent. Initialized
404+
// from corresponding http2_protocol_options. Default value is 10.
405+
const uint32_t max_inbound_window_update_frames_per_data_frame_sent_;
372406

373407
private:
374408
virtual ConnectionCallbacks& callbacks() PURE;

test/integration/http2_integration_test.cc

+112
Original file line numberDiff line numberDiff line change
@@ -1788,4 +1788,116 @@ TEST_P(Http2FloodMitigationTest, ZerolenHeaderAllowed) {
17881788
test_server_->counter("http.config_test.downstream_cx_delayed_close_timeout")->value());
17891789
}
17901790

1791+
TEST_P(Http2FloodMitigationTest, EmptyHeaders) {
1792+
config_helper_.addConfigModifier(
1793+
[&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
1794+
-> void {
1795+
hcm.mutable_http2_protocol_options()
1796+
->mutable_max_consecutive_inbound_frames_with_empty_payload()
1797+
->set_value(0);
1798+
});
1799+
beginSession();
1800+
1801+
uint32_t request_idx = 0;
1802+
auto request = Http2Frame::makeEmptyHeadersFrame(request_idx);
1803+
sendFame(request);
1804+
1805+
tcp_client_->waitForDisconnect();
1806+
1807+
EXPECT_EQ(1, test_server_->counter("http2.inbound_empty_frames_flood")->value());
1808+
// Verify that connection was closed abortively
1809+
EXPECT_EQ(0,
1810+
test_server_->counter("http.config_test.downstream_cx_delayed_close_timeout")->value());
1811+
}
1812+
1813+
TEST_P(Http2FloodMitigationTest, EmptyHeadersContinuation) {
1814+
beginSession();
1815+
1816+
uint32_t request_idx = 0;
1817+
auto request = Http2Frame::makeEmptyHeadersFrame(request_idx);
1818+
sendFame(request);
1819+
1820+
for (int i = 0; i < 2; i++) {
1821+
request = Http2Frame::makeEmptyContinuationFrame(request_idx);
1822+
sendFame(request);
1823+
}
1824+
1825+
tcp_client_->waitForDisconnect();
1826+
1827+
EXPECT_EQ(1, test_server_->counter("http2.inbound_empty_frames_flood")->value());
1828+
// Verify that connection was closed abortively
1829+
EXPECT_EQ(0,
1830+
test_server_->counter("http.config_test.downstream_cx_delayed_close_timeout")->value());
1831+
}
1832+
1833+
TEST_P(Http2FloodMitigationTest, EmptyData) {
1834+
beginSession();
1835+
fake_upstreams_[0]->set_allow_unexpected_disconnects(true);
1836+
1837+
uint32_t request_idx = 0;
1838+
auto request = Http2Frame::makePostRequest(request_idx, "host", "/");
1839+
sendFame(request);
1840+
1841+
for (int i = 0; i < 2; i++) {
1842+
request = Http2Frame::makeEmptyDataFrame(request_idx);
1843+
sendFame(request);
1844+
}
1845+
1846+
tcp_client_->waitForDisconnect();
1847+
1848+
EXPECT_EQ(1, test_server_->counter("http2.inbound_empty_frames_flood")->value());
1849+
// Verify that connection was closed abortively
1850+
EXPECT_EQ(0,
1851+
test_server_->counter("http.config_test.downstream_cx_delayed_close_timeout")->value());
1852+
}
1853+
1854+
TEST_P(Http2FloodMitigationTest, PriorityIdleStream) {
1855+
beginSession();
1856+
1857+
floodServer(Http2Frame::makePriorityFrame(0, 1), "http2.inbound_priority_frames_flood");
1858+
}
1859+
1860+
TEST_P(Http2FloodMitigationTest, PriorityOpenStream) {
1861+
beginSession();
1862+
fake_upstreams_[0]->set_allow_unexpected_disconnects(true);
1863+
1864+
// Open stream.
1865+
uint32_t request_idx = 0;
1866+
auto request = Http2Frame::makeRequest(request_idx, "host", "/");
1867+
sendFame(request);
1868+
1869+
floodServer(Http2Frame::makePriorityFrame(request_idx, request_idx + 1),
1870+
"http2.inbound_priority_frames_flood");
1871+
}
1872+
1873+
TEST_P(Http2FloodMitigationTest, PriorityClosedStream) {
1874+
autonomous_upstream_ = true;
1875+
beginSession();
1876+
fake_upstreams_[0]->set_allow_unexpected_disconnects(true);
1877+
1878+
// Open stream.
1879+
uint32_t request_idx = 0;
1880+
auto request = Http2Frame::makeRequest(request_idx, "host", "/");
1881+
sendFame(request);
1882+
// Reading response marks this stream as closed in nghttp2.
1883+
auto frame = readFrame();
1884+
EXPECT_EQ(Http2Frame::Type::HEADERS, frame.type());
1885+
1886+
floodServer(Http2Frame::makePriorityFrame(request_idx, request_idx + 1),
1887+
"http2.inbound_priority_frames_flood");
1888+
}
1889+
1890+
TEST_P(Http2FloodMitigationTest, WindowUpdate) {
1891+
beginSession();
1892+
fake_upstreams_[0]->set_allow_unexpected_disconnects(true);
1893+
1894+
// Open stream.
1895+
uint32_t request_idx = 0;
1896+
auto request = Http2Frame::makeRequest(request_idx, "host", "/");
1897+
sendFame(request);
1898+
1899+
floodServer(Http2Frame::makeWindowUpdateFrame(request_idx, 1),
1900+
"http2.inbound_window_update_frames_flood");
1901+
}
1902+
17911903
} // namespace Envoy

test/integration/http2_integration_test.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ class Http2FloodMitigationTest : public testing::TestWithParam<Network::Address:
8989

9090
protected:
9191
void startHttp2Session();
92-
void floodServer(const Http2Frame& frame);
92+
void floodServer(const Http2Frame& frame, const std::string& flood_stat);
9393
void floodServer(absl::string_view host, absl::string_view path,
94-
Http2Frame::ResponseStatus expected_http_status);
94+
Http2Frame::ResponseStatus expected_http_status, const std::string& flood_stat);
9595
Http2Frame readFrame();
9696
void sendFame(const Http2Frame& frame);
9797
void beginSession();

0 commit comments

Comments
 (0)