From bbb880ffb6debbfdea535b4b3eb2204d49ae151d Mon Sep 17 00:00:00 2001 From: Nate Berkopec 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 Co-authored-by: Patrik Ragnarsson Co-authored-by: Evan Phoenix --- 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 0741107..9746bd4 100644 --- a/lib/puma/client.rb +++ b/lib/puma/client.rb @@ -40,6 +40,14 @@ module Puma # no body share this one object since it has no state. EmptyBody = NullIO.new + # 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 + include Puma::Const extend Forwardable @@ -427,6 +435,7 @@ module Puma @chunked_body = true @partial_part_left = 0 @prev_chunk = "" + @excess_cr = 0 @body = Tempfile.new(Const::PUMA_TMP_BASE) @body.unlink @@ -496,6 +505,20 @@ module Puma 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) @@ -518,6 +541,10 @@ module Puma @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 bfa2b97..f179757 100644 --- a/test/test_puma_server.rb +++ b/test/test_puma_server.rb @@ -645,6 +645,20 @@ EOF 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 -- 2.33.0