Skip to content

Commit

Permalink
Merge pull request from GHSA-c2f4-cvqm-65w2
Browse files Browse the repository at this point in the history
Co-authored-by: MSP-Greg <MSP-Greg@users.noreply.github.com>
Co-authored-by: Patrik Ragnarsson <patrik@starkast.net>
Co-authored-by: Evan Phoenix <evan@phx.io>
  • Loading branch information
4 people committed Jan 8, 2024
1 parent 78393bf commit bbb880f
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 0 deletions.
27 changes: 27 additions & 0 deletions lib/puma/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ class Client
CHUNK_VALID_ENDING = Const::LINE_END
CHUNK_VALID_ENDING_SIZE = CHUNK_VALID_ENDING.bytesize

# The maximum number of bytes we'll buffer looking for a valid
# chunk header.
MAX_CHUNK_HEADER_SIZE = 4096

# The maximum amount of excess data the client sends
# using chunk size extensions before we abort the connection.
MAX_CHUNK_EXCESS = 16 * 1024

# Content-Length header value validation
CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze

Expand Down Expand Up @@ -460,6 +468,7 @@ def setup_chunked_body(body)
@chunked_body = true
@partial_part_left = 0
@prev_chunk = ""
@excess_cr = 0

@body = Tempfile.new(Const::PUMA_TMP_BASE)
@body.unlink
Expand Down Expand Up @@ -541,6 +550,20 @@ def decode_chunk(chunk)
end
end

# Track the excess as a function of the size of the
# header vs the size of the actual data. Excess can
# go negative (and is expected to) when the body is
# significant.
# The additional of chunk_hex.size and 2 compensates
# for a client sending 1 byte in a chunked body over
# a long period of time, making sure that that client
# isn't accidentally eventually punished.
@excess_cr += (line.size - len - chunk_hex.size - 2)

if @excess_cr >= MAX_CHUNK_EXCESS
raise HttpParserError, "Maximum chunk excess detected"
end

len += 2

part = io.read(len)
Expand Down Expand Up @@ -568,6 +591,10 @@ def decode_chunk(chunk)
@partial_part_left = len - part.size
end
else
if @prev_chunk.size + chunk.size >= MAX_CHUNK_HEADER_SIZE
raise HttpParserError, "maximum size of chunk header exceeded"
end

@prev_chunk = line
return false
end
Expand Down
14 changes: 14 additions & 0 deletions test/test_puma_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,20 @@ def test_large_chunked_request
end
end

def test_large_chunked_request_header
server_run(environment: :production) { |env|
[200, {}, [""]]
}

max_chunk_header_size = Puma::Client::MAX_CHUNK_HEADER_SIZE
header = "GET / HTTP/1.1\r\nConnection: close\r\nContent-Length: 200\r\nTransfer-Encoding: chunked\r\n\r\n"
socket = send_http "#{header}1;t#{'x' * (max_chunk_header_size + 2)}"

data = socket.read

assert_match "HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
end

def test_chunked_request_pause_before_value
body = nil
content_length = nil
Expand Down

0 comments on commit bbb880f

Please sign in to comment.