@@ -30,6 +30,10 @@ module Utils
30
30
'Jul' , 'Aug' , 'Sep' , 'Oct' , 'Nov' , 'Dec' ,
31
31
] . freeze
32
32
33
+ # QueryLimitError is for errors raised when the query provided exceeds one
34
+ # of the query parser limits.
35
+ class QueryLimitError < RangeError ; end
36
+
33
37
# URI escapes. (CGI style space to +)
34
38
def escape ( s )
35
39
URI . encode_www_form_component ( s )
@@ -63,6 +67,8 @@ class << self
63
67
attr_accessor :param_depth_limit
64
68
attr_accessor :multipart_total_part_limit
65
69
attr_accessor :multipart_file_limit
70
+ attr_accessor :bytesize_limit # CVE-2025-46727
71
+ attr_accessor :params_limit # CVE-2025-46727
66
72
67
73
# multipart_part_limit is the original name of multipart_file_limit, but
68
74
# the limit only counts parts with filenames.
@@ -87,6 +93,34 @@ class << self
87
93
# many can lead to excessive memory use and parsing time.
88
94
self . multipart_total_part_limit = ( ENV [ 'RACK_MULTIPART_TOTAL_PART_LIMIT' ] || 4096 ) . to_i
89
95
96
+ # This sets the default for the maximum query string bytesize that we will attempt to parse.
97
+ # Attempts to use a query string that exceeds this number of bytes will result in a
98
+ # `Rack::Utils::QueryLimitError` exception.
99
+ self . bytesize_limit = ( ENV [ 'RACK_QUERY_PARSER_BYTESIZE_LIMIT' ] || 4194304 ) . to_i
100
+
101
+ # This variable sets the default for the maximum number of query
102
+ # parameters that we will attempt to parse. Attempts to use a
103
+ # query string with more than this many query parameters will result in a
104
+ # `Rack::Utils::QueryLimitError` exception.
105
+ self . params_limit = ( ENV [ 'RACK_QUERY_PARSER_PARAMS_LIMIT' ] || 4096 ) . to_i
106
+
107
+ def check_query_string ( qs , sep )
108
+ if qs
109
+ if qs . bytesize > Rack ::Utils . bytesize_limit
110
+ raise QueryLimitError , "total query size (#{ qs . bytesize } ) exceeds limit (#{ Rack ::Utils . bytesize_limit } )"
111
+ end
112
+
113
+ if ( param_count = qs . count ( sep . is_a? ( String ) ? sep : '&' ) ) >= Rack ::Utils . params_limit
114
+ raise QueryLimitError , "total number of query parameters (#{ param_count +1 } ) exceeds limit (#{ Rack ::Utils . params_limit } )"
115
+ end
116
+
117
+ qs
118
+ else
119
+ ''
120
+ end
121
+ end
122
+ module_function :check_query_string
123
+
90
124
# Stolen from Mongrel, with some small modifications:
91
125
# Parses a query string by breaking it up at the '&'
92
126
# and ';' characters. You can also use this to parse
@@ -97,7 +131,7 @@ def parse_query(qs, d = nil, &unescaper)
97
131
98
132
params = KeySpaceConstrainedParams . new
99
133
100
- ( qs || '' ) . split ( d ? /[#{ d } ] */n : DEFAULT_SEP ) . each do |p |
134
+ check_query_string ( qs , d ) . split ( d ? /[#{ d } ] */n : DEFAULT_SEP ) . each do |p |
101
135
next if p . empty?
102
136
k , v = p . split ( '=' , 2 ) . map ( &unescaper )
103
137
next unless k || v
@@ -120,7 +154,7 @@ def parse_query(qs, d = nil, &unescaper)
120
154
def parse_nested_query ( qs , d = nil )
121
155
params = KeySpaceConstrainedParams . new
122
156
123
- ( qs || '' ) . split ( d ? /[#{ d } ] */n : DEFAULT_SEP ) . each do |p |
157
+ check_query_string ( qs , d ) . split ( d ? /[#{ d } ] */n : DEFAULT_SEP ) . each do |p |
124
158
k , v = p . split ( '=' , 2 ) . map { |s | unescape ( s ) }
125
159
126
160
normalize_params ( params , k , v )
0 commit comments