[DRE-maint] Bug#1091795: bookworm-pu: package puma/5.6.5-3+deb12u1
Abhijith PA
abhijith at debian.org
Tue Dec 31 14:12:35 GMT 2024
Package: release.debian.org
Severity: normal
Tags: bookworm
User: release.debian.org at packages.debian.org
Usertags: pu
X-Debbugs-Cc: puma at packages.debian.org
Control: affects -1 + src:puma
Dear stable release team,
[ Reason ]
As a ruby team member I would like to do a update of this package in
stable to fix CVE-2023-40175,CVE-2024-21647 and CVE-2024-45614.
[ Tests ]
All tests from upstream related to this CVE is also backported and ran
successfully along with the existing tests.
[ Checklist ]
[x] *all* changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in stable
[x] the issue is verified as fixed in unstable
[ Changes ]
Debdiff are attached.
Except some minor corrections, all changes fitted without any fuzz.
-------------- next part --------------
diff -Nru puma-5.6.5/debian/changelog puma-5.6.5/debian/changelog
--- puma-5.6.5/debian/changelog 2023-02-09 20:54:05.000000000 +0530
+++ puma-5.6.5/debian/changelog 2024-12-31 17:44:27.000000000 +0530
@@ -1,3 +1,22 @@
+puma (5.6.5-3+deb12u1) bookworm; urgency=medium
+
+ * Team upload
+ * d/patches/
+ + CVE-2023-40175.patch: Fix CVE-2023-40175, incorrect behavior when
+ parsing chunked transfer encoding bodies and zero-length
+ Content-Length headers in a way that allowed HTTP request
+ smuggling. (Closes: #1050079)
+
+ + CVE-2024-21647.patch: Fix CVE-2024-21647 by limiting the size of
+ chunk extensions. (Closes: #1060345)
+
+ + CVE-2024-45614.patch: Fix CVE-2024-45614, clients could clobber
+ values set by intermediate proxies (such as X-Forwarded-For) by
+ providing a underscore version of the same header.
+ (Closes: #1082379)
+
+ -- Abhijith PA <abhijith at debian.org> Tue, 31 Dec 2024 17:44:27 +0530
+
puma (5.6.5-3) unstable; urgency=medium
* Team upload.
diff -Nru puma-5.6.5/debian/patches/CVE-2023-40175.patch puma-5.6.5/debian/patches/CVE-2023-40175.patch
--- puma-5.6.5/debian/patches/CVE-2023-40175.patch 1970-01-01 05:30:00.000000000 +0530
+++ puma-5.6.5/debian/patches/CVE-2023-40175.patch 2024-12-31 11:04:27.000000000 +0530
@@ -0,0 +1,143 @@
+From 7405a219801dcebc0ad6e0aa108d4319ca23f662 Mon Sep 17 00:00:00 2001
+From: Nate Berkopec <nate.berkopec at gmail.com>
+Date: Fri, 18 Aug 2023 09:47:23 +0900
+Subject: [PATCH] Merge pull request from GHSA-68xg-gqqm-vgj8
+
+* Reject empty string for Content-Length
+
+* Ignore trailers in last chunk
+
+* test_puma_server.rb - use heredoc, test_cl_and_te_smuggle
+
+* client.rb - stye/RubyCop
+
+* test_puma_server.rb - indented heredoc rubocop disable
+
+* Dentarg comments
+
+* Remove unused variable
+
+---------
+
+Co-authored-by: MSP-Greg <Greg.mpls at gmail.com>
+---
+ lib/puma/client.rb | 23 ++++++++++++++--------
+ test/test_puma_server.rb | 42 +++++++++++++++++++++++++++++++++++++++-
+ 2 files changed, 56 insertions(+), 9 deletions(-)
+
+diff --git a/lib/puma/client.rb b/lib/puma/client.rb
+index e966f995e8..9c11912caa 100644
+--- a/lib/puma/client.rb
++++ b/lib/puma/client.rb
+@@ -45,7 +45,8 @@ class Client
+
+ # chunked body validation
+ CHUNK_SIZE_INVALID = /[^\h]/.freeze
+- CHUNK_VALID_ENDING = "\r\n".freeze
++ CHUNK_VALID_ENDING = Const::LINE_END
++ CHUNK_VALID_ENDING_SIZE = CHUNK_VALID_ENDING.bytesize
+
+ # Content-Length header value validation
+ CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
+@@ -347,8 +348,8 @@ def setup_body
+ cl = @env[CONTENT_LENGTH]
+
+ if cl
+- # cannot contain characters that are not \d
+- if cl =~ CONTENT_LENGTH_VALUE_INVALID
++ # cannot contain characters that are not \d, or be empty
++ if cl =~ CONTENT_LENGTH_VALUE_INVALID || cl.empty?
+ raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
+ end
+ else
+@@ -509,7 +510,7 @@ def decode_chunk(chunk)
+
+ while !io.eof?
+ line = io.gets
+- if line.end_with?("\r\n")
++ if line.end_with?(CHUNK_VALID_ENDING)
+ # Puma doesn't process chunk extensions, but should parse if they're
+ # present, which is the reason for the semicolon regex
+ chunk_hex = line.strip[/\A[^;]+/]
+@@ -521,13 +522,19 @@ def decode_chunk(chunk)
+ @in_last_chunk = true
+ @body.rewind
+ rest = io.read
+- last_crlf_size = "\r\n".bytesize
+- if rest.bytesize < last_crlf_size
++ if rest.bytesize < CHUNK_VALID_ENDING_SIZE
+ @buffer = nil
+- @partial_part_left = last_crlf_size - rest.bytesize
++ @partial_part_left = CHUNK_VALID_ENDING_SIZE - rest.bytesize
+ return false
+ else
+- @buffer = rest[last_crlf_size..-1]
++ # if the next character is a CRLF, set buffer to everything after that CRLF
++ start_of_rest = if rest.start_with?(CHUNK_VALID_ENDING)
++ CHUNK_VALID_ENDING_SIZE
++ else # we have started a trailer section, which we do not support. skip it!
++ rest.index(CHUNK_VALID_ENDING*2) + CHUNK_VALID_ENDING_SIZE*2
++ end
++
++ @buffer = rest[start_of_rest..-1]
+ @buffer = nil if @buffer.empty?
+ set_ready
+ return true
+diff --git a/test/test_puma_server.rb b/test/test_puma_server.rb
+index 298e44b439..2bfaf98848 100644
+--- a/test/test_puma_server.rb
++++ b/test/test_puma_server.rb
+@@ -627,7 +627,7 @@ def test_large_chunked_request
+ [200, {}, [""]]
+ }
+
+- header = "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n"
++ header = "GET / HTTP/1.1\r\nConnection: close\r\nContent-Length: 200\r\nTransfer-Encoding: chunked\r\n\r\n"
+
+ chunk_header_size = 6 # 4fb8\r\n
+ # Current implementation reads one chunk of CHUNK_SIZE, then more chunks of size 4096.
+@@ -1365,4 +1365,44 @@ def test_rack_url_scheme_user
+ data = send_http_and_read "GET / HTTP/1.0\r\n\r\n"
+ assert_equal "user", data.split("\r\n").last
+ end
++
++ def test_cl_empty_string
++ server_run do |env|
++ [200, {}, [""]]
++ end
++
++ empty_cl_request = "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length:\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
++
++ data = send_http_and_read empty_cl_request
++ assert_operator data, :start_with?, 'HTTP/1.1 400 Bad Request'
++ end
++
++ def test_crlf_trailer_smuggle
++ server_run do |env|
++ [200, {}, [""]]
++ end
++
++ smuggled_payload = "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\nHost: whatever\r\n\r\n0\r\nX:POST / HTTP/1.1\r\nHost: whatever\r\n\r\nGET / HTTP/1.1\r\nHost: whatever\r\n\r\n"
++
++ data = send_http_and_read smuggled_payload
++ assert_equal 2, data.scan("HTTP/1.1 200 OK").size
++ end
++
++ # test to check if content-length is ignored when 'transfer-encoding: chunked'
++ # is used. See also test_large_chunked_request
++ def test_cl_and_te_smuggle
++ body = nil
++ server_run { |env|
++ body = env['rack.input'].read
++ [200, {}, [""]]
++ }
++
++ req = "POST /search HTTP/1.1\r\nHost: vulnerable-website.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 4\r\nTransfer-Encoding: chunked\r\n\r\n7b\r\nGET /404 HTTP/1.1\r\nHost: vulnerable-website.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 144\r\n\r\nx=\r\n0\r\n\r\n"
++
++ data = send_http_and_read req
++
++ assert_includes body, "GET /404 HTTP/1.1\r\n"
++ assert_includes body, "Content-Length: 144\r\n"
++ assert_equal 1, data.scan("HTTP/1.1 200 OK").size
++ end
+ end
diff -Nru puma-5.6.5/debian/patches/CVE-2024-21647.patch puma-5.6.5/debian/patches/CVE-2024-21647.patch
--- puma-5.6.5/debian/patches/CVE-2024-21647.patch 1970-01-01 05:30:00.000000000 +0530
+++ puma-5.6.5/debian/patches/CVE-2024-21647.patch 2024-12-31 11:37:42.000000000 +0530
@@ -0,0 +1,93 @@
+From bbb880ffb6debbfdea535b4b3eb2204d49ae151d Mon Sep 17 00:00:00 2001
+From: Nate Berkopec <nate.berkopec at gmail.com>
+Date: Mon, 8 Jan 2024 14:48:43 +0900
+Subject: [PATCH] Merge pull request from GHSA-c2f4-cvqm-65w2
+
+Co-authored-by: MSP-Greg <MSP-Greg at users.noreply.github.com>
+Co-authored-by: Patrik Ragnarsson <patrik at starkast.net>
+Co-authored-by: Evan Phoenix <evan at phx.io>
+---
+ lib/puma/client.rb | 27 +++++++++++++++++++++++++++
+ test/test_puma_server.rb | 14 ++++++++++++++
+ 2 files changed, 41 insertions(+)
+
+--- a/lib/puma/client.rb
++++ b/lib/puma/client.rb
+@@ -48,6 +48,14 @@ module Puma
+ 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 @@ module Puma
+ @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 @@ 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)
+@@ -568,6 +591,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
+--- a/test/test_puma_server.rb
++++ b/test/test_puma_server.rb
+@@ -648,6 +648,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\n\r\n", data
++ end
++
+ def test_chunked_request_pause_before_value
+ body = nil
+ content_length = nil
diff -Nru puma-5.6.5/debian/patches/CVE-2024-45614.patch puma-5.6.5/debian/patches/CVE-2024-45614.patch
--- puma-5.6.5/debian/patches/CVE-2024-45614.patch 1970-01-01 05:30:00.000000000 +0530
+++ puma-5.6.5/debian/patches/CVE-2024-45614.patch 2024-12-31 14:00:04.000000000 +0530
@@ -0,0 +1,195 @@
+From cac3fd18cf29ed43719ff5d52d9cfec215f0a043 Mon Sep 17 00:00:00 2001
+From: Evan Phoenix <evan at phx.io>
+Date: Wed, 18 Sep 2024 21:56:07 -0700
+Subject: [PATCH] Merge commit from fork
+
+* Prevent underscores from clobbering hyphen headers
+
+* Special case encoding headers to prevent app confusion
+
+* Handle _ as , in jruby as well
+
+* Silence RuboCop offense
+
+---------
+
+Co-authored-by: Patrik Ragnarsson <patrik at starkast.net>
+---
+ ext/puma_http11/org/jruby/puma/Http11.java | 2 +
+ lib/puma/const.rb | 8 +++
+ lib/puma/request.rb | 19 ++++++--
+ test/test_normalize.rb | 57 ++++++++++++++++++++++
+ test/test_request_invalid.rb | 28 +++++++++++
+ 5 files changed, 111 insertions(+), 3 deletions(-)
+ create mode 100644 test/test_normalize.rb
+
+--- a/ext/puma_http11/org/jruby/puma/Http11.java
++++ b/ext/puma_http11/org/jruby/puma/Http11.java
+@@ -99,6 +99,8 @@ public class Http11 extends RubyObject {
+ int bite = b.get(i) & 0xFF;
+ if(bite == '-') {
+ b.set(i, (byte)'_');
++ } else if(bite == '_') {
++ b.set(i, (byte)',');
+ } else {
+ b.set(i, (byte)Character.toUpperCase(bite));
+ }
+--- a/lib/puma/const.rb
++++ b/lib/puma/const.rb
+@@ -244,6 +244,14 @@ module Puma
+ # header values can contain HTAB?
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
+
++ # The keys of headers that should not be convert to underscore
++ # normalized versions. These headers are ignored at the request reading layer,
++ # but if we normalize them after reading, it's just confusing for the application.
++ UNMASKABLE_HEADERS = {
++ "HTTP_TRANSFER,ENCODING" => true,
++ "HTTP_CONTENT,LENGTH" => true,
++ }
++
+ # Banned keys of response header
+ BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
+
+--- a/lib/puma/request.rb
++++ b/lib/puma/request.rb
+@@ -318,6 +318,11 @@ module Puma
+ # compatibility, we'll convert them back. This code is written to
+ # avoid allocation in the common case (ie there are no headers
+ # with `,` in their names), that's why it has the extra conditionals.
++ #
++ # @note If a normalized version of a `,` header already exists, we ignore
++ # the `,` version. This prevents clobbering headers managed by proxies
++ # but not by clients (Like X-Forwarded-For).
++ #
+ # @param env [Hash] see Puma::Client#env, from request, modifies in place
+ # @version 5.0.3
+ #
+@@ -326,23 +331,30 @@ module Puma
+ to_add = nil
+
+ env.each do |k,v|
+- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
++ if k.start_with?("HTTP_") and k.include?(",") and !UNMASKABLE_HEADERS.key?(k)
+ if to_delete
+ to_delete << k
+ else
+ to_delete = [k]
+ end
+
++ new_k = k.tr(",", "_")
++ if env.key?(new_k)
++ next
++ end
++
+ unless to_add
+ to_add = {}
+ end
+
+- to_add[k.tr(",", "_")] = v
++ to_add[new_k] = v
+ end
+ end
+
+ if to_delete
+ to_delete.each { |k| env.delete(k) }
++ end
++ if to_add
+ env.merge! to_add
+ end
+ end
+--- /dev/null
++++ b/test/test_normalize.rb
+@@ -0,0 +1,57 @@
++# frozen_string_literal: true
++
++require_relative "helper"
++
++require "puma/request"
++
++class TestNormalize < Minitest::Test
++ parallelize_me!
++
++ include Puma::Request
++
++ def test_comma_headers
++ env = {
++ "HTTP_X_FORWARDED_FOR" => "1.1.1.1",
++ "HTTP_X_FORWARDED,FOR" => "2.2.2.2",
++ }
++
++ req_env_post_parse env
++
++ expected = {
++ "HTTP_X_FORWARDED_FOR" => "1.1.1.1",
++ }
++
++ assert_equal expected, env
++
++ # Test that the iteration order doesn't matter
++
++ env = {
++ "HTTP_X_FORWARDED,FOR" => "2.2.2.2",
++ "HTTP_X_FORWARDED_FOR" => "1.1.1.1",
++ }
++
++ req_env_post_parse env
++
++ expected = {
++ "HTTP_X_FORWARDED_FOR" => "1.1.1.1",
++ }
++
++ assert_equal expected, env
++ end
++
++ def test_unmaskable_headers
++ env = {
++ "HTTP_CONTENT,LENGTH" => "100000",
++ "HTTP_TRANSFER,ENCODING" => "chunky"
++ }
++
++ req_env_post_parse env
++
++ expected = {
++ "HTTP_CONTENT,LENGTH" => "100000",
++ "HTTP_TRANSFER,ENCODING" => "chunky"
++ }
++
++ assert_equal expected, env
++ end
++end
+--- a/test/test_request_invalid.rb
++++ b/test/test_request_invalid.rb
+@@ -216,4 +216,32 @@ class TestRequestInvalid < Minitest::Tes
+
+ assert_status data
+ end
++
++ def test_underscore_header_1
++ hdrs = [
++ "X-FORWARDED-FOR: 1.1.1.1", # proper
++ "X-FORWARDED-FOR: 2.2.2.2", # proper
++ "X_FORWARDED-FOR: 3.3.3.3", # invalid, contains underscore
++ "Content-Length: 5",
++ ].join "\r\n"
++
++ response = send_http_and_read "#{GET_PREFIX}#{hdrs}\r\n\r\nHello\r\n\r\n"
++
++ assert_includes response, "HTTP_X_FORWARDED_FOR = 1.1.1.1, 2.2.2.2"
++ refute_includes response, "3.3.3.3"
++ end
++
++ def test_underscore_header_2
++ hdrs = [
++ "X_FORWARDED-FOR: 3.3.3.3", # invalid, contains underscore
++ "X-FORWARDED-FOR: 2.2.2.2", # proper
++ "X-FORWARDED-FOR: 1.1.1.1", # proper
++ "Content-Length: 5",
++ ].join "\r\n"
++
++ response = send_http_and_read "#{GET_PREFIX}#{hdrs}\r\n\r\nHello\r\n\r\n"
++
++ assert_includes response, "HTTP_X_FORWARDED_FOR = 2.2.2.2, 1.1.1.1"
++ refute_includes response, "3.3.3.3"
++ end
+ end
diff -Nru puma-5.6.5/debian/patches/series puma-5.6.5/debian/patches/series
--- puma-5.6.5/debian/patches/series 2023-02-09 20:54:05.000000000 +0530
+++ puma-5.6.5/debian/patches/series 2024-12-31 11:54:18.000000000 +0530
@@ -3,3 +3,6 @@
0012-disable-cli-ssl-tests.patch
0013-fix-test-term-not-accepts-new-connections.patch
0014-disable-test-failing-on-amd64.patch
+CVE-2023-40175.patch
+CVE-2024-21647.patch
+CVE-2024-45614.patch
More information about the Pkg-ruby-extras-maintainers
mailing list