Skip to content

Commit 4795831

Browse files
jhawthornmatthewd
authored andcommitted
Fix ReDoS and consistency in multipart regexes
[CVE-2025-49007] There is a ReDoS in multipart parsing here because it is not anchored to the start of a line and so may match as part of its comments. Previously in f92e056 Content-ID and Content-Type were changed to only accept tab and space as whitespace characters. Although that's what the various RFCs show as their BNF, I that's supposed to be interpreted _after_ lines have been unfolded and so we need to allow FWS "Foldable White Space". CR is not allowed unescaped as part of quoted-string. It might be technically valid with a leading backslash, but I don't believe that case is worth supporting. Co-authored-by: Matthew Draper <[email protected]>
1 parent d1d3022 commit 4795831

File tree

2 files changed

+7
-5
lines changed

2 files changed

+7
-5
lines changed

lib/rack/multipart/parser.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ class BoundaryTooLongError < StandardError
3131
Error = BoundaryTooLongError
3232

3333
EOL = "\r\n"
34+
FWS = /[ \t]+(?:\r\n[ \t]+)?/ # whitespace with optional folding
35+
HEADER_VALUE = "(?:[^\r\n]|\r\n[ \t])*" # anything but a non-folding CRLF
3436
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
35-
MULTIPART_CONTENT_TYPE = /Content-Type:[ \t]*(.*)#{EOL}/ni
36-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:(.*)(?=#{EOL}(\S|\z))/ni
37-
MULTIPART_CONTENT_ID = /Content-ID:[ \t]*([^#{EOL}]*)/ni
37+
MULTIPART_CONTENT_TYPE = /^Content-Type:#{FWS}?(#{HEADER_VALUE})/ni
38+
MULTIPART_CONTENT_DISPOSITION = /^Content-Disposition:#{FWS}?(#{HEADER_VALUE})/ni
39+
MULTIPART_CONTENT_ID = /^Content-ID:#{FWS}?(#{HEADER_VALUE})/ni
3840

3941
class Parser
4042
BUFSIZE = 1_048_576

test/spec_multipart.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,7 @@ def initialize(*)
976976
data = <<-EOF
977977
--AaB03x\r
978978
content-type: text/plain\r
979-
content-disposition: attachment; name="quoted\\\\chars\\"in\rname"\r
979+
content-disposition: attachment; name="quoted\\\\chars\\"in\tname"\r
980980
\r
981981
true\r
982982
--AaB03x--\r
@@ -989,7 +989,7 @@ def initialize(*)
989989
}
990990
env = Rack::MockRequest.env_for("/", options)
991991
params = Rack::Multipart.parse_multipart(env)
992-
params["quoted\\chars\"in\rname"].must_equal 'true'
992+
params["quoted\\chars\"in\tname"].must_equal 'true'
993993
end
994994

995995
it "supports mixed case metadata" do

0 commit comments

Comments
 (0)