From cf71c742d6e04b625be1f32217c9ed11a1bd32b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 14 Mar 2024 12:36:54 +0100 Subject: Add max_fragmented_header_block_size HTTP/2 option --- Makefile | 2 +- doc/src/manual/cowboy_http2.asciidoc | 9 +++++++++ rebar.config | 2 +- src/cowboy_http2.erl | 1 + test/security_SUITE.erl | 35 ++++++++++++++++++++++++++++++++++- 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 1be1885..03c4f28 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ CT_OPTS += -ct_hooks cowboy_ct_hook [] # -boot start_sasl LOCAL_DEPS = crypto DEPS = cowlib ranch -dep_cowlib = git https://github.com/ninenines/cowlib 2.12.1 +dep_cowlib = git https://github.com/ninenines/cowlib 2.13.0 dep_ranch = git https://github.com/ninenines/ranch 1.8.0 DOC_DEPS = asciideck diff --git a/doc/src/manual/cowboy_http2.asciidoc b/doc/src/manual/cowboy_http2.asciidoc index 8eb3cf2..1d2619c 100644 --- a/doc/src/manual/cowboy_http2.asciidoc +++ b/doc/src/manual/cowboy_http2.asciidoc @@ -35,6 +35,7 @@ opts() :: #{ max_connection_window_size => 0..16#7fffffff, max_decode_table_size => non_neg_integer(), max_encode_table_size => non_neg_integer(), + max_fragmented_header_block_size => 16384..16#7fffffff, max_frame_size_received => 16384..16777215, max_frame_size_sent => 16384..16777215 | infinity, max_received_frame_rate => {pos_integer(), timeout()}, @@ -172,6 +173,14 @@ Maximum header table size in bytes used by the encoder. The server will compare this value to what the client advertises and choose the smallest one as the encoder's header table size. +max_fragmented_header_block_size (32768):: + +Maximum header block size when headers are split over multiple HEADERS +and CONTINUATION frames. Clients that attempt to send header blocks +larger than this value will receive an ENHANCE_YOUR_CALM connection +error. Note that this value is not advertised and should be large +enough for legitimate requests. + max_frame_size_received (16384):: Maximum size in bytes of the frames received by the server. This value is diff --git a/rebar.config b/rebar.config index 08bb1ec..27d0696 100644 --- a/rebar.config +++ b/rebar.config @@ -1,4 +1,4 @@ {deps, [ -{cowlib,".*",{git,"https://github.com/ninenines/cowlib","2.12.1"}},{ranch,".*",{git,"https://github.com/ninenines/ranch","1.8.0"}} +{cowlib,".*",{git,"https://github.com/ninenines/cowlib","2.13.0"}},{ranch,".*",{git,"https://github.com/ninenines/ranch","1.8.0"}} ]}. {erl_opts, [debug_info,warn_export_vars,warn_shadow_vars,warn_obsolete_guard,warn_missing_spec,warn_untyped_record]}. diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl index 91e9c51..5b1f1e1 100644 --- a/src/cowboy_http2.erl +++ b/src/cowboy_http2.erl @@ -44,6 +44,7 @@ max_connection_window_size => 0..16#7fffffff, max_decode_table_size => non_neg_integer(), max_encode_table_size => non_neg_integer(), + max_fragmented_header_block_size => 16384..16#7fffffff, max_frame_size_received => 16384..16777215, max_frame_size_sent => 16384..16777215 | infinity, max_received_frame_rate => {pos_integer(), timeout()}, diff --git a/test/security_SUITE.erl b/test/security_SUITE.erl index fb63007..a1ba916 100644 --- a/test/security_SUITE.erl +++ b/test/security_SUITE.erl @@ -33,13 +33,14 @@ groups() -> Tests = [nc_rand, nc_zero], H1Tests = [slowloris, slowloris_chunks], H2CTests = [ + http2_cancel_flood, http2_data_dribble, http2_empty_frame_flooding_data, http2_empty_frame_flooding_headers_continuation, http2_empty_frame_flooding_push_promise, + http2_infinite_continuations, http2_ping_flood, http2_reset_flood, - http2_cancel_flood, http2_settings_flood, http2_zero_length_header_leak ], @@ -219,6 +220,38 @@ http2_empty_frame_flooding_push_promise(Config) -> {ok, <<_:24, 7:8, _:72, 1:32>>} = gen_tcp:recv(Socket, 17, 6000), ok. +http2_infinite_continuations(Config) -> + doc("Confirm that Cowboy rejects CONTINUATION frames when the " + "total size of HEADERS + CONTINUATION(s) exceeds the limit."), + {ok, Socket} = rfc7540_SUITE:do_handshake(Config), + %% Send a HEADERS frame followed by a large number + %% of continuation frames. + {HeadersBlock, _} = cow_hpack:encode([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/">>} + ]), + HeadersBlockLen = iolist_size(HeadersBlock), + ok = gen_tcp:send(Socket, [ + %% HEADERS frame. + << + HeadersBlockLen:24, 1:8, 0:5, + 0:1, %% END_HEADERS + 0:1, + 1:1, %% END_STREAM + 0:1, + 1:31 %% Stream ID. + >>, + HeadersBlock, + %% CONTINUATION frames. + [<<1024:24, 9:8, 0:8, 0:1, 1:31, 0:1024/unit:8>> + || _ <- lists:seq(1, 100)] + ]), + %% Receive an ENHANCE_YOUR_CALM connection error. + {ok, <<_:24, 7:8, _:72, 11:32>>} = gen_tcp:recv(Socket, 17, 6000), + ok. + %% @todo http2_internal_data_buffering(Config) -> I do not know how to test this. % doc("Request many very large responses, with a larger than necessary window size, " % "but do not attempt to read from the socket. (CVE-2019-9517)"), -- cgit v1.2.3