Skip to content

Commit bcdc5f4

Browse files
committed
Merge branch 'lts-1-6-stable' into masquerade-1-6-lts-as-1.4.7-for-rails-3-compat
2 parents 08917e9 + a353c7f commit bcdc5f4

17 files changed

+246
-57
lines changed

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
rack (1.6.13)
4+
rack (1.6.13.14)
55

66
GEM
77
remote: https://rubygems.org/

README.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ Rack applications should conform to.
1212
This is a fork of the official [rack gem](https://github.com/rack/rack) with
1313
backported security fixes for:
1414

15+
* [rack 2.2](https://github.com/rails-lts/rack/tree/lts-2-2-stable)
16+
(CWE-444)
1517
* [rack 1.6](https://github.com/rails-lts/rack/tree/lts-1-6-stable)
16-
(CVE-2020-8161, CVE-2020-8184, CVE-2022-30122, CVE-2022-30123)
18+
(CVE-2020-8161, CVE-2020-8184, CVE-2022-30122, CVE-2022-30123, CWE-444, CVE-2022-44570, CVE-2022-44571, CVE-2023-27530, CVE-2023-27539, CVE-2024-25126, CVE-2024-26141, CVE-2024-26146)
1719
* [rack 1.4](https://github.com/rails-lts/rack/tree/lts-rack-1.4)
18-
(CVE-2018-16471, CVE-2020-8161, CVE-2020-8184, CVE-2022-30122, CVE-2022-30123)
20+
(CVE-2018-16471, CVE-2020-8161, CVE-2020-8184, CVE-2022-30122, CVE-2022-30123, CWE-444, CWE-290, CVE-2022-44570, CVE-2022-44571, CVE-2023-27530, CVE-2023-27539, CVE-2024-25126, CVE-2024-26141, CVE-2024-26146)
1921

2022

2123
To use it, you need to add it to the Gemfile like this:
@@ -216,17 +218,32 @@ helps prevent a rogue client from flooding a Request.
216218

217219
Default to 65536 characters (4 kiB in worst case).
218220

219-
### multipart_part_limit
221+
### multipart_file_limit
220222

221-
The maximum number of parts a request can contain. Accepting too many part can
223+
The maximum number of parts with a filename a request can contain. Accepting too many part can
222224
lead to the server running out of file handles.
223225

224226
The default is 128, which means that a single request can't upload more than
225227
128 files at once.
226228

227229
Set to 0 for no limit.
228230

229-
Can also be set via the RACK_MULTIPART_PART_LIMIT environment variable.
231+
Can also be set via the `RACK_MULTIPART_PART_LIMIT` environment variable.
232+
233+
(This is also aliased as `multipart_part_limit` and `RACK_MULTIPART_PART_LIMIT` for compatibility)
234+
235+
### multipart_total_part_limit
236+
237+
The maximum total number of parts a request can contain of any type, including
238+
both file and non-file form fields.
239+
240+
The default is 4096, which means that a single request can't contain more than
241+
4096 parts.
242+
243+
Set to 0 for no limit.
244+
245+
Can also be set via the `RACK_MULTIPART_TOTAL_PART_LIMIT` environment variable.
246+
230247

231248
## History
232249

Rakefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ task :test => 'SPEC' do
8686
specopts = ENV['TESTOPTS'] ||
8787
"-q -t '^(?!Rack::Adapter|Rack::Session::Memcache|Rack::Server|Rack::Handler)'"
8888

89-
sh "bacon -w -I./lib:./test #{opts} #{specopts}"
89+
sh "bacon -I./lib:./test #{opts} #{specopts}"
9090
end
9191

9292
desc "Run all the tests we run on CI"
@@ -96,7 +96,7 @@ desc "Run all the tests"
9696
task :fulltest => %w[SPEC chmod] do
9797
opts = ENV['TEST'] || '-a'
9898
specopts = ENV['TESTOPTS'] || '-q'
99-
sh "bacon -r./test/gemloader -I./lib:./test -w #{opts} #{specopts}"
99+
sh "bacon -r./test/gemloader -I./lib:./test #{opts} #{specopts}"
100100
end
101101

102102
task :gem => ["SPEC"] do

lib/rack/multipart.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ module Multipart
1717
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
1818
BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
1919
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
20-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name="?([^\";]*)"?/ni
20+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s+name="?([^\";]*)"?/ni
2121
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
2222

2323
class << self

lib/rack/multipart/parser.rb

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
module Rack
44
module Multipart
55
class MultipartPartLimitError < Errno::EMFILE; end
6+
class MultipartTotalPartLimitError < StandardError; end
67

78
class Parser
89
BUFSIZE = 16384
@@ -52,14 +53,25 @@ def parse
5253
fast_forward_to_first_boundary
5354

5455
opened_files = 0
56+
parts = 0
5557
loop do
56-
5758
head, filename, content_type, name, body =
5859
get_current_head_and_filename_and_content_type_and_name_and_body
5960

6061
if Utils.multipart_part_limit > 0
6162
opened_files += 1 if filename
62-
raise MultipartPartLimitError, 'Maximum file multiparts in content reached' if opened_files >= Utils.multipart_part_limit
63+
if opened_files >= Utils.multipart_part_limit
64+
close_tempfiles
65+
raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
66+
end
67+
end
68+
69+
if Utils.multipart_total_part_limit > 0
70+
parts += 1
71+
if parts >= Utils.multipart_total_part_limit
72+
close_tempfiles
73+
raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
74+
end
6375
end
6476

6577
# Save the rest.
@@ -178,6 +190,10 @@ def get_filename(head)
178190
filename
179191
end
180192

193+
def close_tempfiles
194+
(@env['rack.tempfiles'] || []).each(&:close!)
195+
end
196+
181197
if "<3".respond_to? :valid_encoding?
182198
def scrub_filename(filename)
183199
unless filename.valid_encoding?

lib/rack/request.rb

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ def logger; @env['rack.logger'] end
4242
# For more information on the use of media types in HTTP, see:
4343
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
4444
def media_type
45-
content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
45+
if content_type && (type = content_type.split(/[;,]/, 2).first)
46+
type.rstrip!
47+
type.downcase!
48+
type
49+
end
4650
end
4751

4852
# The media type parameters provided in CONTENT_TYPE as a Hash, or
@@ -52,8 +56,8 @@ def media_type
5256
# { 'charset' => 'utf-8' }
5357
def media_type_params
5458
return {} if content_type.nil?
55-
Hash[*content_type.split(/\s*[;,]\s*/)[1..-1].
56-
collect { |s| s.split('=', 2) }.
59+
Hash[*content_type.split(/[;,]/)[1..-1].
60+
collect { |s| s.strip.split('=', 2) }.
5761
map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten]
5862
end
5963

@@ -188,7 +192,7 @@ def GET
188192
if @env["rack.request.query_string"] == query_string
189193
@env["rack.request.query_hash"]
190194
else
191-
p = parse_query({ :query => query_string, :separator => '&;' })
195+
p = parse_query({ :query => query_string, :separator => '&' })
192196
@env["rack.request.query_string"] = query_string
193197
@env["rack.request.query_hash"] = p
194198
end
@@ -383,8 +387,8 @@ def parse_multipart(env)
383387
end
384388

385389
def parse_http_accept_header(header)
386-
header.to_s.split(/\s*,\s*/).map do |part|
387-
attribute, parameters = part.split(/\s*;\s*/, 2)
390+
header.to_s.split(",").each(&:strip!).map do |part|
391+
attribute, parameters = part.split(";", 2).each(&:strip!)
388392
quality = 1.0
389393
if parameters and /\Aq=([\d.]+)/ =~ parameters
390394
quality = $1.to_f

lib/rack/server.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,11 @@ def start &blk
284284
end
285285
end
286286

287-
server.run wrapped_app, options, &blk
287+
if RUBY_VERSION >= '3'
288+
server.run wrapped_app, **options, &blk
289+
else
290+
server.run wrapped_app, options, &blk
291+
end
288292
end
289293

290294
def server

lib/rack/tempfile_reaper.rb

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,24 @@ def initialize(app)
1212

1313
def call(env)
1414
env['rack.tempfiles'] ||= []
15-
status, headers, body = @app.call(env)
15+
16+
begin
17+
status, headers, body = @app.call(env)
18+
rescue Exception
19+
close_tempfiles(env)
20+
raise
21+
end
22+
1623
body_proxy = BodyProxy.new(body) do
17-
env['rack.tempfiles'].each { |f| f.close! } unless env['rack.tempfiles'].nil?
24+
close_tempfiles(env)
1825
end
1926
[status, headers, body_proxy]
2027
end
28+
29+
private
30+
31+
def close_tempfiles(env)
32+
env['rack.tempfiles'].each(&:close!) unless env['rack.tempfiles'].nil?
33+
end
2134
end
2235
end

lib/rack/utils.rb

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,18 @@ def unescape(s, encoding = nil)
5757
end
5858
module_function :unescape
5959

60-
DEFAULT_SEP = /[&;] */n
60+
DEFAULT_SEP = /& */n
6161

6262
class << self
6363
attr_accessor :key_space_limit
6464
attr_accessor :param_depth_limit
65-
attr_accessor :multipart_part_limit
65+
attr_accessor :multipart_total_part_limit
66+
attr_accessor :multipart_file_limit
67+
68+
# multipart_part_limit is the original name of multipart_file_limit, but
69+
# the limit only counts parts with filenames.
70+
alias multipart_part_limit multipart_file_limit
71+
alias multipart_part_limit= multipart_file_limit=
6672
end
6773

6874
# The default number of bytes to allow parameter keys to take up.
@@ -73,11 +79,15 @@ class << self
7379
# being too deep. This helps prevent SystemStackErrors
7480
self.param_depth_limit = 100
7581

76-
# The maximum number of parts a request can contain. Accepting too many part
82+
# The maximum number of file parts a request can contain. Accepting too many parts
7783
# can lead to the server running out of file handles.
7884
# Set to `0` for no limit.
7985
# FIXME: RACK_MULTIPART_LIMIT was introduced by mistake and it will be removed in 1.7.0
80-
self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_LIMIT'] || 128).to_i
86+
self.multipart_file_limit = (ENV['RACK_MULTIPART_FILE_LIMIT'] || ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_LIMIT'] || 128).to_i
87+
88+
# The maximum total number of parts a request can contain. Accepting too
89+
# many can lead to excessive memory use and parsing time.
90+
self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i
8191

8292
# Stolen from Mongrel, with some small modifications:
8393
# Parses a query string by breaking it up at the '&'
@@ -203,7 +213,7 @@ def build_nested_query(value, prefix = nil)
203213
module_function :build_nested_query
204214

205215
def q_values(q_value_header)
206-
q_value_header.to_s.split(/\s*,\s*/).map do |part|
216+
q_value_header.to_s.split(",").each(&:strip!).map do |part|
207217
value, parameters = part.split(/\s*;\s*/, 2)
208218
quality = 1.0
209219
if md = /\Aq=([\d.]+)/.match(parameters)
@@ -393,6 +403,9 @@ def rfc2822(time)
393403
end
394404
module_function :rfc2822
395405

406+
RFC2822_DAY_NAME = defined?(Time::RFC2822_DAY_NAME) ? Time::RFC2822_DAY_NAME : ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
407+
RFC2822_MONTH_NAME = defined?(Time::RFC2822_MONTH_NAME) ? Time::RFC2822_MONTH_NAME : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
408+
396409
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
397410
# of '% %b %Y'.
398411
# It assumes that the time is in GMT to comply to the RFC 2109.
@@ -401,10 +414,9 @@ def rfc2822(time)
401414
# that I'm certain someone implemented only that option.
402415
# Do not use %a and %b from Time.strptime, it would use localized names for
403416
# weekday and month.
404-
#
405417
def rfc2109(time)
406-
wday = Time::RFC2822_DAY_NAME[time.wday]
407-
mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
418+
wday = RFC2822_DAY_NAME[time.wday]
419+
mon = RFC2822_MONTH_NAME[time.mon - 1]
408420
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
409421
end
410422
module_function :rfc2109
@@ -418,17 +430,18 @@ def byte_ranges(env, size)
418430
return nil unless http_range && http_range =~ /bytes=([^;]+)/
419431
ranges = []
420432
$1.split(/,\s*/).each do |range_spec|
421-
return nil unless range_spec =~ /(\d*)-(\d*)/
422-
r0,r1 = $1, $2
423-
if r0.empty?
424-
return nil if r1.empty?
433+
return nil unless range_spec.include?('-')
434+
range = range_spec.split('-')
435+
r0, r1 = range[0], range[1]
436+
if r0.nil? || r0.empty?
437+
return nil if r1.nil?
425438
# suffix-byte-range-spec, represents trailing suffix of file
426439
r0 = size - r1.to_i
427440
r0 = 0 if r0 < 0
428441
r1 = size - 1
429442
else
430443
r0 = r0.to_i
431-
if r1.empty?
444+
if r1.nil?
432445
r1 = size - 1
433446
else
434447
r1 = r1.to_i
@@ -438,6 +451,10 @@ def byte_ranges(env, size)
438451
end
439452
ranges << (r0..r1) if r0 <= r1
440453
end
454+
455+
total_size = ranges.reduce(0) { |sum, range| sum + range.size }
456+
return [] if total_size > size
457+
441458
ranges
442459
end
443460
module_function :byte_ranges

lib/rack/version.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# frozen_string_literal: true
2+
3+
module Rack
4+
RELEASE = "1.6.13.16"
5+
6+
# Return the Rack release as a dotted string.
7+
def self.release
8+
RELEASE
9+
end
10+
end
11+

0 commit comments

Comments
 (0)