Skip to content

Commit

Permalink
prevent continuation floods
Browse files Browse the repository at this point in the history
prevent buffered payload from subsequent CONTINUATION frames to overflow the threshold.

the threshold is the local max frame size, which is the same value used by HAProxy, and has the side benefit of not adding another setting to manage, which means no API changes.
  • Loading branch information
HoneyryderChuck committed Jun 25, 2024
1 parent c2a8fa3 commit aeec322
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 1 deletion.
13 changes: 12 additions & 1 deletion lib/http/2/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,18 @@ def receive(data)
connection_error unless frame[:type] == :continuation && frame[:stream] == @continuation.first[:stream]

@continuation << frame
next unless frame[:flags].include? :end_headers
unless frame[:flags].include? :end_headers
buffered_payload = @continuation.sum { |f| f[:payload].bytesize }
# prevent HTTP/2 CONTINUATION FLOOD
# same heuristic as the one from HAProxy: https://www.haproxy.com/blog/haproxy-is-resilient-to-the-http-2-continuation-flood
# different mitigation (connection closed, instead of 400 response)
unless buffered_payload < @local_settings[:settings_max_frame_size]
connection_error(:protocol_error,
msg: "too many continuations received")
end

next
end

payload = @continuation.map { |f| f[:payload] }.join

Expand Down
1 change: 1 addition & 0 deletions spec/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def window_update_frame
def continuation_frame
{
type: :continuation,
stream: 1,
flags: [:end_headers],
payload: "-second-block"
}
Expand Down
29 changes: 29 additions & 0 deletions spec/shared_examples/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,35 @@
context "framing" do
let(:conn) { connected_conn }

it "should chain continuation frames" do
headers = headers_frame
headers[:flags] = []
continuation = continuation_frame
continuation[:stream] = headers[:stream]
continuation[:flags] = []

conn << f.generate(headers)
conn << f.generate(continuation)
expect(conn.active_stream_count).to be_zero # stream not open yet
end

it "should refuse continuation frames which overflow the max frame size" do
max_frame_size = connected_conn.local_settings[:settings_max_frame_size]

headers = headers_frame
headers[:flags] = []

conn << f.generate(headers)
expect do
max_frame_size.times do
continuation = continuation_frame
continuation[:stream] = headers[:stream]
continuation[:flags] = []
conn << f.generate(continuation)
end
end.to raise_error(ProtocolError)
end

it "should require that split header blocks are a contiguous sequence" do
headers = headers_frame
headers[:flags] = []
Expand Down

0 comments on commit aeec322

Please sign in to comment.