106 lines
3.5 KiB
Diff
106 lines
3.5 KiB
Diff
Ubuntu note: simplified test case as to not hit this upstream bug:
|
|
https://github.com/puma/puma/issues/3307
|
|
|
|
From bbb880ffb6debbfdea535b4b3eb2204d49ae151d Mon Sep 17 00:00:00 2001
|
|
From: Nate Berkopec <nate.berkopec@gmail.com>
|
|
Date: Mon, 8 Jan 2024 14:48:43 +0900
|
|
Subject: [PATCH] Merge pull request from GHSA-c2f4-cvqm-65w2
|
|
|
|
Origin: https://github.com/puma/puma/commit/bbb880ffb6debbfdea535b4b3eb2204d49ae151d
|
|
|
|
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>
|
|
---
|
|
lib/puma/client.rb | 27 +++++++++++++++++++++++++++
|
|
test/test_puma_server.rb | 14 ++++++++++++++
|
|
2 files changed, 41 insertions(+)
|
|
|
|
diff --git a/lib/puma/client.rb b/lib/puma/client.rb
|
|
index 9c11912caa..b5a1569c68 100644
|
|
--- a/lib/puma/client.rb
|
|
+++ b/lib/puma/client.rb
|
|
@@ -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
|
|
|
|
@@ -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
|
|
@@ -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)
|
|
@@ -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
|
|
diff --git a/test/test_puma_server.rb b/test/test_puma_server.rb
|
|
index 2bfaf98848..05bf83e20d 100644
|
|
--- a/test/test_puma_server.rb
|
|
+++ b/test/test_puma_server.rb
|
|
@@ -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\n\r\n", data
|
|
+ end
|
|
+
|
|
def test_chunked_request_pause_before_value
|
|
body = nil
|
|
content_length = nil
|
|
--
|
|
2.33.0
|
|
|