rack 2.2.23 → 2.2.25

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: f1956bc102141711f91a9f0daa1507098bd8b7ede58febde9472b875dcb0d4e4
4
- data.tar.gz: a8945afc610aed0c61ce8062e3b1394a24c6a457789637cc6835cfd881dac635
3
+ metadata.gz: c5322ca41af487959f08981c9964d7ef877f82b0ee4374e4338bedbeb93d3538
4
+ data.tar.gz: 4accf7c52defac80efd55caef60ef967f1a813743d8487723e733f66591fce5d
5
5
  SHA512:
6
- metadata.gz: afe4c41e29d6112ba8fe825b33b93d10317d576add705a72de25825caa504c21eefe86cf9308df573dd422cfb4aed24e9453ad4e92720188f4f98c4d2f04c5c4
7
- data.tar.gz: d28cd489644e2191917b28f39d689d384c21296e4f4a8a2bab7b1413f4110cb2092336039afc571d1278adbbfa4ebac9b2d27cb342b873336aa6efe037a93f4d
6
+ metadata.gz: 4b5cedb21343a2e062ca5daf8c55f88fe29bc7d884244dcd13ca6cad858e61764b18f71ea93cfb6a538bc03dadbb82ad7471895d1f6cbc1b2bc727651a807a11
7
+ data.tar.gz: 5bc5a948d20e77d1c831173b4ba5ba5b661bc985f60a6bf9fd1224033c6725ed2c8753f39766a3fe386b56457de9439ace3459cd0f1f650dd0c7fdb2effdcc13
data/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
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.25] - 2026-10-30
6
+
7
+ ### Security
8
+
9
+ - [GHSA-5v64-555j-wjxm](https://github.com/advisories/GHSA-5v64-555j-wjxm) Multipart parser retains parsed data in its scan buffer, allowing denial of service via memory exhaustion.
10
+ - [GHSA-wvg4-6p58-3gh3](https://github.com/advisories/GHSA-wvg4-6p58-3gh3) `Rack::Static` header rules were matched against an unnormalized path, allowing dot segments to bypass them.
11
+ - [GHSA-p8x9-v4hv-2w4g](https://github.com/advisories/GHSA-p8x9-v4hv-2w4g) Multipart requests with more than one `boundary` parameter were accepted when the first value was empty or began with a quote, semicolon, or comma, bypassing the guard added for CVE-2026-26961.
12
+
13
+ ### Fixed
14
+
15
+ - Multipart parser: scan boundary lines with a literal newline search instead of a backtracking regexp, which allocated tens of megabytes per scan for long lines and made the scan buffer fix above ineffective on this series.
16
+
17
+ ## [2.2.24] - 2026-08-13
18
+
19
+ ### Security
20
+
21
+ - [CVE-2026-26962](https://github.com/advisories/GHSA-rx22-g9mx-qrhv) Improper unfolding of folded multipart headers preserves CRLF in parsed parameter values.
22
+
5
23
  ## [2.2.23] - 2026-04-01
6
24
 
7
25
  ### Security
@@ -20,10 +20,17 @@ module Rack
20
20
 
21
21
  BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
22
22
 
23
+ LINE_END = /\n/
24
+ private_constant :LINE_END
25
+
23
26
  BOUNDARY_START_LIMIT = 16 * 1024
24
27
  private_constant :BOUNDARY_START_LIMIT
25
28
 
26
29
  MIME_HEADER_BYTESIZE_LIMIT = 64 * 1024
30
+
31
+ OBS_UNFOLD = /\r\n([ \t])/
32
+
33
+ private_constant :OBS_UNFOLD
27
34
  private_constant :MIME_HEADER_BYTESIZE_LIMIT
28
35
 
29
36
  env_int = lambda do |key, val|
@@ -89,7 +96,11 @@ module Rack
89
96
  unless data[1].empty?
90
97
  raise EOFError, "whitespace between boundary parameter name and equal sign"
91
98
  end
92
- if data.post_match =~ /boundary\s*=/i
99
+ # Count across the whole header rather than only after the match: the
100
+ # lazy prefix in MULTIPART can skip a first boundary parameter whose
101
+ # value is empty or starts with a quote, semicolon, or comma. Also
102
+ # count RFC 2231 boundary* parameters, which some parsers prefer.
103
+ if content_type.scan(/boundary(?:\*\d*\*?)?\s*=/i).size > 1
93
104
  raise EOFError, "multiple boundary parameters found in multipart content type"
94
105
  end
95
106
 
@@ -310,14 +321,17 @@ module Rack
310
321
  def handle_mime_head
311
322
  if @sbuf.scan_until(@head_regex)
312
323
  head = @sbuf[1]
313
- content_type = head[MULTIPART_CONTENT_TYPE, 1]
314
- if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
324
+ # Implement OBS unfolding (RFC 5322 Section 2.2.3). Done on a copy
325
+ # because content type, disposition and filename are extracted by regex.
326
+ unfolded_head = head.gsub(OBS_UNFOLD, '\\1')
327
+ content_type = unfolded_head[MULTIPART_CONTENT_TYPE, 1]
328
+ if name = unfolded_head[MULTIPART_CONTENT_DISPOSITION, 1]
315
329
  name = Rack::Auth::Digest::Params::dequote(name)
316
330
  else
317
- name = head[MULTIPART_CONTENT_ID, 1]
331
+ name = unfolded_head[MULTIPART_CONTENT_ID, 1]
318
332
  end
319
333
 
320
- filename = get_filename(head)
334
+ filename = get_filename(unfolded_head)
321
335
 
322
336
  if name.nil? || name.empty?
323
337
  name = filename || "#{content_type || TEXT_PLAIN}[]".dup
@@ -339,6 +353,7 @@ module Rack
339
353
  # buffering. Note that the actual limit is the higher of 64KB and the buffer size (1MB by default)
340
354
  raise EOFError, "multipart mime part header too large" if @sbuf.rest.bytesize > MIME_HEADER_BYTESIZE_LIMIT
341
355
 
356
+ @sbuf.string = @sbuf.rest
342
357
  return :want_read
343
358
  end
344
359
  end
@@ -359,8 +374,8 @@ module Rack
359
374
  update_retained_size(body.bytesize) if @body_retained
360
375
  @collector.on_mime_body @mime_index, body
361
376
  @sbuf.pos += delta
362
- @sbuf.string = @sbuf.rest
363
377
  end
378
+ @sbuf.string = @sbuf.rest
364
379
  :want_read
365
380
  end
366
381
  end
@@ -379,7 +394,7 @@ module Rack
379
394
  # end of the boundary. If we don't find the start or end of the
380
395
  # boundary, clear the buffer and return nil.
381
396
  def consume_boundary
382
- while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
397
+ while read_buffer = scan_line
383
398
  case read_buffer.strip
384
399
  when full_boundary then return :BOUNDARY
385
400
  when @end_boundary then return :END_BOUNDARY
@@ -388,6 +403,21 @@ module Rack
388
403
  end
389
404
  end
390
405
 
406
+ # Return the next line of the buffer including its trailing "\n", or the
407
+ # rest of the buffer if it contains no "\n", and advance past it. This is
408
+ # equivalent to `@sbuf.scan_until(BOUNDARY_REGEX)`, but searching for the
409
+ # "\n" directly avoids the regexp engine's backtracking stack, which grows
410
+ # by one entry per byte matched by `[^\n]*` (tens of MB for a 1 MB line).
411
+ def scan_line
412
+ if line = @sbuf.scan_until(LINE_END)
413
+ line
414
+ else
415
+ line = @sbuf.rest
416
+ @sbuf.terminate
417
+ line
418
+ end
419
+ end
420
+
391
421
  def get_filename(head)
392
422
  filename = nil
393
423
  case head
data/lib/rack/static.rb CHANGED
@@ -168,7 +168,7 @@ module Rack
168
168
 
169
169
  # Convert HTTP header rules to HTTP headers
170
170
  def applicable_rules(path)
171
- path = ::Rack::Utils.unescape_path(path)
171
+ path = ::Rack::Utils.clean_path_info(::Rack::Utils.unescape_path(path))
172
172
 
173
173
  @header_rules.find_all do |rule, new_headers|
174
174
  case rule
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.23"
23
+ RELEASE = "2.2.25"
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.23
4
+ version: 2.2.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leah Neukirchen
@@ -182,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
182
182
  - !ruby/object:Gem::Version
183
183
  version: '0'
184
184
  requirements: []
185
- rubygems_version: 4.0.6
185
+ rubygems_version: 4.0.10
186
186
  specification_version: 4
187
187
  summary: A modular Ruby webserver interface.
188
188
  test_files: []