File rubygem-rack-CVE-2025-46727.patch of Package rubygem-rack.38666
Index: rack-2.0.8/README.rdoc
===================================================================
--- rack-2.0.8.orig/README.rdoc
+++ rack-2.0.8/README.rdoc
@@ -182,6 +182,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 a single parameter key to take up.
Index: rack-2.0.8/lib/rack/query_parser.rb
===================================================================
--- rack-2.0.8.orig/lib/rack/query_parser.rb
+++ rack-2.0.8/lib/rack/query_parser.rb
@@ -12,16 +12,47 @@ module Rack
# sequence.
class InvalidParameterError < ArgumentError; end
- def self.make_default(key_space_limit, param_depth_limit)
- new Params, key_space_limit, param_depth_limit
+ # 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, **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:
@@ -34,7 +65,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('='.freeze, 2).map!(&unescaper)
@@ -146,8 +177,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
Index: rack-2.0.8/test/spec_request.rb
===================================================================
--- rack-2.0.8.orig/test/spec_request.rb
+++ rack-2.0.8/test/spec_request.rb
@@ -1391,6 +1391,10 @@ EOF
class NonDelegate < Rack::Request
def delegate?; false; end
+
+ def query_parser
+ Rack::QueryParser.make_default(Rack::Utils.key_space_limit, Rack::Utils.param_depth_limit, bytesize_limit: 2**30)
+ end
end
def make_request(env)
@@ -1414,6 +1418,10 @@ EOF
def delegate?; true; end
def env; @req.env.dup; end
+
+ def query_parser
+ Rack::QueryParser.make_default(Rack::Utils.key_space_limit, Rack::Utils.param_depth_limit, bytesize_limit: 2**30)
+ end
end
def make_request(env)