diff --git a/doc/src/manual/cowboy_http2.asciidoc b/doc/src/manual/cowboy_http2.asciidoc index 826f00be..00e4f310 100644 --- a/doc/src/manual/cowboy_http2.asciidoc +++ b/doc/src/manual/cowboy_http2.asciidoc @@ -201,8 +201,8 @@ similar to a supervisor restart intensity/period. max_cancel_stream_rate ({500, 10000}):: -Maximum cancel stream rate per connection. This can be used to protect -against the rapid reset attack (CVE-2023-44487), by limiting the +Maximum cancel stream rate per connection. This can be used to +protect against misbehaving or malicious peers, by limiting the number of streams that the peer can reset over a certain time period. The rate is expressed as a tuple `{NumCancels, TimeMs}`. This is similar to a supervisor restart intensity/period. diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl index 32cbaa07..9ad16bd7 100644 --- a/src/cowboy_http2.erl +++ b/src/cowboy_http2.erl @@ -576,21 +576,30 @@ early_error(State0=#state{ref=Ref, opts=Opts, peer=Peer}, send_headers(State0, StreamID, fin, StatusCode0, RespHeaders0) end. -rst_stream_frame(State0=#state{streams=Streams0, children=Children0}, StreamID, Reason) -> - State1 = case maps:take(StreamID, Streams0) of +rst_stream_frame(State=#state{streams=Streams0, children=Children0}, StreamID, Reason) -> + case maps:take(StreamID, Streams0) of {#stream{state=StreamState}, Streams} -> - terminate_stream_handler(State0, StreamID, Reason, StreamState), + terminate_stream_handler(State, StreamID, Reason, StreamState), Children = cowboy_children:shutdown(Children0, StreamID), - State0#state{streams=Streams, children=Children}; + cancel_rate_limit(State#state{streams=Streams, children=Children}); error -> - State0 - end, - case cancel_rate(State1) of - {ok, State} -> - State; - error -> - terminate(State1, {connection_error, enhance_your_calm, - 'Stream cancel rate larger than configuration allows. Flood? (CVE-2023-44487)'}) + State + end. + +cancel_rate_limit(State0=#state{cancel_rate_num=Num0, cancel_rate_time=Time}) -> + case Num0 - 1 of + 0 -> + CurrentTime = erlang:monotonic_time(millisecond), + if + CurrentTime < Time -> + terminate(State0, {connection_error, enhance_your_calm, + 'Stream cancel rate larger than configuration allows. Flood? (CVE-2023-44487)'}); + true -> + %% When the option has a period of infinity we cannot reach this clause. + init_cancel_rate_limiting(State0, CurrentTime) + end; + Num -> + State0#state{cancel_rate_num=Num} end. ignored_frame(State=#state{http2_machine=HTTP2Machine0}) -> @@ -1157,21 +1166,6 @@ reset_rate(State0=#state{reset_rate_num=Num0, reset_rate_time=Time}) -> {ok, State0#state{reset_rate_num=Num}} end. -cancel_rate(State0=#state{cancel_rate_num=Num0, cancel_rate_time=Time}) -> - case Num0 - 1 of - 0 -> - CurrentTime = erlang:monotonic_time(millisecond), - if - CurrentTime < Time -> - error; - true -> - %% When the option has a period of infinity we cannot reach this clause. - {ok, init_cancel_rate_limiting(State0, CurrentTime)} - end; - Num -> - {ok, State0#state{cancel_rate_num=Num}} - end. - stop_stream(State=#state{http2_machine=HTTP2Machine}, StreamID) -> case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine) of %% When the stream terminates normally (without sending RST_STREAM)