diff --git a/Fix-CVE-2025-46727.patch b/Fix-CVE-2025-46727.patch new file mode 100644 index 0000000000000000000000000000000000000000..3177541dec23435e41038dd62c46829b7e4aca48 --- /dev/null +++ b/Fix-CVE-2025-46727.patch @@ -0,0 +1,176 @@ +From 3f5a4249118d09d199fe480466c8c6717e43b6e3 Mon Sep 17 00:00:00 2001 +From: Jeremy Evans +Date: Tue, 6 May 2025 19:08:08 +0900 +Subject: [PATCH] Merge commit from fork + +* Apply bytesize and number of param limits in QueryParser + +The param limit is 4096, chosen because it matches the existing +multipart limit. The bytesize limit is 4MB. These limits should +substantially exceed what almost all applications need, though +there will likely be applications that require higher limits. +Allow overriding the limits on a per-QueryParser basis via the +constructors, and allow overriding the default limits with +environment variables RACK_QUERY_PARSER_BYTESIZE_LIMIT and +RACK_QUERY_PARSER_PARAMS_LIMIT. + +Add new Rack::QueryParser::QueryLimitError to raise in case one +of the limits are exceeded, and make ParamsTooDeepError an +alias to, since that is also a case where a limit is exceeded. +This allows code that already rescues ParamsTooDeepError to +automatically handle these other limits as well. + +* Update CHANGELOG. + +--------- + +Co-authored-by: Samuel Williams +--- + README.rdoc | 27 +++++++++++++++++ + lib/rack/query_parser.rb | 63 ++++++++++++++++++++++++++++++++------- + 2 files changed, 80 insertions(+), 10 deletions(-) + +diff --git a/README.rdoc b/README.rdoc +index cbb25723..6f678ea6 100644 +--- a/README.rdoc ++++ b/README.rdoc +@@ -179,6 +179,33 @@ e.g: + + Rack::Utils.key_space_limit = 128 + ++=== `RACK_QUERY_PARSER_BYTESIZE_LIMIT` ++ ++This environment variable sets the default for the maximum query string bytesize ++that `Rack::QueryParser` will attempt to parse. Attempts to use a query string ++that exceeds this number of bytes will result in a ++`Rack::QueryParser::QueryLimitError` exception. If this enviroment variable is ++provided, it must be an integer, or `Rack::QueryParser` will raise an exception. ++ ++The default limit can be overridden on a per-`Rack::QueryParser` basis using ++the `bytesize_limit` keyword argument when creating the `Rack::QueryParser`. ++ ++=== `RACK_QUERY_PARSER_PARAMS_LIMIT` ++ ++This environment variable sets the default for the maximum number of query ++parameters that `Rack::QueryParser` will attempt to parse. Attempts to use a ++query string with more than this many query parameters will result in a ++`Rack::QueryParser::QueryLimitError` exception. If this enviroment variable is ++provided, it must be an integer, or `Rack::QueryParser` will raise an exception. ++ ++The default limit can be overridden on a per-`Rack::QueryParser` basis using ++the `params_limit` keyword argument when creating the `Rack::QueryParser`. ++ ++This is implemented by counting the number of parameter separators in the ++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. ++ + === 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/query_parser.rb b/lib/rack/query_parser.rb +index 1c3923c3..a6f6d68c 100644 +--- a/lib/rack/query_parser.rb ++++ b/lib/rack/query_parser.rb +@@ -16,20 +16,47 @@ module Rack + # sequence. + class InvalidParameterError < ArgumentError; end + +- # ParamsTooDeepError is the error that is raised when params are recursively +- # nested over the specified limit. +- class ParamsTooDeepError < RangeError; end ++ # QueryLimitError is for errors raised when the query provided exceeds one ++ # of the query parser limits. ++ class QueryLimitError < RangeError ++ end ++ ++ # ParamsTooDeepError is the old name for the error that is raised when params ++ # are recursively nested over the specified limit. Make it the same as ++ # as QueryLimitError, so that code that rescues ParamsTooDeepError error ++ # to handle bad query strings also now handles other limits. ++ ParamsTooDeepError = QueryLimitError + +- def self.make_default(key_space_limit, param_depth_limit) +- new Params, key_space_limit, param_depth_limit ++ def self.make_default(key_space_limit, param_depth_limit, **options) ++ new(Params, key_space_limit, param_depth_limit, **options) + end + + attr_reader :key_space_limit, :param_depth_limit + +- def initialize(params_class, key_space_limit, param_depth_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 ++ ++ BYTESIZE_LIMIT = env_int.call("RACK_QUERY_PARSER_BYTESIZE_LIMIT", 4194304) ++ private_constant :BYTESIZE_LIMIT ++ ++ PARAMS_LIMIT = env_int.call("RACK_QUERY_PARSER_PARAMS_LIMIT", 4096) ++ private_constant :PARAMS_LIMIT ++ ++ def initialize(params_class, key_space_limit, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT) + @params_class = params_class + @key_space_limit = key_space_limit + @param_depth_limit = param_depth_limit ++ @bytesize_limit = bytesize_limit ++ @params_limit = params_limit + end + + # Stolen from Mongrel, with some small modifications: +@@ -42,7 +69,7 @@ module Rack + + params = make_params + +- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p| ++ check_query_string(qs, d).split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p| + next if p.empty? + k, v = p.split('=', 2).map!(&unescaper) + +@@ -69,7 +96,7 @@ module Rack + params = make_params + + unless qs.nil? || qs.empty? +- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p| ++ check_query_string(qs, d).split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p| + k, v = p.split('=', 2).map! { |s| unescape(s) } + + normalize_params(params, k, v, param_depth_limit) +@@ -155,8 +182,24 @@ module Rack + true + end + +- def unescape(s) +- Utils.unescape(s) ++ def check_query_string(qs, sep) ++ if qs ++ if qs.bytesize > @bytesize_limit ++ raise QueryLimitError, "total query size (#{qs.bytesize}) exceeds limit (#{@bytesize_limit})" ++ end ++ ++ if (param_count = qs.count(sep.is_a?(String) ? sep : '&')) >= @params_limit ++ raise QueryLimitError, "total number of query parameters (#{param_count+1}) exceeds limit (#{@params_limit})" ++ end ++ ++ qs ++ else ++ '' ++ end ++ end ++ ++ def unescape(string, encoding = Encoding::UTF_8) ++ Utils.unescape(string, encoding) + end + + class Params +-- +2.25.1 + diff --git a/rubygem-rack.spec b/rubygem-rack.spec index 951f923a3fe4ce8f45f0840785b5da223c43f764..76e6592051622c409b1d2ecad5b7a8a9f0f023b5 100644 --- a/rubygem-rack.spec +++ b/rubygem-rack.spec @@ -4,7 +4,7 @@ Name: rubygem-%{gem_name} Version: 2.2.4 Epoch: 1 -Release: 11 +Release: 12 Summary: A modular Ruby webserver interface License: MIT and BSD URL: https://rack.github.io/ @@ -20,6 +20,7 @@ Patch7: Fix-CVE-2022-44572.patch Patch8: Fix-CVE-2025-27610.patch Patch9: Fix-CVE-2025-27111.patch Patch10: Fix-CVE-2025-25184.patch +Patch11: Fix-CVE-2025-46727.patch BuildRequires: ruby(release) rubygems-devel ruby >= 2.2.2 git BuildRequires: memcached rubygem(memcache-client) rubygem(minitest) BuildRequires: rubygem(memcache-client) @@ -108,6 +109,12 @@ popd %doc %{gem_instdir}/contrib %changelog +* Wed Aug 20 2025 zouzhimin - 1:2.2.4-12 +- Type:CVES +- ID:CVE-2025-46727 +- SUG:NA +- DESC:CVE-2025-46727 + * Tue Mar 18 2025 changtao - 1:2.2.4-11 - Type:CVE - CVE:CVE-2025-25184