puma 5.6.2 → 5.6.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f2fbd628cb88e37c6df33dccf70b11f52b5e0ae56693e8ada921793cf607f0a
4
- data.tar.gz: fd7fa2520c4ac378f616373655f9b72127cd50c1ba36db4d986857e736a526b1
3
+ metadata.gz: ff9f4c8b81e1bfced36da8ecfe60912bd6bb37a2f989f2f8489d31cbe1a5947a
4
+ data.tar.gz: 6821bc93adc639fea1ed82559b0e67c70bfd86fa0b0ec641eed65e020fb53440
5
5
  SHA512:
6
- metadata.gz: 448267cd44a571941f8ab0133d9ab2a5bedd2c7fcd964fb3948a7a9a3190b8052a69e8b849e1bbb7622373a5ccf754cdd39d1e24b8fa839cf0af71015b608b30
7
- data.tar.gz: b37d8563ce45b33bdd12ae8e4a913a8a702f36f7b4afaf16edf17611868da089d8b98ee130a565488f3007b0a28bce32202fe4f391a9ff81aa29e66a9372be36
6
+ metadata.gz: 7b3eafbc651e03214851f79a89cb92637f7966f7d645446e15ac29e118d3b564aa28222ef54c1ea02f946da813172cf24bf9fb3b4c9d9b14dc1dadbc20754579
7
+ data.tar.gz: 760c958a8c56ee5ec7b2208e5d1d9e0ef31c09975d058cd535f42382895f76f0854a18c68f671b37736e1588a372bc752a676cbbd1a896a08bd97962642dc0b7
data/History.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 5.6.4 / 2022-03-30
2
+
3
+ * Security
4
+ * Close several HTTP Request Smuggling exploits (CVE-2022-24790)
5
+
1
6
  ## 5.6.2 / 2022-02-11
2
7
 
3
8
  * Bugfix/Security
data/lib/puma/client.rb CHANGED
@@ -23,6 +23,8 @@ module Puma
23
23
 
24
24
  class ConnectionError < RuntimeError; end
25
25
 
26
+ class HttpParserError501 < IOError; end
27
+
26
28
  # An instance of this class represents a unique request from a client.
27
29
  # For example, this could be a web request from a browser or from CURL.
28
30
  #
@@ -35,7 +37,21 @@ module Puma
35
37
  # Instances of this class are responsible for knowing if
36
38
  # the header and body are fully buffered via the `try_to_finish` method.
37
39
  # They can be used to "time out" a response via the `timeout_at` reader.
40
+ #
38
41
  class Client
42
+
43
+ # this tests all values but the last, which must be chunked
44
+ ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
45
+
46
+ # chunked body validation
47
+ CHUNK_SIZE_INVALID = /[^\h]/.freeze
48
+ CHUNK_VALID_ENDING = "\r\n".freeze
49
+
50
+ # Content-Length header value validation
51
+ CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
52
+
53
+ TE_ERR_MSG = 'Invalid Transfer-Encoding'
54
+
39
55
  # The object used for a request with no body. All requests with
40
56
  # no body share this one object since it has no state.
41
57
  EmptyBody = NullIO.new
@@ -302,16 +318,27 @@ module Puma
302
318
  body = @parser.body
303
319
 
304
320
  te = @env[TRANSFER_ENCODING2]
305
-
306
321
  if te
307
- if te.include?(",")
308
- te.split(",").each do |part|
309
- if CHUNKED.casecmp(part.strip) == 0
310
- return setup_chunked_body(body)
311
- end
322
+ te_lwr = te.downcase
323
+ if te.include? ','
324
+ te_ary = te_lwr.split ','
325
+ te_count = te_ary.count CHUNKED
326
+ te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e }
327
+ if te_ary.last == CHUNKED && te_count == 1 && te_valid
328
+ @env.delete TRANSFER_ENCODING2
329
+ return setup_chunked_body body
330
+ elsif te_count >= 1
331
+ raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'"
332
+ elsif !te_valid
333
+ raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
312
334
  end
