Skip to content

Commit 623b0cc

Browse files
kratobxanwerneck
authored andcommitted
rack: backport fix for CVE-2025-61919
1 parent faadf7e commit 623b0cc

3 files changed

Lines changed: 92 additions & 2 deletions

File tree

lib/rack/request.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,10 @@ def POST
209209
@env["rack.request.form_hash"]
210210
elsif form_data? || parseable_data?
211211
unless @env["rack.request.form_hash"] = parse_multipart(env)
212-
form_vars = @env["rack.input"].read
212+
# Add 2 bytes. One to check whether it is over the limit, and a second
213+
# in case the slice! call below removes the last byte
214+
# If read returns nil, use the empty string
215+
form_vars = @env["rack.input"].read(Rack::Utils.bytesize_limit + 2) || ''
213216

214217
# Fix for Safari Ajax postings that always append \0
215218
# form_vars.sub!(/\0\z/, '') # performance replacement:

lib/rack/utils.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ class << self
115115
def check_query_string(qs, sep)
116116
if qs
117117
if qs.bytesize > Rack::Utils.bytesize_limit
118-
raise QueryLimitError, "total query size (#{qs.bytesize}) exceeds limit (#{Rack::Utils.bytesize_limit})"
118+
raise QueryLimitError, "total query size exceeds limit (#{Rack::Utils.bytesize_limit})"
119119
end
120120

121121
if (param_count = qs.count(sep.is_a?(String) ? sep : '&')) >= Rack::Utils.params_limit

test/spec_request.rb

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,93 @@
290290
req.POST.should.equal "foo" => "bar", "quux" => "bla"
291291
end
292292

293+
should "limit POST body read to bytesize_limit when parsing url-encoded data" do
294+
# Create a mock input that tracks read calls
295+
reads = []
296+
mock_input = Object.new
297+
mock_input.define_singleton_method(:read) do |len=nil|
298+
reads << len
299+
# Return mutable string
300+
"foo=bar".dup
301+
end
302+
mock_input.define_singleton_method(:rewind) do
303+
# no-op for compatibility
304+
end
305+
306+
request = Rack::Request.new \
307+
Rack::MockRequest.env_for("/",
308+
'REQUEST_METHOD' => 'POST',
309+
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
310+
:input => mock_input)
311+
312+
request.POST.should.equal "foo" => "bar"
313+
314+
# Verify read was called with a limit (bytesize_limit + 2), not nil
315+
reads.size.should.equal 1
316+
reads.first.should.not.be.nil
317+
reads.first.should.equal(Rack::Utils.bytesize_limit + 2)
318+
end
319+
320+
should "handle nil return from rack.input.read when parsing url-encoded data" do
321+
# Simulate an input that returns nil on read
322+
mock_input = Object.new
323+
mock_input.define_singleton_method(:read) do |len=nil|
324+
nil
325+
end
326+
mock_input.define_singleton_method(:rewind) do
327+
# no-op for compatibility
328+
end
329+
330+
request = Rack::Request.new \
331+
Rack::MockRequest.env_for("/",
332+
'REQUEST_METHOD' => 'POST',
333+
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
334+
:input => mock_input)
335+
336+
# Should handle nil gracefully and return empty hash
337+
request.POST.should.equal({})
338+
end
339+
340+
should "truncate POST body at bytesize_limit when parsing url-encoded data" do
341+
# Create input larger than limit
342+
large_body = "a=1&" * 1000000 # Very large body
343+
344+
request = Rack::Request.new \
345+
Rack::MockRequest.env_for("/",
346+
'REQUEST_METHOD' => 'POST',
347+
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
348+
:input => large_body)
349+
350+
# Should parse only up to the limit without reading entire body into memory
351+
# The actual parsing may fail due to size limit, which is expected
352+
proc { request.POST }.should.raise Rack::Utils::QueryLimitError
353+
end
354+
355+
should "clean up Safari's ajax POST body with limited read" do
356+
# Verify Safari null-byte cleanup still works with bounded read
357+
reads = []
358+
mock_input = Object.new
359+
mock_input.define_singleton_method(:read) do |len=nil|
360+
reads << len
361+
# Return mutable string (dup ensures it's not frozen)
362+
"foo=bar\0".dup
363+
end
364+
mock_input.define_singleton_method(:rewind) do
365+
# no-op for compatibility
366+
end
367+
368+
request = Rack::Request.new \
369+
Rack::MockRequest.env_for("/",
370+
'REQUEST_METHOD' => 'POST',
371+
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
372+
:input => mock_input)
373+
374+
request.POST.should.equal "foo" => "bar"
375+
376+
# Verify bounded read was used
377+
reads.first.should.not.be.nil
378+
end
379+
293380
should "get value by key from params with #[]" do
294381
req = Rack::Request.new \
295382
Rack::MockRequest.env_for("?foo=quux")

0 commit comments

Comments
 (0)