139 lines
4.8 KiB
Diff
139 lines
4.8 KiB
Diff
From 9aac3757fe19cdb0476504c9245170115bec9668 Mon Sep 17 00:00:00 2001
|
|
From: John Hawthorn <john@hawthorn.email>
|
|
Date: Thu, 8 Dec 2022 15:54:28 -0800
|
|
Subject: [PATCH] Limit all multipart parts, not just files
|
|
|
|
Previously we would limit the number of multipart parts which were
|
|
files, but not other parts. In some cases this could cause parsing of
|
|
maliciously crafted inputs to take longer than expected.
|
|
|
|
[CVE-2023-27530]
|
|
---
|
|
README.rdoc | 20 +++++++++++++++++---
|
|
lib/rack/multipart/parser.rb | 19 +++++++++++++++----
|
|
lib/rack/utils.rb | 19 +++++++++++++++----
|
|
3 files changed, 76 insertions(+), 12 deletions(-)
|
|
|
|
diff --git a/README.rdoc b/README.rdoc
|
|
index 8533846f..cbb25723 100644
|
|
--- a/README.rdoc
|
|
+++ b/README.rdoc
|
|
@@ -202,16 +202,30 @@ Limiting the depth prevents a possible stack overflow when parsing parameters.
|
|
|
|
Defaults to 100.
|
|
|
|
-=== multipart_part_limit
|
|
+=== multipart_file_limit
|
|
|
|
-The maximum number of parts a request can contain.
|
|
+The maximum number of parts with a filename a request can contain.
|
|
Accepting too many part can lead to the server running out of file handles.
|
|
|
|
The default is 128, which means that a single request can't upload more than 128 files at once.
|
|
|
|
Set to 0 for no limit.
|
|
|
|
-Can also be set via the +RACK_MULTIPART_PART_LIMIT+ environment variable.
|
|
+Can also be set via the +RACK_MULTIPART_FILE_LIMIT+ environment variable.
|
|
+
|
|
+(This is also aliased as +multipart_part_limit+ and +RACK_MULTIPART_PART_LIMIT+ for compatibility)
|
|
+
|
|
+=== multipart_total_part_limit
|
|
+
|
|
+The maximum total number of parts a request can contain of any type, including
|
|
+both file and non-file form fields.
|
|
+
|
|
+The default is 4096, which means that a single request can't contain more than
|
|
+4096 parts.
|
|
+
|
|
+Set to 0 for no limit.
|
|
+
|
|
+Can also be set via the +RACK_MULTIPART_TOTAL_PART_LIMIT+ environment variable.
|
|
|
|
== Changelog
|
|
|
|
diff --git a/lib/rack/multipart/parser.rb b/lib/rack/multipart/parser.rb
|
|
index e8ed3e97..0fc18560 100644
|
|
--- a/lib/rack/multipart/parser.rb
|
|
+++ b/lib/rack/multipart/parser.rb
|
|
@@ -5,6 +5,7 @@ require 'strscan'
|
|
module Rack
|
|
module Multipart
|
|
class MultipartPartLimitError < Errno::EMFILE; end
|
|
+ class MultipartTotalPartLimitError < StandardError; end
|
|
|
|
class Parser
|
|
(require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
|
@@ -140,7 +141,7 @@ module Rack
|
|
|
|
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
|
|
|
- check_open_files
|
|
+ check_part_limits
|
|
end
|
|
|
|
def on_mime_body(mime_index, content)
|
|
@@ -152,13 +153,23 @@ module Rack
|
|
|
|
private
|
|
|
|
- def check_open_files
|
|
- if Utils.multipart_part_limit > 0
|
|
- if @open_files >= Utils.multipart_part_limit
|
|
+ def check_part_limits
|
|
+ file_limit = Utils.multipart_file_limit
|
|
+ part_limit = Utils.multipart_total_part_limit
|
|
+
|
|
+ if file_limit && file_limit > 0
|
|
+ if @open_files >= file_limit
|
|
@mime_parts.each(&:close)
|
|
raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
|
|
end
|
|
end
|
|
+
|
|
+ if part_limit && part_limit > 0
|
|
+ if @mime_parts.size >= part_limit
|
|
+ @mime_parts.each(&:close)
|
|
+ raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
|
|
+ end
|
|
+ end
|
|
end
|
|
end
|
|
|
|
diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb
|
|
index 14d9e17d..c8e61ea1 100644
|
|
--- a/lib/rack/utils.rb
|
|
+++ b/lib/rack/utils.rb
|
|
@@ -58,13 +58,24 @@ module Rack
|
|
end
|
|
|
|
class << self
|
|
- attr_accessor :multipart_part_limit
|
|
+ attr_accessor :multipart_total_part_limit
|
|
+
|
|
+ attr_accessor :multipart_file_limit
|
|
+
|
|
+ # multipart_part_limit is the original name of multipart_file_limit, but
|
|
+ # the limit only counts parts with filenames.
|
|
+ alias multipart_part_limit multipart_file_limit
|
|
+ alias multipart_part_limit= multipart_file_limit=
|
|
end
|
|
|
|
- # The maximum number of parts a request can contain. Accepting too many part
|
|
- # can lead to the server running out of file handles.
|
|
+ # The maximum number of file parts a request can contain. Accepting too
|
|
+ # many parts can lead to the server running out of file handles.
|
|
# Set to `0` for no limit.
|
|
- self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
|
|
+ self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i
|
|
+
|
|
+ # The maximum total number of parts a request can contain. Accepting too
|
|
+ # many can lead to excessive memory use and parsing time.
|
|
+ self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i
|
|
|
|
def self.param_depth_limit
|
|
default_query_parser.param_depth_limit
|
|
--
|
|
2.37.1
|
|
|