313
- elsif CHUNKED.casecmp(te) == 0
314
- return setup_chunked_body(body)
335
+ elsif te_lwr == CHUNKED
336
+ @env.delete TRANSFER_ENCODING2
337
+ return setup_chunked_body body
338
+ elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
339
+ raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
340
+ else
341
+ raise HttpParserError501 , "#{TE_ERR_MSG}, unknown value: '#{te}'"
315
342
  end
316
343
  end
317
344
 
@@ -319,7 +346,12 @@ module Puma
319
346
 
320
347
  cl = @env[CONTENT_LENGTH]
321
348
 
322
- unless cl
349
+ if cl
350
+ # cannot contain characters that are not \d
351
+ if cl =~ CONTENT_LENGTH_VALUE_INVALID
352
+ raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
353
+ end
354
+ else
323
355
  @buffer = body.empty? ? nil : body
324
356
  @body = EmptyBody
325
357
  set_ready
@@ -478,7 +510,13 @@ module Puma
478
510
  while !io.eof?
479
511
  line = io.gets
480
512
  if line.end_with?("\r\n")
481
- len = line.strip.to_i(16)
513
+ # Puma doesn't process chunk extensions, but should parse if they're
514
+ # present, which is the reason for the semicolon regex
515
+ chunk_hex = line.strip[/\A[^;]+/]
516
+ if chunk_hex =~ CHUNK_SIZE_INVALID
517
+ raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
518
+ end
519
+ len = chunk_hex.to_i(16)
482
520
  if len == 0
483
521
  @in_last_chunk = true
484
522
  @body.rewind
@@ -509,7 +547,12 @@ module Puma
509
547
 
510
548
  case
511
549
  when got == len
512
- write_chunk(part[0..-3]) # to skip the ending \r\n
550
+ # proper chunked segment must end with "\r\n"
551
+ if part.end_with? CHUNK_VALID_ENDING
552
+ write_chunk(part[0..-3]) # to skip the ending \r\n
553
+ else
554
+ raise HttpParserError, "Chunk size mismatch"
555
+ end
513
556
  when got <= len - 2
514
557
  write_chunk(part)
515
558
  @partial_part_left = len - part.size
data/lib/puma/const.rb CHANGED
@@ -76,7 +76,7 @@ module Puma
76
76
  508 => 'Loop Detected',
77
77
  510 => 'Not Extended',
78
78
  511 => 'Network Authentication Required'
79
- }
79
+ }.freeze
80
80
 
81
81
  # For some HTTP status codes the client only expects headers.
82
82
  #
@@ -85,7 +85,7 @@ module Puma
85
85
  204 => true,
86
86
  205 => true,
87
87
  304 => true
88
- }
88
+ }.freeze
89
89
 
90
90
  # Frequently used constants when constructing requests or responses. Many times
91
91
  # the constant just refers to a string with the same contents. Using these constants
@@ -100,7 +100,7 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.6.2".freeze
103
+ PUMA_VERSION = VERSION = "5.6.4".freeze
104
104
  CODE_NAME = "Birdie's Version".freeze
105
105
 
106
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
@@ -145,9 +145,11 @@ module Puma
145
145
  408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
146
146
  # Indicate that there was an internal error, obviously.
147
147
  500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
148
+ # Incorrect or invalid header value
149
+ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
148
150
  # A common header for indicating the server is too busy. Not used yet.
149
151
  503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
150
- }
152
+ }.freeze
151
153
 
152
154
  # The basic max request size we'll try to read.
153
155
  CHUNK_SIZE = 16 * 1024
data/lib/puma/server.rb CHANGED
@@ -515,6 +515,9 @@ module Puma
515
515
  when HttpParserError
516
516
  client.write_error(400)
517
517
  @events.parse_error e, client
518
+ when HttpParserError501
519
+ client.write_error(501)
520
+ @events.parse_error e, client
518
521
  else
519
522
  client.write_error(500)
520
523
  @events.unknown_error e, nil, "Read"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.2
4
+ version: 5.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix