rack 2.2.18 → 2.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a0c02f8ad516a9e66e1d8ec32ca620dfe46328d5b248fb6a72ed0b8e4a0f91c
4
- data.tar.gz: d32193586a1367c718a9a4385b26a4159a8e376c852c624fd9e1ad6b77e5137d
3
+ metadata.gz: ef82ea76fce7ba344410cc419cff56e201f8ec9ded624b01b34ad9521b613f99
4
+ data.tar.gz: de812a77455e5e05e6e2ced224c8cd0ea3c664f9244ed055346a9912ad0091eb
5
5
  SHA512:
6
- metadata.gz: 8ea04755b2f8c7d4482a35e601d5fdc73d1fd7f62fc0bade4cf6ce0769cbe7e182ec6c6659dddc44489069a19959a3d5fa799451573a38fb943f21376356329d
7
- data.tar.gz: d8c8032d0fb15a750878a0d4127d79957f21890298b7dbdb743dc276ede56fb0a32b94ece434c7d6bea26bf55167b5318ffc9c0e4c0fe6f5d1baf0cadeba6ba5
6
+ metadata.gz: 53b096b3b1d67e810a7dbb880dab8e06a107bb9b9e640f94cc1136b5eb9dc9ca9712d9a0d1c28de7544796095222d71ab589776ea361e56a2136e791bbaf5110
7
+ data.tar.gz: 8fdc0b68d511c84b62d8eaaad9c520928d6e48b75aa2c01ede21b6309231a32652188726bb1e2bff67f849d6b804ad80c8896b3cf28e6a9c700edf8a9fc26ee3
data/CHANGELOG.md CHANGED
@@ -2,9 +2,26 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. For info on how to format all future additions to this file please reference [Keep A Changelog](https://keepachangelog.com/en/1.0.0/).
4
4
 
5
+ ## [2.2.20] - 2025-10-10
6
+
7
+ ### Security
8
+
9
+ - [CVE-2025-61780](https://github.com/advisories/GHSA-r657-rxjc-j557) Improper handling of headers in `Rack::Sendfile` may allow proxy bypass.
10
+ - [CVE-2025-61919](https://github.com/advisories/GHSA-6xw4-3v39-52mm) Unbounded read in `Rack::Request` form parsing can lead to memory exhaustion.
11
+
12
+ ## [2.2.19] - 2025-10-07
13
+
14
+ ### Security
15
+
16
+ - [CVE-2025-61772](https://github.com/advisories/GHSA-wpv5-97wm-hp9c) Multipart parser buffers unbounded per-part headers, enabling DoS (memory exhaustion)
17
+ - [CVE-2025-61771](https://github.com/advisories/GHSA-w9pc-fmgc-vxvw) Multipart parser buffers large non‑file fields entirely in memory, enabling DoS (memory exhaustion)
18
+ - [CVE-2025-61770](https://github.com/advisories/GHSA-p543-xpfm-54cp) Unbounded multipart preamble buffering enables DoS (memory exhaustion)
19
+
5
20
  ## [2.2.18] - 2025-09-25
6
21
 
7
- - [CVE-2025-59830](https://github.com/rack/rack/security/advisories/GHSA-625h-95r8-8xpm) Unbounded parameter parsing in `Rack::QueryParser` can lead to memory exhaustion via semicolon-separated parameters.
22
+ ### Security
23
+
24
+ - [CVE-2025-59830](https://github.com/advisories/GHSA-625h-95r8-8xpm) Unbounded parameter parsing in `Rack::QueryParser` can lead to memory exhaustion via semicolon-separated parameters.
8
25
 
9
26
  ## [2.2.17] - 2025-06-03
10
27
 
@@ -22,26 +39,26 @@ All notable changes to this project will be documented in this file. For info on
22
39
 
23
40
  ### Security
24
41
 
25
- - [CVE-2025-32441](https://github.com/rack/rack/security/advisories/GHSA-vpfw-47h7-xj4g) Rack session can be restored after deletion.
26
- - [CVE-2025-46727](https://github.com/rack/rack/security/advisories/GHSA-gjh7-p2fx-99vx) Unbounded parameter parsing in `Rack::QueryParser` can lead to memory exhaustion.
42
+ - [CVE-2025-32441](https://github.com/advisories/GHSA-vpfw-47h7-xj4g) Rack session can be restored after deletion.
43
+ - [CVE-2025-46727](https://github.com/advisories/GHSA-gjh7-p2fx-99vx) Unbounded parameter parsing in `Rack::QueryParser` can lead to memory exhaustion.
27
44
 
28
45
  ## [2.2.13] - 2025-03-11
29
46
 
30
47
  ### Security
31
48
 
32
- - [CVE-2025-27610](https://github.com/rack/rack/security/advisories/GHSA-7wqh-767x-r66v) Local file inclusion in `Rack::Static`.
49
+ - [CVE-2025-27610](https://github.com/advisories/GHSA-7wqh-767x-r66v) Local file inclusion in `Rack::Static`.
33
50
 
34
51
  ## [2.2.12] - 2025-03-04
35
52
 
36
53
  ### Security
37
54
 
38
- - [CVE-2025-27111](https://github.com/rack/rack/security/advisories/GHSA-8cgq-6mh2-7j6v) Possible Log Injection in `Rack::Sendfile`.
55
+ - [CVE-2025-27111](https://github.com/advisories/GHSA-8cgq-6mh2-7j6v) Possible Log Injection in `Rack::Sendfile`.
39
56
 
40
57
  ## [2.2.11] - 2025-02-12
41
58
 
42
59
  ### Security
43
60
 
44
- - [CVE-2025-25184](https://github.com/rack/rack/security/advisories/GHSA-7g2v-jj9q-g3rg) Possible Log Injection in `Rack::CommonLogger`.
61
+ - [CVE-2025-25184](https://github.com/advisories/GHSA-7g2v-jj9q-g3rg) Possible Log Injection in `Rack::CommonLogger`.
45
62
 
46
63
  ## [2.2.10] - 2024-10-14
47
64
 
data/README.rdoc CHANGED
@@ -206,6 +206,14 @@ query string, before attempting parsing, so if the same parameter key is
206
206
  used multiple times in the query, each counts as a separate parameter for
207
207
  this check.
208
208
 
209
+ === `RACK_MULTIPART_BUFFERED_UPLOAD_BYTESIZE_LIMIT`
210
+
211
+ This environment variable sets the maximum amount of memory Rack will use
212
+ to buffer multipart parameters when parsing a request body. This considers
213
+ the size of the multipart mime headers and the body part for multipart
214
+ parameters that are buffered in memory and do not use tempfiles. This
215
+ defaults to 16MB if not provided.
216
+
209
217
  === key_space_limit
210
218
 
211
219
  The default number of bytes to allow all parameters keys in a given parameter hash to take up.
@@ -20,6 +20,27 @@ module Rack
20
20
 
21
21
  BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
22
22
 
23
+ BOUNDARY_START_LIMIT = 16 * 1024
24
+ private_constant :BOUNDARY_START_LIMIT
25
+
26
+ MIME_HEADER_BYTESIZE_LIMIT = 64 * 1024
27
+ private_constant :MIME_HEADER_BYTESIZE_LIMIT
28
+
29
+ env_int = lambda do |key, val|
30
+ if str_val = ENV[key]
31
+ begin
32
+ val = Integer(str_val, 10)
33
+ rescue ArgumentError
34
+ raise ArgumentError, "non-integer value provided for environment variable #{key}"
35
+ end
36
+ end
37
+
38
+ val
39
+ end
40
+
41
+ BUFFERED_UPLOAD_BYTESIZE_LIMIT = env_int.call("RACK_MULTIPART_BUFFERED_UPLOAD_BYTESIZE_LIMIT", 16 * 1024 * 1024)
42
+ private_constant :BUFFERED_UPLOAD_BYTESIZE_LIMIT
43
+
23
44
  class BoundedIO # :nodoc:
24
45
  def initialize(io, content_length)
25
46
  @io = io
@@ -187,6 +208,8 @@ module Rack
187
208
  @end_boundary = @boundary + '--'
188
209
  @state = :FAST_FORWARD
189
210
  @mime_index = 0
211
+ @body_retained = nil
212
+ @retained_size = 0
190
213
  @collector = Collector.new tempfile
191
214
 
192
215
  @sbuf = StringScanner.new("".dup)
@@ -241,7 +264,13 @@ module Rack
241
264
  @state = :MIME_HEAD
242
265
  else
243
266
  raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
244
- :want_read
267
+
268
+ # We raise if we don't find the multipart boundary, to avoid unbounded memory
269
+ # buffering. Note that the actual limit is the higher of 16KB and the buffer size (1MB by default)
270
+ raise EOFError, "multipart boundary not found within limit" if @sbuf.string.bytesize > BOUNDARY_START_LIMIT
271
+
272
+ # no boundary found, keep reading data
273
+ return :want_read
245
274
  end
246
275
  end
247
276
 
@@ -271,16 +300,30 @@ module Rack
271
300
  name = filename || "#{content_type || TEXT_PLAIN}[]".dup
272
301
  end
273
302
 
303
+ # Mime part head data is retained for both TempfilePart and BufferPart
304
+ # for the entireity of the parse, even though it isn't used for BufferPart.
305
+ update_retained_size(head.bytesize)
306
+
307
+ # If a filename is given, a TempfilePart will be used, so the body will
308
+ # not be buffered in memory. However, if a filename is not given, a BufferPart
309
+ # will be used, and the body will be buffered in memory.
310
+ @body_retained = !filename
311
+
274
312
  @collector.on_mime_head @mime_index, head, filename, content_type, name
275
313
  @state = :MIME_BODY
276
314
  else
277
- :want_read
315
+ # We raise if the mime part header is too large, to avoid unbounded memory
316
+ # buffering. Note that the actual limit is the higher of 64KB and the buffer size (1MB by default)
317
+ raise EOFError, "multipart mime part header too large" if @sbuf.string.bytesize > MIME_HEADER_BYTESIZE_LIMIT
318
+
319
+ return :want_read
278
320
  end
279
321
  end
280
322
 
281
323
  def handle_mime_body
282
324
  if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
283
325
  body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
326
+ update_retained_size(body.bytesize) if @body_retained
284
327
  @collector.on_mime_body @mime_index, body
285
328
  @sbuf.pos += body.length + 2 # skip \r\n after the content
286
329
  @state = :CONSUME_TOKEN
@@ -289,7 +332,9 @@ module Rack
289
332
  # Save what we have so far
290
333
  if @rx_max_size < @sbuf.rest_size
291
334
  delta = @sbuf.rest_size - @rx_max_size
292
- @collector.on_mime_body @mime_index, @sbuf.peek(delta)
335
+ body = @sbuf.peek(delta)
336
+ update_retained_size(body.bytesize) if @body_retained
337
+ @collector.on_mime_body @mime_index, body
293
338
  @sbuf.pos += delta
294
339
  @sbuf.string = @sbuf.rest
295
340
  end
@@ -299,6 +344,17 @@ module Rack
299
344
 
300
345
  def full_boundary; @full_boundary; end
301
346
 
347
+ def update_retained_size(size)
348
+ @retained_size += size
349
+ if @retained_size > BUFFERED_UPLOAD_BYTESIZE_LIMIT
350
+ raise EOFError, "multipart data over retained size limit"
351
+ end
352
+ end
353
+
354
+ # Scan until the we find the start or end of the boundary.
355
+ # If we find it, return the appropriate symbol for the start or
356
+ # end of the boundary. If we don't find the start or end of the
357
+ # boundary, clear the buffer and return nil.
302
358
  def consume_boundary
303
359
  while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
304
360
  case read_buffer.strip
@@ -51,6 +51,8 @@ module Rack
51
51
  PARAMS_LIMIT = env_int.call("RACK_QUERY_PARSER_PARAMS_LIMIT", 4096)
52
52
  private_constant :PARAMS_LIMIT
53
53
 
54
+ attr_reader :bytesize_limit
55
+
54
56
  def initialize(params_class, key_space_limit, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
55
57
  @params_class = params_class
56
58
  @key_space_limit = key_space_limit
@@ -185,7 +187,7 @@ module Rack
185
187
  def check_query_string(qs, sep)
186
188
  if qs
187
189
  if qs.bytesize > @bytesize_limit
188
- raise QueryLimitError, "total query size (#{qs.bytesize}) exceeds limit (#{@bytesize_limit})"
190
+ raise QueryLimitError, "total query size exceeds limit (#{@bytesize_limit})"
189
191
  end
190
192
 
191
193
  if (param_count = qs.count(sep.is_a?(String) ? sep : '&;')) >= @params_limit
data/lib/rack/request.rb CHANGED
@@ -444,7 +444,10 @@ module Rack
444
444
  get_header(RACK_REQUEST_FORM_HASH)
445
445
  elsif form_data? || parseable_data?
446
446
  unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
447
- form_vars = get_header(RACK_INPUT).read
447
+ # Add 2 bytes. One to check whether it is over the limit, and a second
448
+ # in case the slice! call below removes the last byte
449
+ # If read returns nil, use the empty string
450
+ form_vars = get_header(RACK_INPUT).read(query_parser.bytesize_limit + 2) || ''
448
451
 
449
452
  # Fix for Safari Ajax postings that always append \0
450
453
  # form_vars.sub!(/\0\z/, '') # performance replacement:
data/lib/rack/sendfile.rb CHANGED
@@ -40,18 +40,23 @@ module Rack
40
40
  # proxy_set_header X-Real-IP $remote_addr;
41
41
  # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
42
42
  #
43
- # proxy_set_header X-Sendfile-Type X-Accel-Redirect;
44
43
  # proxy_set_header X-Accel-Mapping /var/www/=/files/;
45
44
  #
46
45
  # proxy_pass http://127.0.0.1:8080/;
47
46
  # }
48
47
  #
49
- # Note that the X-Sendfile-Type header must be set exactly as shown above.
50
48
  # The X-Accel-Mapping header should specify the location on the file system,
51
49
  # followed by an equals sign (=), followed name of the private URL pattern
52
50
  # that it maps to. The middleware performs a simple substitution on the
53
51
  # resulting path.
54
52
  #
53
+ # To enable X-Accel-Redirect, you must configure the middleware explicitly:
54
+ #
55
+ # use Rack::Sendfile, "X-Accel-Redirect"
56
+ #
57
+ # For security reasons, the X-Sendfile-Type header from requests is ignored.
58
+ # The sendfile variation must be set via the middleware constructor.
59
+ #
55
60
  # See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile
56
61
  #
57
62
  # === lighttpd
@@ -96,13 +101,25 @@ module Rack
96
101
  # X-Accel-Mapping header. Mappings should be provided in tuples of internal to
97
102
  # external. The internal values may contain regular expression syntax, they
98
103
  # will be matched with case indifference.
104
+ #
105
+ # When X-Accel-Redirect is explicitly enabled via the variation parameter,
106
+ # and no application-level mappings are provided, the middleware will read
107
+ # the X-Accel-Mapping header from the proxy. This allows nginx to control
108
+ # the path mapping without requiring application-level configuration.
109
+ #
110
+ # === Security
111
+ #
112
+ # For security reasons, the X-Sendfile-Type header from HTTP requests is
113
+ # ignored. The sendfile variation must be explicitly configured via the
114
+ # middleware constructor to prevent information disclosure vulnerabilities
115
+ # where attackers could bypass proxy restrictions.
99
116
 
100
117
  class Sendfile
101
118
  def initialize(app, variation = nil, mappings = [])
102
119
  @app = app
103
120
  @variation = variation
104
121
  @mappings = mappings.map do |internal, external|
105
- [/^#{internal}/i, external]
122
+ [/\A#{internal}/i, external]
106
123
  end
107
124
  end
108
125
 
@@ -140,22 +157,35 @@ module Rack
140
157
  end
141
158
 
142
159
  private
160
+
143
161
  def variation(env)
144
- @variation ||
145
- env['sendfile.type'] ||
146
- env['HTTP_X_SENDFILE_TYPE']
162
+ # Note: HTTP_X_SENDFILE_TYPE is intentionally NOT read for security reasons.
163
+ # Attackers could use this header to enable x-accel-redirect and bypass proxy restrictions.
164
+ @variation || env['sendfile.type']
165
+ end
166
+
167
+ def x_accel_mapping(env)
168
+ # Only allow header when:
169
+ # 1. X-Accel-Redirect is explicitly enabled via constructor.
170
+ # 2. No application-level mappings are configured.
171
+ return nil unless @variation =~ /x-accel-redirect/i
172
+ return nil if @mappings.any?
173
+
174
+ env['HTTP_X_ACCEL_MAPPING']
147
175
  end
148
176
 
149
177
  def map_accel_path(env, path)
150
178
  if mapping = @mappings.find { |internal, _| internal =~ path }
151
- path.sub(*mapping)
152
- elsif mapping = env['HTTP_X_ACCEL_MAPPING']
179
+ return path.sub(*mapping)
180
+ elsif mapping = x_accel_mapping(env)
181
+ # Safe to use header: explicit config + no app mappings:
153
182
  mapping.split(',').map(&:strip).each do |m|
154
183
  internal, external = m.split('=', 2).map(&:strip)
155
- new_path = path.sub(/^#{internal}/i, external)
184
+ new_path = path.sub(/\A#{internal}/i, external)
156
185
  return new_path unless path == new_path
157
186
  end
158
- path
187
+
188
+ return path
159
189
  end
160
190
  end
161
191
  end
data/lib/rack/version.rb CHANGED
@@ -20,7 +20,7 @@ module Rack
20
20
  VERSION.join(".")
21
21
  end
22
22
 
23
- RELEASE = "2.2.18"
23
+ RELEASE = "2.2.20"
24
24
 
25
25
  # Return the Rack release as a dotted string.
26
26
  def self.release
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.18
4
+ version: 2.2.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leah Neukirchen