|
@@ -48,6 +48,7 @@
|
|
max_frame_size_sent => 16384..16777215 | infinity,
|
|
max_frame_size_sent => 16384..16777215 | infinity,
|
|
max_received_frame_rate => {pos_integer(), timeout()},
|
|
max_received_frame_rate => {pos_integer(), timeout()},
|
|
max_reset_stream_rate => {pos_integer(), timeout()},
|
|
max_reset_stream_rate => {pos_integer(), timeout()},
|
|
|
|
+ max_cancel_stream_rate => {pos_integer(), timeout()},
|
|
max_stream_buffer_size => non_neg_integer(),
|
|
max_stream_buffer_size => non_neg_integer(),
|
|
max_stream_window_size => 0..16#7fffffff,
|
|
max_stream_window_size => 0..16#7fffffff,
|
|
metrics_callback => cowboy_metrics_h:metrics_callback(),
|
|
metrics_callback => cowboy_metrics_h:metrics_callback(),
|
|
@@ -114,6 +115,10 @@
|
|
reset_rate_num :: undefined | pos_integer(),
|
|
reset_rate_num :: undefined | pos_integer(),
|
|
reset_rate_time :: undefined | integer(),
|
|
reset_rate_time :: undefined | integer(),
|
|
|
|
|
|
|
|
+ %% HTTP/2 rapid reset attack protection.
|
|
|
|
+ cancel_rate_num :: undefined | pos_integer(),
|
|
|
|
+ cancel_rate_time :: undefined | integer(),
|
|
|
|
+
|
|
%% Flow requested for all streams.
|
|
%% Flow requested for all streams.
|
|
flow = 0 :: non_neg_integer(),
|
|
flow = 0 :: non_neg_integer(),
|
|
|
|
|
|
@@ -173,9 +178,11 @@ init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer
|
|
_ -> parse(State, Buffer)
|
|
_ -> parse(State, Buffer)
|
|
end.
|
|
end.
|
|
|
|
|
|
-init_rate_limiting(State) ->
|
|
|
|
|
|
+init_rate_limiting(State0) ->
|
|
CurrentTime = erlang:monotonic_time(millisecond),
|
|
CurrentTime = erlang:monotonic_time(millisecond),
|
|
- init_reset_rate_limiting(init_frame_rate_limiting(State, CurrentTime), CurrentTime).
|
|
|
|
|
|
+ State1 = init_frame_rate_limiting(State0, CurrentTime),
|
|
|
|
+ State2 = init_reset_rate_limiting(State1, CurrentTime),
|
|
|
|
+ init_cancel_rate_limiting(State2, CurrentTime).
|
|
|
|
|
|
init_frame_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->
|
|
init_frame_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->
|
|
{FrameRateNum, FrameRatePeriod} = maps:get(max_received_frame_rate, Opts, {10000, 10000}),
|
|
{FrameRateNum, FrameRatePeriod} = maps:get(max_received_frame_rate, Opts, {10000, 10000}),
|
|
@@ -189,6 +196,12 @@ init_reset_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->
|
|
reset_rate_num=ResetRateNum, reset_rate_time=add_period(CurrentTime, ResetRatePeriod)
|
|
reset_rate_num=ResetRateNum, reset_rate_time=add_period(CurrentTime, ResetRatePeriod)
|
|
}.
|
|
}.
|
|
|
|
|
|
|
|
+init_cancel_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->
|
|
|
|
+ {CancelRateNum, CancelRatePeriod} = maps:get(max_cancel_stream_rate, Opts, {500, 10000}),
|
|
|
|
+ State#state{
|
|
|
|
+ cancel_rate_num=CancelRateNum, cancel_rate_time=add_period(CurrentTime, CancelRatePeriod)
|
|
|
|
+ }.
|
|
|
|
+
|
|
add_period(_, infinity) -> infinity;
|
|
add_period(_, infinity) -> infinity;
|
|
add_period(Time, Period) -> Time + Period.
|
|
add_period(Time, Period) -> Time + Period.
|
|
|
|
|
|
@@ -568,11 +581,27 @@ rst_stream_frame(State=#state{streams=Streams0, children=Children0}, StreamID, R
|
|
{#stream{state=StreamState}, Streams} ->
|
|
{#stream{state=StreamState}, Streams} ->
|
|
terminate_stream_handler(State, StreamID, Reason, StreamState),
|
|
terminate_stream_handler(State, StreamID, Reason, StreamState),
|
|
Children = cowboy_children:shutdown(Children0, StreamID),
|
|
Children = cowboy_children:shutdown(Children0, StreamID),
|
|
- State#state{streams=Streams, children=Children};
|
|
|
|
|
|
+ cancel_rate_limit(State#state{streams=Streams, children=Children});
|
|
error ->
|
|
error ->
|
|
State
|
|
State
|
|
end.
|
|
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}) ->
|
|
ignored_frame(State=#state{http2_machine=HTTP2Machine0}) ->
|
|
case cow_http2_machine:ignored_frame(HTTP2Machine0) of
|
|
case cow_http2_machine:ignored_frame(HTTP2Machine0) of
|
|
{ok, HTTP2Machine} ->
|
|
{ok, HTTP2Machine} ->
|