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)
openSUSE Build Service is sponsored by