Skip to content

Commit

Permalink
Handle too long dates passed to DateTime.parse
Browse files Browse the repository at this point in the history
Since Ruby 3.0.3, this method raises an ArgumentError
with a different message from the invalid date message,
as mitigation for CVE-2021-41817.
https://www.ruby-lang.org/en/news/2021/11/15/date-parsing-method-regexp-dos-cve-2021-41817/

Example:
  ArgumentError: string length (150) exceeds the limit 128

In this case we should just return nil, like when the date itself is
invalid.
  • Loading branch information
rosa committed Jan 3, 2022
1 parent 6e5db59 commit 4483651
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 20 deletions.
2 changes: 1 addition & 1 deletion lib/mail/elements/received_element.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def to_s(*args)
def datetime_for(received)
::DateTime.parse("#{received.date} #{received.time}")
rescue ArgumentError => e
raise e unless e.message == 'invalid date'
raise unless e.message =~ /\A(invalid date|string length \(\d+\) exceeds the limit \d+)\z/
warn "WARNING: Invalid date field for received element (#{received.date} #{received.time}): #{e.class}: #{e.message}"
nil
end
Expand Down
40 changes: 21 additions & 19 deletions lib/mail/fields/common_date_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,30 @@

module Mail
class CommonDateField < NamedStructuredField #:nodoc:
def self.singular?
true
end
class << self
def singular?
true
end

def normalize_datetime(string)
if Utilities.blank?(string)
datetime = ::DateTime.now
else
stripped = string.to_s.gsub(/\(.*?\)/, '').squeeze(' ')
datetime = parse_date_time(stripped)
end

def self.normalize_datetime(string)
if Utilities.blank?(string)
datetime = ::DateTime.now
else
stripped = string.to_s.gsub(/\(.*?\)/, '').squeeze(' ')
begin
datetime = ::DateTime.parse(stripped)
rescue ArgumentError => e
raise unless 'invalid date' == e.message
if datetime
datetime.strftime('%a, %d %b %Y %H:%M:%S %z')
else
string
end
end

if datetime
datetime.strftime('%a, %d %b %Y %H:%M:%S %z')
else
string
def parse_date_time(string)
::DateTime.parse(string)
rescue ArgumentError => e
raise unless e.message =~ /\A(invalid date|string length \(\d+\) exceeds the limit \d+)\z/
end
end

Expand All @@ -33,9 +37,7 @@ def initialize(value = nil, charset = nil)

# Returns a date time object of the parsed date
def date_time
::DateTime.parse("#{element.date_string} #{element.time_string}")
rescue ArgumentError => e
raise e unless e.message == 'invalid date'
self.class.parse_date_time("#{element.date_string} #{element.time_string}")
end

def default
Expand Down
6 changes: 6 additions & 0 deletions spec/mail/fields/date_field_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,11 @@
field = Mail::DateField.new("12 Aug 2009 30:00:02 GMT")
expect(field.date_time).to be_nil
end

it "should handle too long invalid date" do
# field = Mail::DateField.new("12 Aug 2009 30:00:02 GMT")
field = Mail::DateField.new("Wed, 23 Jan 2019 30:51:32 -0500")
expect(field.date_time).to be_nil
end
end
end
9 changes: 9 additions & 0 deletions spec/mail/fields/received_field_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,13 @@
expect(t.date_time).to eq nil
expect(t.formatted_date).to eq nil
end

it "should handle too long invalid date" do
t = Mail::ReceivedField.new("mail.example.com (192.168.1.1) by mail.example.com with (esmtp) id (qid) for <foo@example.com>; Mon, (envelope-from <g-3851351679-7322-354668012-1559235622088@bounce.news.mapp.com (g-3851351679-7322-354668012-1559235622088@bounce.news.mapp.com)>) 29 Jul 2013 23:12:46 +0900")

expect(t.name).to eq "Received"
expect(t.info).to eq "mail.example.com (192.168.1.1) by mail.example.com with (esmtp) id (qid) for <foo@example.com>"
expect(t.date_time).to eq nil
expect(t.formatted_date).to eq nil
end
end

0 comments on commit 4483651

Please sign in to comment.