diff --git a/Fix-CVE-2025-61771.patch b/Fix-CVE-2025-61771.patch new file mode 100644 index 0000000000000000000000000000000000000000..5c2b75d2532bdca44bf6123a27b0360a5c713843 --- /dev/null +++ b/Fix-CVE-2025-61771.patch @@ -0,0 +1,127 @@ +From c370dcd9405a6799763b70a83f06ae2d1aaa0e87 Mon Sep 17 00:00:00 2001 +From: Jeremy Evans +Date: Mon, 15 Sep 2025 19:10:49 -0700 +Subject: [PATCH] Limit amount of retained data when parsing multipart requests + +The limit is 16MB by default, and it can be adjusted with the +RACK_MULTIPART_MAX_BUFFERED_UPLOAD_SIZE environment variable. + +Data stored in temporary files is not counted against this limit. +However data for other parameters, as well as the data for the +mime headers for each parameter (which is retained during parsing) +is counted against the limit. +--- + README.rdoc | 8 +++ + lib/rack/multipart/parser.rb | 42 ++++++++++++++- + 2 files changed, 49 insertions(+), 1 deletion(-) + +diff --git a/README.rdoc b/README.rdoc +index 6f678ea6..71c46ae6 100644 +--- a/README.rdoc ++++ b/README.rdoc +@@ -206,6 +206,14 @@ query string, before attempting parsing, so if the same parameter key is + used multiple times in the query, each counts as a separate parameter for + this check. + ++=== `RACK_MULTIPART_BUFFERED_UPLOAD_BYTESIZE_LIMIT` ++ ++This environment variable sets the maximum amount of memory Rack will use ++to buffer multipart parameters when parsing a request body. This considers ++the size of the multipart mime headers and the body part for multipart ++parameters that are buffered in memory and do not use tempfiles. This ++defaults to 16MB if not provided. ++ + === key_space_limit + + The default number of bytes to allow all parameters keys in a given parameter hash to take up. +diff --git a/lib/rack/multipart/parser.rb b/lib/rack/multipart/parser.rb +index 0cb5d347..ba7b4794 100644 +--- a/lib/rack/multipart/parser.rb ++++ b/lib/rack/multipart/parser.rb +@@ -26,6 +26,21 @@ module Rack + MIME_HEADER_BYTESIZE_LIMIT = 64 * 1024 + private_constant :MIME_HEADER_BYTESIZE_LIMIT + ++ env_int = lambda do |key, val| ++ if str_val = ENV[key] ++ begin ++ val = Integer(str_val, 10) ++ rescue ArgumentError ++ raise ArgumentError, "non-integer value provided for environment variable #{key}" ++ end ++ end ++ ++ val ++ end ++ ++ BUFFERED_UPLOAD_BYTESIZE_LIMIT = env_int.call("RACK_MULTIPART_BUFFERED_UPLOAD_BYTESIZE_LIMIT", 16 * 1024 * 1024) ++ private_constant :BUFFERED_UPLOAD_BYTESIZE_LIMIT ++ + class BoundedIO # :nodoc: + def initialize(io, content_length) + @io = io +@@ -193,6 +208,8 @@ module Rack + @end_boundary = @boundary + '--' + @state = :FAST_FORWARD + @mime_index = 0 ++ @body_retained = nil ++ @retained_size = 0 + @collector = Collector.new tempfile + + @sbuf = StringScanner.new("".dup) +@@ -283,6 +300,15 @@ module Rack + name = filename || "#{content_type || TEXT_PLAIN}[]".dup + end + ++ # Mime part head data is retained for both TempfilePart and BufferPart ++ # for the entireity of the parse, even though it isn't used for BufferPart. ++ update_retained_size(head.bytesize) ++ ++ # If a filename is given, a TempfilePart will be used, so the body will ++ # not be buffered in memory. However, if a filename is not given, a BufferPart ++ # will be used, and the body will be buffered in memory. ++ @body_retained = !filename ++ + @collector.on_mime_head @mime_index, head, filename, content_type, name + @state = :MIME_BODY + else +@@ -297,6 +323,7 @@ module Rack + def handle_mime_body + if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet + body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string ++ update_retained_size(body.bytesize) if @body_retained + @collector.on_mime_body @mime_index, body + @sbuf.pos += body.length + 2 # skip \r\n after the content + @state = :CONSUME_TOKEN +@@ -305,7 +332,9 @@ module Rack + # Save what we have so far + if @rx_max_size < @sbuf.rest_size + delta = @sbuf.rest_size - @rx_max_size +- @collector.on_mime_body @mime_index, @sbuf.peek(delta) ++ body = @sbuf.peek(delta) ++ update_retained_size(body.bytesize) if @body_retained ++ @collector.on_mime_body @mime_index, body + @sbuf.pos += delta + @sbuf.string = @sbuf.rest + end +@@ -315,6 +344,17 @@ module Rack + + def full_boundary; @full_boundary; end + ++ def update_retained_size(size) ++ @retained_size += size ++ if @retained_size > BUFFERED_UPLOAD_BYTESIZE_LIMIT ++ raise EOFError, "multipart data over retained size limit" ++ end ++ end ++ ++ # Scan until the we find the start or end of the boundary. ++ # If we find it, return the appropriate symbol for the start or ++ # end of the boundary. If we don't find the start or end of the ++ # boundary, clear the buffer and return nil. + def consume_boundary + while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX) + case read_buffer.strip +-- +2.43.0 + diff --git a/rubygem-rack.spec b/rubygem-rack.spec index 5abcb7c5675e0b437e110edc5c64cb698e620b69..666d21b17527fdf79c88bb90b45e2864b7081e94 100644 --- a/rubygem-rack.spec +++ b/rubygem-rack.spec @@ -4,7 +4,7 @@ Name: rubygem-%{gem_name} Version: 2.2.4 Epoch: 1 -Release: 14 +Release: 15 Summary: A modular Ruby webserver interface License: MIT and BSD URL: https://rack.github.io/ @@ -24,6 +24,7 @@ Patch11: Fix-CVE-2025-46727.patch Patch12: Fix-CVE-2025-59830.patch Patch13: Fix-CVE-2025-61919.patch Patch14: Fix-CVE-2025-61772.patch +Patch15: Fix-CVE-2025-61771.patch BuildRequires: ruby(release) rubygems-devel ruby >= 2.2.2 git BuildRequires: memcached rubygem(memcache-client) rubygem(minitest) BuildRequires: rubygem(memcache-client) @@ -112,6 +113,12 @@ popd %doc %{gem_instdir}/contrib %changelog +* Mon Nov 10 2025 wangziliang - 1:2.2.4-15 +- Type:CVES +- ID:CVE-2025-61771 +- SUG:NA +- DESC:CVE-2025-61771 + * Thu Oct 09 2025 wangziliang - 1:2.2.4-14 - Type:CVES - ID:CVE-2025-61772