CVE-2021-41817
Description
Date.parse in the date gem through 3.2.0 for Ruby allows ReDoS (regular expression Denial of Service) via a long string. The fixed versions are 3.2.1, 3.1.2, 3.0.2, and 2.0.1.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Date.parse in Ruby's date gem through 3.2.0 is vulnerable to ReDoS via a long string, allowing denial of service.
Vulnerability
The Date.parse method in Ruby's date gem (versions 3.2.0 and prior, including 3.1.1, 3.0.1, and 2.0.0) contains vulnerable regular expressions that can lead to a regular expression denial of service (ReDoS). The gem uses internally many regexps for parsing date strings; some of these cause catastrophic backtracking when processing long, specially crafted strings [1], [4]. The affected versions are all date gem releases before the patches: 3.2.1, 3.1.2, 3.0.2, and 2.0.1 [4].
Exploitation
An attacker can exploit this vulnerability by providing a long, maliciously crafted string to any application or library that calls Date.parse (or other date parsing methods) on untrusted input. No authentication or special network position is required if the input is user-supplied (e.g., via HTTP parameters, file uploads, or API payloads). The attack does not require any special permissions or user interaction beyond that needed to deliver the input [1], [4].
Impact
Successful exploitation leads to effective denial of service (DoS) against the Ruby process, as the regex engine consumes excessive CPU time due to catastrophic backtracking. This can cause the application to become unresponsive or crash, affecting availability [1], [4]. Information disclosure or code execution is not reported for this vulnerability.
Mitigation
The fix was released on 2021-11-15 in date gem versions 3.2.1, 3.1.2, 3.0.2, and 2.0.1 [4]. Users should update their date gem to the latest patched version (gem update date) or upgrade Ruby to versions 3.0.3, 2.7.5, or 2.6.9, which bundle the fixed gem [4]. As a workaround, the patch limits input length to 128 bytes by default; compatibility can be maintained by passing limit: nil explicitly, but this restores the vulnerability. No other workarounds are documented in the available references [4].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
dateRubyGems | >= 3.2.0, < 3.2.1 | 3.2.1 |
dateRubyGems | >= 3.1.0, < 3.1.2 | 3.1.2 |
dateRubyGems | >= 3.0.0, < 3.0.2 | 3.0.2 |
dateRubyGems | < 2.0.1 | 2.0.1 |
Affected products
76- Ruby/date gemdescription
- osv-coords75 versionspkg:bitnami/rubypkg:bitnami/ruby-minpkg:gem/datepkg:rpm/almalinux/rubypkg:rpm/almalinux/ruby-default-gemspkg:rpm/almalinux/ruby-develpkg:rpm/almalinux/ruby-docpkg:rpm/almalinux/rubygem-abrtpkg:rpm/almalinux/rubygem-abrt-docpkg:rpm/almalinux/rubygem-bigdecimalpkg:rpm/almalinux/rubygem-bsonpkg:rpm/almalinux/rubygem-bson-docpkg:rpm/almalinux/rubygem-bundlerpkg:rpm/almalinux/rubygem-bundler-docpkg:rpm/almalinux/rubygem-did_you_meanpkg:rpm/almalinux/rubygem-io-consolepkg:rpm/almalinux/rubygem-irbpkg:rpm/almalinux/rubygem-jsonpkg:rpm/almalinux/rubygem-minitestpkg:rpm/almalinux/rubygem-mongopkg:rpm/almalinux/rubygem-mongo-docpkg:rpm/almalinux/rubygem-mysql2pkg:rpm/almalinux/rubygem-mysql2-docpkg:rpm/almalinux/rubygem-net-telnetpkg:rpm/almalinux/rubygem-opensslpkg:rpm/almalinux/rubygem-pgpkg:rpm/almalinux/rubygem-pg-docpkg:rpm/almalinux/rubygem-power_assertpkg:rpm/almalinux/rubygem-psychpkg:rpm/almalinux/rubygem-rakepkg:rpm/almalinux/rubygem-rbspkg:rpm/almalinux/rubygem-rdocpkg:rpm/almalinux/rubygem-rexmlpkg:rpm/almalinux/rubygem-rsspkg:rpm/almalinux/rubygemspkg:rpm/almalinux/rubygems-develpkg:rpm/almalinux/rubygem-test-unitpkg:rpm/almalinux/rubygem-typeprofpkg:rpm/almalinux/rubygem-xmlrpcpkg:rpm/almalinux/ruby-irbpkg:rpm/almalinux/ruby-libspkg:rpm/opensuse/ruby2.5&distro=openSUSE%20Leap%2015.3pkg:rpm/opensuse/ruby2.5&distro=openSUSE%20Leap%2015.4pkg:rpm/opensuse/ruby2.5&distro=openSUSE%20Leap%2015.5pkg:rpm/suse/ruby2.5&distro=SUSE%20Enterprise%20Storage%206pkg:rpm/suse/ruby2.5&distro=SUSE%20Enterprise%20Storage%207pkg:rpm/suse/ruby2.5&distro=SUSE%20Enterprise%20Storage%207.1pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP1-ESPOSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP1-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP2-ESPOSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP2-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP3-ESPOSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP3-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015-ESPOSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Micro%205.0pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Basesystem%2015%20SP3pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Basesystem%2015%20SP4pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Basesystem%2015%20SP5pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Real%20Time%2015%20SP2pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP1-BCLpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP1-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP2-BCLpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP2-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP3-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%2015-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP1pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP2pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP3pkg:rpm/suse/ruby2.5&distro=SUSE%20Manager%20Proxy%204.1pkg:rpm/suse/ruby2.5&distro=SUSE%20Manager%20Proxy%204.2pkg:rpm/suse/ruby2.5&distro=SUSE%20Manager%20Retail%20Branch%20Server%204.1pkg:rpm/suse/ruby2.5&distro=SUSE%20Manager%20Server%204.1pkg:rpm/suse/ruby2.5&distro=SUSE%20Manager%20Server%204.2
>= 2.6.0, < 2.6.9+ 74 more
- (no CPE)range: >= 2.6.0, < 2.6.9
- (no CPE)range: >= 2.6.0, < 2.6.9
- (no CPE)range: >= 3.2.0, < 3.2.1
- (no CPE)range: < 2.6.9-108.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 2.7.6-138.module_el8.6.0+3263+904da987
- (no CPE)range: < 2.6.9-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 2.6.9-108.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 0.3.0-4.module_el8.5.0+259+8cec6917
- (no CPE)range: < 0.3.0-4.module_el8.5.0+259+8cec6917
- (no CPE)range: < 1.4.1-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 4.5.0-1.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 4.5.0-1.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 1.17.2-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 1.16.1-4.module_el8.5.0+2625+ec418553
- (no CPE)range: < 1.3.0-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 0.4.7-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 1.0.0-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 2.1.0-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 5.11.3-108.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 2.8.0-1.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 2.8.0-1.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 0.5.2-1.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 0.5.2-1.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 0.2.0-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 2.1.2-108.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 1.1.4-1.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 1.1.4-1.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 1.1.3-108.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 3.1.0-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 12.3.3-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 1.4.0-141.module_el8.6.0+3263+41cde0c0
- (no CPE)range: < 6.1.2.1-108.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 3.2.5-141.module_el8.6.0+3263+41cde0c0
- (no CPE)range: < 0.2.9-141.module_el8.6.0+3263+41cde0c0
- (no CPE)range: < 3.0.3.1-108.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 3.0.3.1-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 3.2.9-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 0.15.2-141.module_el8.6.0+3263+41cde0c0
- (no CPE)range: < 0.3.0-108.module_el8.5.0+250+ba22dbf7
- (no CPE)range: < 2.5.9-110.module_el8.6.0+3074+4b08f9d4
- (no CPE)range: < 2.6.9-108.module_el8.5.0+2623+08a8ba32
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.29.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.29.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.29.1
- (no CPE)range: < 2.5.9-150000.4.29.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.29.1
- (no CPE)range: < 2.5.9-150000.4.29.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.29.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.29.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.29.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.23.1
- (no CPE)range: < 2.5.9-150000.4.29.1
Patches
13959accef8daAdd length limit option for methods that parses date strings
3 files changed · +326 −89
date.gemspec+1 −1 modified@@ -1,7 +1,7 @@ # frozen_string_literal: true Gem::Specification.new do |s| s.name = "date" - s.version = '3.2.0' + s.version = '3.2.1' s.summary = "A subclass of Object includes Comparable module for handling dates." s.description = "A subclass of Object includes Comparable module for handling dates."
ext/date/date_core.c+296 −88 modified@@ -4328,12 +4328,37 @@ date_s_strptime(int argc, VALUE *argv, VALUE klass) VALUE date__parse(VALUE str, VALUE comp); +static size_t +get_limit(VALUE opt) +{ + if (!NIL_P(opt)) { + VALUE limit = rb_hash_aref(opt, ID2SYM(rb_intern("limit"))); + if (NIL_P(limit)) return SIZE_MAX; + return NUM2SIZET(limit); + } + return 128; +} + +static void +check_limit(VALUE str, VALUE opt) +{ + StringValue(str); + size_t slen = RSTRING_LEN(str); + size_t limit = get_limit(opt); + if (slen > limit) { + rb_raise(rb_eArgError, + "string length (%"PRI_SIZE_PREFIX"u) exceeds the limit %"PRI_SIZE_PREFIX"u", slen, limit); + } +} + static VALUE date_s__parse_internal(int argc, VALUE *argv, VALUE klass) { - VALUE vstr, vcomp, hash; + VALUE vstr, vcomp, hash, opt; - rb_scan_args(argc, argv, "11", &vstr, &vcomp); + rb_scan_args(argc, argv, "11:", &vstr, &vcomp, &opt); + if (!NIL_P(opt)) argc--; + check_limit(vstr, opt); StringValue(vstr); if (!rb_enc_str_asciicompat_p(vstr)) rb_raise(rb_eArgError, @@ -4348,7 +4373,7 @@ date_s__parse_internal(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * Date._parse(string[, comp=true]) -> hash + * Date._parse(string[, comp=true], limit: 128) -> hash * * Parses the given representation of date and time, and returns a * hash of parsed elements. @@ -4363,6 +4388,10 @@ date_s__parse_internal(int argc, VALUE *argv, VALUE klass) * it full. * * Date._parse('2001-02-03') #=> {:year=>2001, :mon=>2, :mday=>3} + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s__parse(int argc, VALUE *argv, VALUE klass) @@ -4372,7 +4401,7 @@ date_s__parse(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * Date.parse(string='-4712-01-01'[, comp=true[, start=Date::ITALY]]) -> date + * Date.parse(string='-4712-01-01'[, comp=true[, start=Date::ITALY]], limit: 128) -> date * * Parses the given representation of date and time, and creates a * date object. @@ -4389,13 +4418,18 @@ date_s__parse(int argc, VALUE *argv, VALUE klass) * Date.parse('2001-02-03') #=> #<Date: 2001-02-03 ...> * Date.parse('20010203') #=> #<Date: 2001-02-03 ...> * Date.parse('3rd Feb 2001') #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_parse(int argc, VALUE *argv, VALUE klass) { - VALUE str, comp, sg; + VALUE str, comp, sg, opt; - rb_scan_args(argc, argv, "03", &str, &comp, &sg); + rb_scan_args(argc, argv, "03:", &str, &comp, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -4407,11 +4441,12 @@ date_s_parse(int argc, VALUE *argv, VALUE klass) } { - VALUE argv2[2], hash; - - argv2[0] = str; - argv2[1] = comp; - hash = date_s__parse(2, argv2, klass); + int argc2 = 2; + VALUE argv2[3]; + argv2[0] = str; + argv2[1] = comp; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__parse(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } @@ -4425,33 +4460,47 @@ VALUE date__jisx0301(VALUE); /* * call-seq: - * Date._iso8601(string) -> hash + * Date._iso8601(string, limit: 128) -> hash * * Returns a hash of parsed elements. + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE -date_s__iso8601(VALUE klass, VALUE str) +date_s__iso8601(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + return date__iso8601(str); } /* * call-seq: - * Date.iso8601(string='-4712-01-01'[, start=Date::ITALY]) -> date + * Date.iso8601(string='-4712-01-01'[, start=Date::ITALY], limit: 128) -> date * * Creates a new Date object by parsing from a string according to * some typical ISO 8601 formats. * * Date.iso8601('2001-02-03') #=> #<Date: 2001-02-03 ...> * Date.iso8601('20010203') #=> #<Date: 2001-02-03 ...> * Date.iso8601('2001-W05-6') #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_iso8601(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -4461,38 +4510,56 @@ date_s_iso8601(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__iso8601(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__iso8601(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._rfc3339(string) -> hash + * Date._rfc3339(string, limit: 128) -> hash * * Returns a hash of parsed elements. + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE -date_s__rfc3339(VALUE klass, VALUE str) +date_s__rfc3339(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + return date__rfc3339(str); } /* * call-seq: - * Date.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> date + * Date.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> date * * Creates a new Date object by parsing from a string according to * some typical RFC 3339 formats. * * Date.rfc3339('2001-02-03T04:05:06+07:00') #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_rfc3339(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -4502,38 +4569,56 @@ date_s_rfc3339(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__rfc3339(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__rfc3339(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._xmlschema(string) -> hash + * Date._xmlschema(string, limit: 128) -> hash * * Returns a hash of parsed elements. + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE -date_s__xmlschema(VALUE klass, VALUE str) +date_s__xmlschema(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + return date__xmlschema(str); } /* * call-seq: - * Date.xmlschema(string='-4712-01-01'[, start=Date::ITALY]) -> date + * Date.xmlschema(string='-4712-01-01'[, start=Date::ITALY], limit: 128) -> date * * Creates a new Date object by parsing from a string according to * some typical XML Schema formats. * * Date.xmlschema('2001-02-03') #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_xmlschema(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -4543,41 +4628,58 @@ date_s_xmlschema(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__xmlschema(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__xmlschema(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._rfc2822(string) -> hash - * Date._rfc822(string) -> hash + * Date._rfc2822(string, limit: 128) -> hash + * Date._rfc822(string, limit: 128) -> hash * * Returns a hash of parsed elements. + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE -date_s__rfc2822(VALUE klass, VALUE str) +date_s__rfc2822(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + return date__rfc2822(str); } /* * call-seq: - * Date.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY]) -> date - * Date.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY]) -> date + * Date.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> date + * Date.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> date * * Creates a new Date object by parsing from a string according to * some typical RFC 2822 formats. * * Date.rfc2822('Sat, 3 Feb 2001 00:00:00 +0000') * #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_rfc2822(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: @@ -4587,39 +4689,56 @@ date_s_rfc2822(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__rfc2822(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__rfc2822(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._httpdate(string) -> hash + * Date._httpdate(string, limit: 128) -> hash * * Returns a hash of parsed elements. + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE -date_s__httpdate(VALUE klass, VALUE str) +date_s__httpdate(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + return date__httpdate(str); } /* * call-seq: - * Date.httpdate(string='Mon, 01 Jan -4712 00:00:00 GMT'[, start=Date::ITALY]) -> date + * Date.httpdate(string='Mon, 01 Jan -4712 00:00:00 GMT'[, start=Date::ITALY], limit: 128) -> date * * Creates a new Date object by parsing from a string according to * some RFC 2616 format. * * Date.httpdate('Sat, 03 Feb 2001 00:00:00 GMT') * #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_httpdate(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: @@ -4629,26 +4748,39 @@ date_s_httpdate(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__httpdate(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__httpdate(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._jisx0301(string) -> hash + * Date._jisx0301(string, limit: 128) -> hash * * Returns a hash of parsed elements. + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE -date_s__jisx0301(VALUE klass, VALUE str) +date_s__jisx0301(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + return date__jisx0301(str); } /* * call-seq: - * Date.jisx0301(string='-4712-01-01'[, start=Date::ITALY]) -> date + * Date.jisx0301(string='-4712-01-01'[, start=Date::ITALY], limit: 128) -> date * * Creates a new Date object by parsing from a string according to * some typical JIS X 0301 formats. @@ -4658,13 +4790,18 @@ date_s__jisx0301(VALUE klass, VALUE str) * For no-era year, legacy format, Heisei is assumed. * * Date.jisx0301('13.02.03') #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_jisx0301(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -4674,7 +4811,11 @@ date_s_jisx0301(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__jisx0301(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__jisx0301(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } @@ -8013,7 +8154,7 @@ datetime_s_strptime(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * DateTime.parse(string='-4712-01-01T00:00:00+00:00'[, comp=true[, start=Date::ITALY]]) -> datetime + * DateTime.parse(string='-4712-01-01T00:00:00+00:00'[, comp=true[, start=Date::ITALY]], limit: 128) -> datetime * * Parses the given representation of date and time, and creates a * DateTime object. @@ -8032,13 +8173,18 @@ datetime_s_strptime(int argc, VALUE *argv, VALUE klass) * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> * DateTime.parse('3rd Feb 2001 04:05:06 PM') * #=> #<DateTime: 2001-02-03T16:05:06+00:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_parse(int argc, VALUE *argv, VALUE klass) { - VALUE str, comp, sg; + VALUE str, comp, sg, opt; - rb_scan_args(argc, argv, "03", &str, &comp, &sg); + rb_scan_args(argc, argv, "03:", &str, &comp, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8050,18 +8196,20 @@ datetime_s_parse(int argc, VALUE *argv, VALUE klass) } { - VALUE argv2[2], hash; - - argv2[0] = str; - argv2[1] = comp; - hash = date_s__parse(2, argv2, klass); + int argc2 = 2; + VALUE argv2[3]; + argv2[0] = str; + argv2[1] = comp; + argv2[2] = opt; + if (!NIL_P(opt)) argc2++; + VALUE hash = date_s__parse(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.iso8601(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> datetime + * DateTime.iso8601(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime object by parsing from a string according to * some typical ISO 8601 formats. @@ -8072,13 +8220,18 @@ datetime_s_parse(int argc, VALUE *argv, VALUE klass) * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> * DateTime.iso8601('2001-W05-6T04:05:06+07:00') * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_iso8601(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8088,27 +8241,37 @@ datetime_s_iso8601(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__iso8601(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2--; + VALUE hash = date_s__iso8601(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> datetime + * DateTime.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime object by parsing from a string according to * some typical RFC 3339 formats. * * DateTime.rfc3339('2001-02-03T04:05:06+07:00') * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_rfc3339(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8118,27 +8281,37 @@ datetime_s_rfc3339(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__rfc3339(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + VALUE hash = date_s__rfc3339(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.xmlschema(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> datetime + * DateTime.xmlschema(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime object by parsing from a string according to * some typical XML Schema formats. * * DateTime.xmlschema('2001-02-03T04:05:06+07:00') * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_xmlschema(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8148,28 +8321,38 @@ datetime_s_xmlschema(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__xmlschema(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + VALUE hash = date_s__xmlschema(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY]) -> datetime - * DateTime.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY]) -> datetime + * DateTime.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> datetime + * DateTime.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime object by parsing from a string according to * some typical RFC 2822 formats. * * DateTime.rfc2822('Sat, 3 Feb 2001 04:05:06 +0700') * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_rfc2822(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8179,7 +8362,12 @@ datetime_s_rfc2822(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__rfc2822(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + VALUE hash = date_s__rfc2822(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } @@ -8193,13 +8381,18 @@ datetime_s_rfc2822(int argc, VALUE *argv, VALUE klass) * * DateTime.httpdate('Sat, 03 Feb 2001 04:05:06 GMT') * #=> #<DateTime: 2001-02-03T04:05:06+00:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_httpdate(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8209,14 +8402,19 @@ datetime_s_httpdate(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__httpdate(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + VALUE hash = date_s__httpdate(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.jisx0301(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> datetime + * DateTime.jisx0301(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime object by parsing from a string according to * some typical JIS X 0301 formats. @@ -8228,13 +8426,18 @@ datetime_s_httpdate(int argc, VALUE *argv, VALUE klass) * * DateTime.jisx0301('13.02.03T04:05:06+07:00') * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_jisx0301(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8244,7 +8447,12 @@ datetime_s_jisx0301(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__jisx0301(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + VALUE hash = date_s__jisx0301(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } @@ -9403,19 +9611,19 @@ Init_date_core(void) rb_define_singleton_method(cDate, "strptime", date_s_strptime, -1); rb_define_singleton_method(cDate, "_parse", date_s__parse, -1); rb_define_singleton_method(cDate, "parse", date_s_parse, -1); - rb_define_singleton_method(cDate, "_iso8601", date_s__iso8601, 1); + rb_define_singleton_method(cDate, "_iso8601", date_s__iso8601, -1); rb_define_singleton_method(cDate, "iso8601", date_s_iso8601, -1); - rb_define_singleton_method(cDate, "_rfc3339", date_s__rfc3339, 1); + rb_define_singleton_method(cDate, "_rfc3339", date_s__rfc3339, -1); rb_define_singleton_method(cDate, "rfc3339", date_s_rfc3339, -1); - rb_define_singleton_method(cDate, "_xmlschema", date_s__xmlschema, 1); + rb_define_singleton_method(cDate, "_xmlschema", date_s__xmlschema, -1); rb_define_singleton_method(cDate, "xmlschema", date_s_xmlschema, -1); - rb_define_singleton_method(cDate, "_rfc2822", date_s__rfc2822, 1); - rb_define_singleton_method(cDate, "_rfc822", date_s__rfc2822, 1); + rb_define_singleton_method(cDate, "_rfc2822", date_s__rfc2822, -1); + rb_define_singleton_method(cDate, "_rfc822", date_s__rfc2822, -1); rb_define_singleton_method(cDate, "rfc2822", date_s_rfc2822, -1); rb_define_singleton_method(cDate, "rfc822", date_s_rfc2822, -1); - rb_define_singleton_method(cDate, "_httpdate", date_s__httpdate, 1); + rb_define_singleton_method(cDate, "_httpdate", date_s__httpdate, -1); rb_define_singleton_method(cDate, "httpdate", date_s_httpdate, -1); - rb_define_singleton_method(cDate, "_jisx0301", date_s__jisx0301, 1); + rb_define_singleton_method(cDate, "_jisx0301", date_s__jisx0301, -1); rb_define_singleton_method(cDate, "jisx0301", date_s_jisx0301, -1); rb_define_method(cDate, "initialize", date_initialize, -1);
test/date/test_date_parse.rb+29 −0 modified@@ -1,6 +1,7 @@ # frozen_string_literal: true require 'test/unit' require 'date' +require 'timeout' class TestDateParse < Test::Unit::TestCase @@ -1228,4 +1229,32 @@ def test_given_string assert_equal(s0, s) end + def test_length_limit + assert_raise(ArgumentError) { Date._parse("1" * 1000) } + assert_raise(ArgumentError) { Date._iso8601("1" * 1000) } + assert_raise(ArgumentError) { Date._rfc3339("1" * 1000) } + assert_raise(ArgumentError) { Date._xmlschema("1" * 1000) } + assert_raise(ArgumentError) { Date._rfc2822("1" * 1000) } + assert_raise(ArgumentError) { Date._rfc822("1" * 1000) } + assert_raise(ArgumentError) { Date._jisx0301("1" * 1000) } + + assert_raise(ArgumentError) { Date.parse("1" * 1000) } + assert_raise(ArgumentError) { Date.iso8601("1" * 1000) } + assert_raise(ArgumentError) { Date.rfc3339("1" * 1000) } + assert_raise(ArgumentError) { Date.xmlschema("1" * 1000) } + assert_raise(ArgumentError) { Date.rfc2822("1" * 1000) } + assert_raise(ArgumentError) { Date.rfc822("1" * 1000) } + assert_raise(ArgumentError) { Date.jisx0301("1" * 1000) } + + assert_raise(ArgumentError) { DateTime.parse("1" * 1000) } + assert_raise(ArgumentError) { DateTime.iso8601("1" * 1000) } + assert_raise(ArgumentError) { DateTime.rfc3339("1" * 1000) } + assert_raise(ArgumentError) { DateTime.xmlschema("1" * 1000) } + assert_raise(ArgumentError) { DateTime.rfc2822("1" * 1000) } + assert_raise(ArgumentError) { DateTime.rfc822("1" * 1000) } + assert_raise(ArgumentError) { DateTime.jisx0301("1" * 1000) } + + assert_raise(ArgumentError) { Date._parse("Jan " + "9" * 1000000) } + assert_raise(Timeout::Error) { Timeout.timeout(1) { Date._parse("Jan " + "9" * 1000000, limit: nil) } } + end end
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
14- github.com/advisories/GHSA-qg54-694p-wgppghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/IUXQCH6FRKANCVZO2Q7D2SQX33FP3KWN/mitrevendor-advisory
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/UTOJGS5IEFDK3UOO7IY4OTTFGHGLSWZF/mitrevendor-advisory
- nvd.nist.gov/vuln/detail/CVE-2021-41817ghsaADVISORY
- security.gentoo.org/glsa/202401-27ghsavendor-advisoryWEB
- github.com/ruby/date/commit/3959accef8da5c128f8a8e2fd54e932a4fb253b0ghsaWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/date/CVE-2021-41817.ymlghsaWEB
- hackerone.com/reports/1254844ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/IUXQCH6FRKANCVZO2Q7D2SQX33FP3KWNghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/UTOJGS5IEFDK3UOO7IY4OTTFGHGLSWZFghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/IUXQCH6FRKANCVZO2Q7D2SQX33FP3KWNghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/UTOJGS5IEFDK3UOO7IY4OTTFGHGLSWZFghsaWEB
- www.ruby-lang.org/en/news/2021/11/15/date-parsing-method-regexp-dos-cve-2021-41817ghsaWEB
- www.ruby-lang.org/en/news/2021/11/15/date-parsing-method-regexp-dos-cve-2021-41817/mitre
News mentions
0No linked articles in our index yet.