puma 5.6.2 → 5.6.4

Sign up to get free protection for your applications and to get access to all the features.
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