CVE-2025-25186
Description
Net::IMAP implements Internet Message Access Protocol (IMAP) client functionality in Ruby. Starting in version 0.3.2 and prior to versions 0.3.8, 0.4.19, and 0.5.6, there is a possibility for denial of service by memory exhaustion in net-imap's response parser. At any time while the client is connected, a malicious server can send can send highly compressed uid-set data which is automatically read by the client's receiver thread. The response parser uses Range#to_a to convert the uid-set data into arrays of integers, with no limitation on the expanded size of the ranges. Versions 0.3.8, 0.4.19, 0.5.6, and higher fix this issue. Additional details for proper configuration of fixed versions and backward compatibility are available in the GitHub Security Advisory.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
net-imapRubyGems | >= 0.3.2, < 0.3.8 | 0.3.8 |
net-imapRubyGems | >= 0.4.0, < 0.4.19 | 0.4.19 |
net-imapRubyGems | >= 0.5.0, < 0.5.6 | 0.5.6 |
Patches
638ce681e594e4c4ed09997cc62710b905d56c8c5a643739dMerge commit from fork
3 files changed · +108 −4
lib/net/imap/config.rb+41 −1 modified@@ -266,6 +266,12 @@ def self.[](config) # CopyUIDData for +COPYUID+ response codes, and UIDPlusData or # AppendUIDData for +APPENDUID+ response codes. # + # UIDPlusData stores its data in arrays of numbers, which is vulnerable to + # a memory exhaustion denial of service attack from an untrusted or + # compromised server. Set this option to +false+ to completely block this + # vulnerability. Otherwise, parser_max_deprecated_uidplus_data_size + # mitigates this vulnerability. + # # AppendUIDData and CopyUIDData are _mostly_ backward-compatible with # UIDPlusData. Most applications should be able to upgrade with little # or no changes. @@ -282,12 +288,41 @@ def self.[](config) # [+true+ <em>(original default)</em>] # ResponseParser only uses UIDPlusData. # + # [+:up_to_max_size+ <em>(default since +v0.5.6+)</em>] + # ResponseParser uses UIDPlusData when the +uid-set+ size is below + # parser_max_deprecated_uidplus_data_size. Above that size, + # ResponseParser uses AppendUIDData or CopyUIDData. + # # [+false+ <em>(planned default for +v0.6+)</em>] # ResponseParser _only_ uses AppendUIDData and CopyUIDData. attr_accessor :parser_use_deprecated_uidplus_data, type: [ - true, false + true, :up_to_max_size, false ] + # The maximum +uid-set+ size that ResponseParser will parse into + # deprecated UIDPlusData. This limit only applies when + # parser_use_deprecated_uidplus_data is not +false+. + # + # <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em> + # + # <em>Support for limiting UIDPlusData to a maximum size was added in + # +v0.3.8+, +v0.4.19+, and +v0.5.6+.</em> + # + # <em>UIDPlusData will be removed in +v0.6+.</em> + # + # ==== Versioned Defaults + # + # Because this limit guards against a remote server causing catastrophic + # memory exhaustion, the versioned default (used by #load_defaults) also + # applies to versions without the feature. + # + # * +0.3+ and prior: <tt>10,000</tt> + # * +0.4+: <tt>1,000</tt> + # * +0.5+: <tt>100</tt> + # * +0.6+: <tt>0</tt> + # + attr_accessor :parser_max_deprecated_uidplus_data_size, type: Integer + # Creates a new config object and initialize its attribute with +attrs+. # # If +parent+ is not given, the global config is used by default. @@ -368,6 +403,7 @@ def defaults_hash sasl_ir: true, responses_without_block: :silence_deprecation_warning, parser_use_deprecated_uidplus_data: true, + parser_max_deprecated_uidplus_data_size: 1000, ).freeze @global = default.new @@ -377,6 +413,7 @@ def defaults_hash version_defaults[0] = Config[0.4].dup.update( sasl_ir: false, parser_use_deprecated_uidplus_data: true, + parser_max_deprecated_uidplus_data_size: 10_000, ).freeze version_defaults[0.0] = Config[0] version_defaults[0.1] = Config[0] @@ -385,6 +422,8 @@ def defaults_hash version_defaults[0.5] = Config[0.4].dup.update( responses_without_block: :warn, + parser_use_deprecated_uidplus_data: :up_to_max_size, + parser_max_deprecated_uidplus_data_size: 100, ).freeze version_defaults[:default] = Config[0.4] @@ -394,6 +433,7 @@ def defaults_hash version_defaults[0.6] = Config[0.5].dup.update( responses_without_block: :frozen_dup, parser_use_deprecated_uidplus_data: false, + parser_max_deprecated_uidplus_data_size: 0, ).freeze version_defaults[:future] = Config[0.6]
lib/net/imap/response_parser.rb+10 −3 modified@@ -1889,9 +1889,16 @@ def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end # TODO: remove this code in the v0.6.0 release def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids) return unless config.parser_use_deprecated_uidplus_data - src_uids &&= src_uids.each_ordered_number.to_a - dst_uids = dst_uids.each_ordered_number.to_a - UIDPlusData.new(validity, src_uids, dst_uids) + compact_uid_sets = [src_uids, dst_uids].compact + count = compact_uid_sets.map { _1.count_with_duplicates }.max + max = config.parser_max_deprecated_uidplus_data_size + if count <= max + src_uids &&= src_uids.each_ordered_number.to_a + dst_uids = dst_uids.each_ordered_number.to_a + UIDPlusData.new(validity, src_uids, dst_uids) + elsif config.parser_use_deprecated_uidplus_data != :up_to_max_size + parse_error("uid-set is too large: %d > %d", count, max) + end end ADDRESS_REGEXP = /\G
test/net/imap/test_imap_response_parser.rb+57 −0 modified@@ -205,16 +205,43 @@ def test_fetch_binary_and_binary_size test "APPENDUID with parser_use_deprecated_uidplus_data = true" do parser = Net::IMAP::ResponseParser.new(config: { parser_use_deprecated_uidplus_data: true, + parser_max_deprecated_uidplus_data_size: 10_000, }) + assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set is too large/ do + parser.parse( + "A004 OK [APPENDUID 1 10000:20000,1] Done\r\n" + ) + end + response = parser.parse("A004 OK [APPENDUID 1 100:200] Done\r\n") + uidplus = response.data.code.data + assert_equal 101, uidplus.assigned_uids.size + parser.config.parser_max_deprecated_uidplus_data_size = 100 + assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set is too large/ do + parser.parse( + "A004 OK [APPENDUID 1 100:200] Done\r\n" + ) + end response = parser.parse("A004 OK [APPENDUID 1 101:200] Done\r\n") uidplus = response.data.code.data assert_instance_of Net::IMAP::UIDPlusData, uidplus assert_equal 100, uidplus.assigned_uids.size end + test "APPENDUID with parser_use_deprecated_uidplus_data = :up_to_max_size" do + parser = Net::IMAP::ResponseParser.new(config: { + parser_use_deprecated_uidplus_data: :up_to_max_size, + parser_max_deprecated_uidplus_data_size: 100 + }) + response = parser.parse("A004 OK [APPENDUID 1 101:200] Done\r\n") + assert_instance_of Net::IMAP::UIDPlusData, response.data.code.data + response = parser.parse("A004 OK [APPENDUID 1 100:200] Done\r\n") + assert_instance_of Net::IMAP::AppendUIDData, response.data.code.data + end + test "APPENDUID with parser_use_deprecated_uidplus_data = false" do parser = Net::IMAP::ResponseParser.new(config: { parser_use_deprecated_uidplus_data: false, + parser_max_deprecated_uidplus_data_size: 10_000_000, }) response = parser.parse("A004 OK [APPENDUID 1 10] Done\r\n") assert_instance_of Net::IMAP::AppendUIDData, response.data.code.data @@ -253,17 +280,47 @@ def test_fetch_binary_and_binary_size test "COPYUID with parser_use_deprecated_uidplus_data = true" do parser = Net::IMAP::ResponseParser.new(config: { parser_use_deprecated_uidplus_data: true, + parser_max_deprecated_uidplus_data_size: 10_000, }) + assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set is too large/ do + parser.parse( + "A004 OK [copyUID 1 10000:20000,1 1:10001] Done\r\n" + ) + end + response = parser.parse("A004 OK [copyUID 1 100:200 1:101] Done\r\n") + uidplus = response.data.code.data + assert_equal 101, uidplus.assigned_uids.size + assert_equal 101, uidplus.source_uids.size + parser.config.parser_max_deprecated_uidplus_data_size = 100 + assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set is too large/ do + parser.parse( + "A004 OK [copyUID 1 100:200 1:101] Done\r\n" + ) + end response = parser.parse("A004 OK [copyUID 1 101:200 1:100] Done\r\n") uidplus = response.data.code.data assert_instance_of Net::IMAP::UIDPlusData, uidplus assert_equal 100, uidplus.assigned_uids.size assert_equal 100, uidplus.source_uids.size end + test "COPYUID with parser_use_deprecated_uidplus_data = :up_to_max_size" do + parser = Net::IMAP::ResponseParser.new(config: { + parser_use_deprecated_uidplus_data: :up_to_max_size, + parser_max_deprecated_uidplus_data_size: 100 + }) + response = parser.parse("A004 OK [COPYUID 1 101:200 1:100] Done\r\n") + copyuid = response.data.code.data + assert_instance_of Net::IMAP::UIDPlusData, copyuid + response = parser.parse("A004 OK [COPYUID 1 100:200 1:101] Done\r\n") + copyuid = response.data.code.data + assert_instance_of Net::IMAP::CopyUIDData, copyuid + end + test "COPYUID with parser_use_deprecated_uidplus_data = false" do parser = Net::IMAP::ResponseParser.new(config: { parser_use_deprecated_uidplus_data: false, + parser_max_deprecated_uidplus_data_size: 10_000_000, }) response = parser.parse("A004 OK [COPYUID 1 101 1] Done\r\n") assert_instance_of Net::IMAP::CopyUIDData, response.data.code.data
cb92191b1ddcMerge commit from fork
6 files changed · +103 −136
lib/net/imap/response_parser.rb+23 −3 modified@@ -7,6 +7,8 @@ class IMAP < Protocol # Parses an \IMAP server response. class ResponseParser + MAX_UID_SET_SIZE = 10_000 + # :call-seq: Net::IMAP::ResponseParser.new -> Net::IMAP::ResponseParser def initialize @str = nil @@ -1379,11 +1381,29 @@ def uid_set case token.symbol when T_NUMBER then [Integer(token.value)] when T_ATOM - token.value.split(",").flat_map {|range| - range = range.split(":").map {|uniqueid| Integer(uniqueid) } - range.size == 1 ? range : Range.new(range.min, range.max).to_a + entries = uid_set__ranges(token.value) + if (count = entries.sum(&:count)) > MAX_UID_SET_SIZE + parse_error("uid-set is too large: %d > 10k", count) + end + entries.flat_map(&:to_a) + end + end + + # returns an array of ranges + def uid_set__ranges(uidset) + entries = [] + uidset.split(",") do |entry| + uids = entry.split(":", 2).map {|uid| + unless uid =~ /\A[1-9][0-9]*\z/ + parse_error("invalid uid-set uid: %p", uid) + end + uid = Integer(uid) + NumValidator.ensure_nz_number(uid) + uid } + entries << Range.new(*uids.minmax) end + entries end def nil_atom
test/net/fixtures/cacert.pem+22 −22 modified@@ -1,24 +1,24 @@ -----BEGIN CERTIFICATE----- -MIID7TCCAtWgAwIBAgIJAIltvxrFAuSnMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD -VQQGEwJKUDEQMA4GA1UECAwHU2hpbWFuZTEUMBIGA1UEBwwLTWF0ei1lIGNpdHkx -FzAVBgNVBAoMDlJ1YnkgQ29yZSBUZWFtMRUwEwYDVQQDDAxSdWJ5IFRlc3QgQ0Ex -JTAjBgkqhkiG9w0BCQEWFnNlY3VyaXR5QHJ1YnktbGFuZy5vcmcwHhcNMTkwMTAy -MDI1ODI4WhcNMjQwMTAxMDI1ODI4WjCBjDELMAkGA1UEBhMCSlAxEDAOBgNVBAgM -B1NoaW1hbmUxFDASBgNVBAcMC01hdHotZSBjaXR5MRcwFQYDVQQKDA5SdWJ5IENv -cmUgVGVhbTEVMBMGA1UEAwwMUnVieSBUZXN0IENBMSUwIwYJKoZIhvcNAQkBFhZz -ZWN1cml0eUBydWJ5LWxhbmcub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAznlbjRVhz1NlutHVrhcGnK8W0qug2ujKXv1njSC4U6nJF6py7I9EeehV -SaKePyv+I9z3K1LnfUHOtUbdwdKC77yN66A6q2aqzu5q09/NSykcZGOIF0GuItYI -3nvW3IqBddff2ffsyR+9pBjfb5AIPP08WowF9q4s1eGULwZc4w2B8PFhtxYANd7d -BvGLXFlcufv9tDtzyRi4t7eqxCRJkZQIZNZ6DHHIJrNxejOILfHLarI12yk8VK6L -2LG4WgGqyeePiRyd1o1MbuiAFYqAwpXNUbRKg5NaZGwBHZk8UZ+uFKt1QMBURO5R -WFy1c349jbWszTqFyL4Lnbg9HhAowQIDAQABo1AwTjAdBgNVHQ4EFgQU9tEiKdU9 -I9derQyc5nWPnc34nVMwHwYDVR0jBBgwFoAU9tEiKdU9I9derQyc5nWPnc34nVMw -DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAxj7F/u3C3fgq24N7hGRA -of7ClFQxGmo/IGT0AISzW3HiVYiFaikKhbO1NwD9aBpD8Zwe62sCqMh8jGV/b0+q -aOORnWYNy2R6r9FkASAglmdF6xn3bhgGD5ls4pCvcG9FynGnGc24g6MrjFNrBYUS -2iIZsg36i0IJswo/Dy6HLphCms2BMCD3DeWtfjePUiTmQHJo6HsQIKP/u4N4Fvee -uMBInei2M4VU74fLXbmKl1F9AEX7JDP3BKSZG19Ch5pnUo4uXM1uNTGsi07P4Y0s -K44+SKBC0bYEFbDK0eQWMrX3kIhkPxyIWhxdq9/NqPYjShuSEAhA6CSpmRg0pqc+ -mA== +MIID+zCCAuOgAwIBAgIUGMvHl3EhtKPKcgc3NQSAYfFuC+8wDQYJKoZIhvcNAQEL +BQAwgYwxCzAJBgNVBAYTAkpQMRAwDgYDVQQIDAdTaGltYW5lMRQwEgYDVQQHDAtN +YXR6LWUgY2l0eTEXMBUGA1UECgwOUnVieSBDb3JlIFRlYW0xFTATBgNVBAMMDFJ1 +YnkgVGVzdCBDQTElMCMGCSqGSIb3DQEJARYWc2VjdXJpdHlAcnVieS1sYW5nLm9y +ZzAeFw0yNDAxMDExMTQ3MjNaFw0zMzEyMjkxMTQ3MjNaMIGMMQswCQYDVQQGEwJK +UDEQMA4GA1UECAwHU2hpbWFuZTEUMBIGA1UEBwwLTWF0ei1lIGNpdHkxFzAVBgNV +BAoMDlJ1YnkgQ29yZSBUZWFtMRUwEwYDVQQDDAxSdWJ5IFRlc3QgQ0ExJTAjBgkq +hkiG9w0BCQEWFnNlY3VyaXR5QHJ1YnktbGFuZy5vcmcwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCw+egZQ6eumJKq3hfKfED4dE/tL4FI5sjqont9ABVI ++1GSqyi1bFBgsRjM0THllIdMbKmJtWwnKW8J+5OgNN8y6Xxv8JmM/Y5vQt2lis0f +qXmG8UTz0VTWdlAXXmhUs6lSADvAaIe4RVrCsZ97L3ZQTryY7JRVcbB4khUN3Gp0 +yg+801SXzoFTTa+UGIRLE66jH51aa5VXu99hnv1OiH8tQrjdi8mH6uG/icq4XuIe +NWMF32wHqIOOPvQcWV3M5D2vxJEj702Ku6k9OQXkAo17qRSEonWW4HtLbtmS8He1 +JNPc/n3dVUm+fM6NoDXPoLP7j55G9zKyqGtGAWXAj1MTAgMBAAGjUzBRMB0GA1Ud +DgQWBBSJGVleDvFp9cu9R+E0/OKYzGkwkTAfBgNVHSMEGDAWgBSJGVleDvFp9cu9 +R+E0/OKYzGkwkTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBl +8GLB8skAWlkSw/FwbUmEV3zyqu+p7PNP5YIYoZs0D74e7yVulGQ6PKMZH5hrZmHo +orFSQU+VUUirG8nDGj7Rzce8WeWBxsaDGC8CE2dq6nC6LuUwtbdMnBrH0LRWAz48 +jGFF3jHtVz8VsGfoZTZCjukWqNXvU6hETT9GsfU+PZqbqcTVRPH52+XgYayKdIbD +r97RM4X3+aXBHcUW0b76eyyi65RR/Xtvn8ioZt2AdX7T2tZzJyXJN3Hupp77s6Ui +AZR35SToHCZeTZD12YBvLBdaTPLZN7O/Q/aAO9ZiJaZ7SbFOjz813B2hxXab4Fob +2uJX6eMWTVxYK5D4M9lm -----END CERTIFICATE-----
test/net/fixtures/Makefile+3 −3 modified@@ -5,11 +5,11 @@ regen_certs: make server.crt cacert.pem: server.key - openssl req -new -x509 -days 1825 -key server.key -out cacert.pem -text -subj "/C=JP/ST=Shimane/L=Matz-e city/O=Ruby Core Team/CN=Ruby Test CA/emailAddress=security@ruby-lang.org" + openssl req -new -x509 -days 3650 -key server.key -out cacert.pem -subj "/C=JP/ST=Shimane/L=Matz-e city/O=Ruby Core Team/CN=Ruby Test CA/emailAddress=security@ruby-lang.org" server.csr: - openssl req -new -key server.key -out server.csr -text -subj "/C=JP/ST=Shimane/O=Ruby Core Team/OU=Ruby Test/CN=localhost" + openssl req -new -key server.key -out server.csr -subj "/C=JP/ST=Shimane/O=Ruby Core Team/OU=Ruby Test/CN=localhost" server.crt: server.csr cacert.pem - openssl x509 -days 1825 -CA cacert.pem -CAkey server.key -set_serial 00 -in server.csr -req -text -out server.crt + openssl x509 -days 3650 -CA cacert.pem -CAkey server.key -set_serial 00 -in server.csr -req -out server.crt rm server.csr
test/net/fixtures/server.crt+19 −80 modified@@ -1,82 +1,21 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 2 (0x2) - Signature Algorithm: sha256WithRSAEncryption - Issuer: C=JP, ST=Shimane, L=Matz-e city, O=Ruby Core Team, CN=Ruby Test CA/emailAddress=security@ruby-lang.org - Validity - Not Before: Jan 2 03:27:13 2019 GMT - Not After : Jan 1 03:27:13 2024 GMT - Subject: C=JP, ST=Shimane, O=Ruby Core Team, OU=Ruby Test, CN=localhost - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: - 00:e8:da:9c:01:2e:2b:10:ec:49:cd:5e:07:13:07: - 9c:70:9e:c6:74:bc:13:c2:e1:6f:c6:82:fd:e3:48: - e0:2c:a5:68:c7:9e:42:de:60:54:65:e6:6a:14:57: - 7a:30:d0:cc:b5:b6:d9:c3:d2:df:c9:25:97:54:67: - cf:f6:be:5e:cb:8b:ee:03:c5:e1:e2:f9:e7:f7:d1: - 0c:47:f0:b8:da:33:5a:ad:41:ad:e7:b5:a2:7b:b7: - bf:30:da:60:f8:e3:54:a2:bc:3a:fd:1b:74:d9:dc: - 74:42:e9:29:be:df:ac:b4:4f:eb:32:f4:06:f1:e1: - 8c:4b:a8:8b:fb:29:e7:b1:bf:1d:01:ee:73:0f:f9: - 40:dc:d5:15:79:d9:c6:73:d0:c0:dd:cb:e4:da:19: - 47:80:c6:14:04:72:fd:9a:7c:8f:11:82:76:49:04: - 79:cc:f2:5c:31:22:95:13:3e:5d:40:a6:4d:e0:a3: - 02:26:7d:52:3b:bb:ed:65:a1:0f:ed:6b:b0:3c:d4: - de:61:15:5e:d3:dd:68:09:9f:4a:57:a5:c2:a9:6d: - 86:92:c5:f4:a4:d4:b7:13:3b:52:63:24:05:e2:cc: - e3:8a:3c:d4:35:34:2b:10:bb:58:72:e7:e1:8d:1d: - 74:8c:61:16:20:3d:d0:1c:4e:8f:6e:fd:fe:64:10: - 4f:41 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - Netscape Comment: - OpenSSL Generated Certificate - X509v3 Subject Key Identifier: - ED:28:C2:7E:AB:4B:C8:E8:FE:55:6D:66:95:31:1C:2D:60:F9:02:36 - X509v3 Authority Key Identifier: - keyid:F6:D1:22:29:D5:3D:23:D7:5E:AD:0C:9C:E6:75:8F:9D:CD:F8:9D:53 - - Signature Algorithm: sha256WithRSAEncryption - 1d:b8:c5:8b:72:41:20:65:ad:27:6f:15:63:06:26:12:8d:9c: - ad:ca:f4:db:97:b4:90:cb:ff:35:94:bb:2a:a7:a1:ab:1e:35: - 2d:a5:3f:c9:24:b0:1a:58:89:75:3e:81:0a:2c:4f:98:f9:51: - fb:c0:a3:09:d0:0a:9b:e7:a2:b7:c3:60:40:c8:f4:6d:b2:6a: - 56:12:17:4c:00:24:31:df:9c:60:ae:b1:68:54:a9:e6:b5:4a: - 04:e6:92:05:86:d9:5a:dc:96:30:a5:58:de:14:99:0f:e5:15: - 89:3e:9b:eb:80:e3:bd:83:c3:ea:33:35:4b:3e:2f:d3:0d:64: - 93:67:7f:8d:f5:3f:0c:27:bc:37:5a:cc:d6:47:16:af:5a:62: - d2:da:51:f8:74:06:6b:24:ad:28:68:08:98:37:7d:ed:0e:ab: - 1e:82:61:05:d0:ba:75:a0:ab:21:b0:9a:fd:2b:54:86:1d:0d: - 1f:c2:d4:77:1f:72:26:5e:ad:8a:9f:09:36:6d:44:be:74:c2: - 5a:3e:ff:5c:9d:75:d6:38:7b:c5:39:f9:44:6e:a1:d1:8e:ff: - 63:db:c4:bb:c6:91:92:ca:5c:60:9b:1d:eb:0a:de:08:ee:bf: - da:76:03:65:62:29:8b:f8:7f:c7:86:73:1e:f6:1f:2d:89:69: - fd:be:bd:6e -----BEGIN CERTIFICATE----- -MIID4zCCAsugAwIBAgIBAjANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCSlAx -EDAOBgNVBAgMB1NoaW1hbmUxFDASBgNVBAcMC01hdHotZSBjaXR5MRcwFQYDVQQK -DA5SdWJ5IENvcmUgVGVhbTEVMBMGA1UEAwwMUnVieSBUZXN0IENBMSUwIwYJKoZI -hvcNAQkBFhZzZWN1cml0eUBydWJ5LWxhbmcub3JnMB4XDTE5MDEwMjAzMjcxM1oX -DTI0MDEwMTAzMjcxM1owYDELMAkGA1UEBhMCSlAxEDAOBgNVBAgMB1NoaW1hbmUx -FzAVBgNVBAoMDlJ1YnkgQ29yZSBUZWFtMRIwEAYDVQQLDAlSdWJ5IFRlc3QxEjAQ -BgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AOjanAEuKxDsSc1eBxMHnHCexnS8E8Lhb8aC/eNI4CylaMeeQt5gVGXmahRXejDQ -zLW22cPS38kll1Rnz/a+XsuL7gPF4eL55/fRDEfwuNozWq1Bree1onu3vzDaYPjj -VKK8Ov0bdNncdELpKb7frLRP6zL0BvHhjEuoi/sp57G/HQHucw/5QNzVFXnZxnPQ -wN3L5NoZR4DGFARy/Zp8jxGCdkkEeczyXDEilRM+XUCmTeCjAiZ9Uju77WWhD+1r -sDzU3mEVXtPdaAmfSlelwqlthpLF9KTUtxM7UmMkBeLM44o81DU0KxC7WHLn4Y0d -dIxhFiA90BxOj279/mQQT0ECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhC -AQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFO0o -wn6rS8jo/lVtZpUxHC1g+QI2MB8GA1UdIwQYMBaAFPbRIinVPSPXXq0MnOZ1j53N -+J1TMA0GCSqGSIb3DQEBCwUAA4IBAQAduMWLckEgZa0nbxVjBiYSjZytyvTbl7SQ -y/81lLsqp6GrHjUtpT/JJLAaWIl1PoEKLE+Y+VH7wKMJ0Aqb56K3w2BAyPRtsmpW -EhdMACQx35xgrrFoVKnmtUoE5pIFhtla3JYwpVjeFJkP5RWJPpvrgOO9g8PqMzVL -Pi/TDWSTZ3+N9T8MJ7w3WszWRxavWmLS2lH4dAZrJK0oaAiYN33tDqsegmEF0Lp1 -oKshsJr9K1SGHQ0fwtR3H3ImXq2Knwk2bUS+dMJaPv9cnXXWOHvFOflEbqHRjv9j -28S7xpGSylxgmx3rCt4I7r/adgNlYimL+H/HhnMe9h8tiWn9vr1u +MIIDYTCCAkkCAQAwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAkpQMRAwDgYD +VQQIDAdTaGltYW5lMRQwEgYDVQQHDAtNYXR6LWUgY2l0eTEXMBUGA1UECgwOUnVi +eSBDb3JlIFRlYW0xFTATBgNVBAMMDFJ1YnkgVGVzdCBDQTElMCMGCSqGSIb3DQEJ +ARYWc2VjdXJpdHlAcnVieS1sYW5nLm9yZzAeFw0yNDAxMDExMTQ3MjNaFw0zMzEy +MjkxMTQ3MjNaMGAxCzAJBgNVBAYTAkpQMRAwDgYDVQQIDAdTaGltYW5lMRcwFQYD +VQQKDA5SdWJ5IENvcmUgVGVhbTESMBAGA1UECwwJUnVieSBUZXN0MRIwEAYDVQQD +DAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCw+egZ +Q6eumJKq3hfKfED4dE/tL4FI5sjqont9ABVI+1GSqyi1bFBgsRjM0THllIdMbKmJ +tWwnKW8J+5OgNN8y6Xxv8JmM/Y5vQt2lis0fqXmG8UTz0VTWdlAXXmhUs6lSADvA +aIe4RVrCsZ97L3ZQTryY7JRVcbB4khUN3Gp0yg+801SXzoFTTa+UGIRLE66jH51a +a5VXu99hnv1OiH8tQrjdi8mH6uG/icq4XuIeNWMF32wHqIOOPvQcWV3M5D2vxJEj +702Ku6k9OQXkAo17qRSEonWW4HtLbtmS8He1JNPc/n3dVUm+fM6NoDXPoLP7j55G +9zKyqGtGAWXAj1MTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACtGNdj5TEtnJBYp +M+LhBeU3oNteldfycEm993gJp6ghWZFg23oX8fVmyEeJr/3Ca9bAgDqg0t9a0npN +oWKEY6wVKqcHgu3gSvThF5c9KhGbeDDmlTSVVNQmXWX0K2d4lS2cwZHH8mCm2mrY +PDqlEkSc7k4qSiqigdS8i80Yk+lDXWsm8CjsiC93qaRM7DnS0WPQR0c16S95oM6G +VklFKUSDAuFjw9aVWA/nahOucjn0w5fVW6lyIlkBslC1ChlaDgJmvhz+Ol3iMsE0 +kAmFNu2KKPVrpMWaBID49QwQTDyhetNLaVVFM88iUdA9JDoVMEuP1mm39JqyzHTu +uBrdP4Q= -----END CERTIFICATE-----
test/net/fixtures/server.key+27 −28 modified@@ -1,28 +1,27 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDo2pwBLisQ7EnN -XgcTB5xwnsZ0vBPC4W/Ggv3jSOAspWjHnkLeYFRl5moUV3ow0My1ttnD0t/JJZdU -Z8/2vl7Li+4DxeHi+ef30QxH8LjaM1qtQa3ntaJ7t78w2mD441SivDr9G3TZ3HRC -6Sm+36y0T+sy9Abx4YxLqIv7Keexvx0B7nMP+UDc1RV52cZz0MDdy+TaGUeAxhQE -cv2afI8RgnZJBHnM8lwxIpUTPl1Apk3gowImfVI7u+1loQ/ta7A81N5hFV7T3WgJ -n0pXpcKpbYaSxfSk1LcTO1JjJAXizOOKPNQ1NCsQu1hy5+GNHXSMYRYgPdAcTo9u -/f5kEE9BAgMBAAECggEBAOHkwhc7DLh8IhTDNSW26oMu5OP2WU1jmiYAigDmf+OQ -DBgrZj+JQBci8qINQxL8XLukSZn5hvQCLc7Kbyu1/wyEEUFDxSGGwwzclodr9kho -LX2LDASPZrOSzD2+fPi2wTKmXKuS6Uc44OjQfZkYMNkz9r4Vkm8xGgOD3VipjIYX -QXlhhdqkXZcNABsihCV52GKkDFSVm8jv95YJc5xhoYCy/3a4/qPdF0aT2R7oYUej -hKrxVDskyooe8Zg/JTydZNV5GQEDmW01/K3r6XGT26oPi1AqMU1gtv/jkW56CRQQ -1got8smnqM+AV7Slf9R6DauIPdQJ2S8wsr/o8ISBsOECgYEA9YrqEP2gAYSGFXRt -liw0WI2Ant8BqXS6yvq1jLo/qWhLw/ph4Di73OQ2mpycVTpgfGr2wFPQR1XJ+0Fd -U+Ir/C3Q7FK4VIGHK7B0zNvZr5tEjlFfeRezo2JMVw5YWeSagIFcSwK+KqCTH9qc -pw/Eb8nB/4XNcpTZu7Fg0Wc+ooUCgYEA8sVaicn1Wxkpb45a4qfrA6wOr5xdJ4cC -A5qs7vjX2OdPIQOmoQhdI7bCWFXZzF33wA4YCws6j5wRaySLIJqdms8Gl9QnODy1 -ZlA5gwKToBC/jqPmWAXSKb8EH7cHilaxU9OKnQ7CfwlGLHqjMtjrhR7KHlt3CVRs -oRmvsjZVXI0CgYAmPedslAO6mMhFSSfULrhMXmV82OCqYrrA6EEkVNGbcdnzAOkD -gfKIWabDd8bFY10po4Mguy0CHzNhBXIioWQWV5BlbhC1YKMLw+S9DzSdLAKGY9gJ -xQ4+UQ3wtRQ/k+IYR413RUsW2oFvgZ3KSyNeAb9MK6uuv84VdG/OzVSs/QKBgQDn -kap//l2EbObiWyaERunckdVcW0lcN+KK75J/TGwPoOwQsLvTpPe65kxRGGrtDsEQ -uCDk/+v3KkZPLgdrrTAih9FhJ+PVN8tMcb+6IM4SA4fFFr/UPJEwct0LJ3oQ0grJ -y+HPWFHb/Uurh7t99/4H98uR02sjQh1wOeEmm78mzQKBgQDm+LzGH0se6CXQ6cdZ -g1JRZeXkDEsrW3hfAsW62xJQmXcWxBoblP9OamMY+A06rM5og3JbDk5Zm6JsOaA8 -wS2gw4ilp46jors4eQey8ux7kB9LzdBoDBBElnsbjLO8oBNZlVcYXg+6BOl/CUi7 -2whRF0FEjKA8ehrNhAq+VFfFNw== ------END PRIVATE KEY----- +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAsPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqso +tWxQYLEYzNEx5ZSHTGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE +89FU1nZQF15oVLOpUgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNU +l86BU02vlBiESxOuox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9s +B6iDjj70HFldzOQ9r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P59 +3VVJvnzOjaA1z6Cz+4+eRvcysqhrRgFlwI9TEwIDAQABAoIBAEEYiyDP29vCzx/+ +dS3LqnI5BjUuJhXUnc6AWX/PCgVAO+8A+gZRgvct7PtZb0sM6P9ZcLrweomlGezI +FrL0/6xQaa8bBr/ve/a8155OgcjFo6fZEw3Dz7ra5fbSiPmu4/b/kvrg+Br1l77J +aun6uUAs1f5B9wW+vbR7tzbT/mxaUeDiBzKpe15GwcvbJtdIVMa2YErtRjc1/5B2 +BGVXyvlJv0SIlcIEMsHgnAFOp1ZgQ08aDzvilLq8XVMOahAhP1O2A3X8hKdXPyrx +IVWE9bS9ptTo+eF6eNl+d7htpKGEZHUxinoQpWEBTv+iOoHsVunkEJ3vjLP3lyI/ +fY0NQ1ECgYEA3RBXAjgvIys2gfU3keImF8e/TprLge1I2vbWmV2j6rZCg5r/AS0u +pii5CvJ5/T5vfJPNgPBy8B/yRDs+6PJO1GmnlhOkG9JAIPkv0RBZvR0PMBtbp6nT +Y3yo1lwamBVBfY6rc0sLTzosZh2aGoLzrHNMQFMGaauORzBFpY5lU50CgYEAzPHl +u5DI6Xgep1vr8QvCUuEesCOgJg8Yh1UqVoY/SmQh6MYAv1I9bLGwrb3WW/7kqIoD +fj0aQV5buVZI2loMomtU9KY5SFIsPV+JuUpy7/+VE01ZQM5FdY8wiYCQiVZYju9X +Wz5LxMNoz+gT7pwlLCsC4N+R8aoBk404aF1gum8CgYAJ7VTq7Zj4TFV7Soa/T1eE +k9y8a+kdoYk3BASpCHJ29M5R2KEA7YV9wrBklHTz8VzSTFTbKHEQ5W5csAhoL5Fo +qoHzFFi3Qx7MHESQb9qHyolHEMNx6QdsHUn7rlEnaTTyrXh3ifQtD6C0yTmFXUIS +CW9wKApOrnyKJ9nI0HcuZQKBgQCMtoV6e9VGX4AEfpuHvAAnMYQFgeBiYTkBKltQ +XwozhH63uMMomUmtSG87Sz1TmrXadjAhy8gsG6I0pWaN7QgBuFnzQ/HOkwTm+qKw +AsrZt4zeXNwsH7QXHEJCFnCmqw9QzEoZTrNtHJHpNboBuVnYcoueZEJrP8OnUG3r +UjmopwKBgAqB2KYYMUqAOvYcBnEfLDmyZv9BTVNHbR2lKkMYqv5LlvDaBxVfilE0 +2riO4p6BaAdvzXjKeRrGNEKoHNBpOSfYCOM16NjL8hIZB1CaV3WbT5oY+jp7Mzd5 +7d56RZOE+ERK2uz/7JX9VSsM/LbH9pJibd4e8mikDS9ntciqOH/3 +-----END RSA PRIVATE KEY-----
test/net/imap/test_imap_response_parser.rb+9 −0 modified@@ -438,4 +438,13 @@ def test_uidplus_copyuid__uid_mapping ) end + def test_uidplus_copyuid__too_large + parser = Net::IMAP::ResponseParser.new + assert_raise Net::IMAP::ResponseParseError, /uid-set is too large/ do + parser.parse( + "A004 OK [copyUID 1 10000:20000,1 1:10001] Done\r\n" + ) + end + end + end
70e3ddd071a9Merge commit from fork
3 files changed · +108 −5
lib/net/imap/config.rb+41 −2 modified@@ -291,6 +291,12 @@ def self.[](config) # CopyUIDData for +COPYUID+ response codes, and UIDPlusData or # AppendUIDData for +APPENDUID+ response codes. # + # UIDPlusData stores its data in arrays of numbers, which is vulnerable to + # a memory exhaustion denial of service attack from an untrusted or + # compromised server. Set this option to +false+ to completely block this + # vulnerability. Otherwise, parser_max_deprecated_uidplus_data_size + # mitigates this vulnerability. + # # AppendUIDData and CopyUIDData are _mostly_ backward-compatible with # UIDPlusData. Most applications should be able to upgrade with little # or no changes. @@ -307,12 +313,41 @@ def self.[](config) # [+true+ <em>(original default)</em>] # ResponseParser only uses UIDPlusData. # + # [+:up_to_max_size+ <em>(default since +v0.5.6+)</em>] + # ResponseParser uses UIDPlusData when the +uid-set+ size is below + # parser_max_deprecated_uidplus_data_size. Above that size, + # ResponseParser uses AppendUIDData or CopyUIDData. + # # [+false+ <em>(planned default for +v0.6+)</em>] # ResponseParser _only_ uses AppendUIDData and CopyUIDData. attr_accessor :parser_use_deprecated_uidplus_data, type: [ - true, false + true, :up_to_max_size, false ] + # The maximum +uid-set+ size that ResponseParser will parse into + # deprecated UIDPlusData. This limit only applies when + # parser_use_deprecated_uidplus_data is not +false+. + # + # <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em> + # + # <em>Support for limiting UIDPlusData to a maximum size was added in + # +v0.3.8+, +v0.4.19+, and +v0.5.6+.</em> + # + # <em>UIDPlusData will be removed in +v0.6+.</em> + # + # ==== Versioned Defaults + # + # Because this limit guards against a remote server causing catastrophic + # memory exhaustion, the versioned default (used by #load_defaults) also + # applies to versions without the feature. + # + # * +0.3+ and prior: <tt>10,000</tt> + # * +0.4+: <tt>1,000</tt> + # * +0.5+: <tt>100</tt> + # * +0.6+: <tt>0</tt> + # + attr_accessor :parser_max_deprecated_uidplus_data_size, type: Integer + # Creates a new config object and initialize its attribute with +attrs+. # # If +parent+ is not given, the global config is used by default. @@ -393,7 +428,8 @@ def defaults_hash sasl_ir: true, enforce_logindisabled: true, responses_without_block: :warn, - parser_use_deprecated_uidplus_data: true, + parser_use_deprecated_uidplus_data: :up_to_max_size, + parser_max_deprecated_uidplus_data_size: 100, ).freeze @global = default.new @@ -406,6 +442,7 @@ def defaults_hash responses_without_block: :silence_deprecation_warning, enforce_logindisabled: false, parser_use_deprecated_uidplus_data: true, + parser_max_deprecated_uidplus_data_size: 10_000, ).freeze version_defaults[0.0] = Config[0] version_defaults[0.1] = Config[0] @@ -414,13 +451,15 @@ def defaults_hash version_defaults[0.4] = Config[0.3].dup.update( sasl_ir: true, + parser_max_deprecated_uidplus_data_size: 1000, ).freeze version_defaults[0.5] = Config[:current] version_defaults[0.6] = Config[0.5].dup.update( responses_without_block: :frozen_dup, parser_use_deprecated_uidplus_data: false, + parser_max_deprecated_uidplus_data_size: 0, ).freeze version_defaults[:next] = Config[0.6] version_defaults[:future] = Config[:next]
lib/net/imap/response_parser.rb+10 −3 modified@@ -2023,9 +2023,16 @@ def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end # TODO: remove this code in the v0.6.0 release def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids) return unless config.parser_use_deprecated_uidplus_data - src_uids &&= src_uids.each_ordered_number.to_a - dst_uids = dst_uids.each_ordered_number.to_a - UIDPlusData.new(validity, src_uids, dst_uids) + compact_uid_sets = [src_uids, dst_uids].compact + count = compact_uid_sets.map { _1.count_with_duplicates }.max + max = config.parser_max_deprecated_uidplus_data_size + if count <= max + src_uids &&= src_uids.each_ordered_number.to_a + dst_uids = dst_uids.each_ordered_number.to_a + UIDPlusData.new(validity, src_uids, dst_uids) + elsif config.parser_use_deprecated_uidplus_data != :up_to_max_size + parse_error("uid-set is too large: %d > %d", count, max) + end end ADDRESS_REGEXP = /\G
test/net/imap/test_imap_response_parser.rb+57 −0 modified@@ -214,16 +214,43 @@ def test_fetch_binary_and_binary_size test "APPENDUID with parser_use_deprecated_uidplus_data = true" do parser = Net::IMAP::ResponseParser.new(config: { parser_use_deprecated_uidplus_data: true, + parser_max_deprecated_uidplus_data_size: 10_000, }) + assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set is too large/ do + parser.parse( + "A004 OK [APPENDUID 1 10000:20000,1] Done\r\n" + ) + end + response = parser.parse("A004 OK [APPENDUID 1 100:200] Done\r\n") + uidplus = response.data.code.data + assert_equal 101, uidplus.assigned_uids.size + parser.config.parser_max_deprecated_uidplus_data_size = 100 + assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set is too large/ do + parser.parse( + "A004 OK [APPENDUID 1 100:200] Done\r\n" + ) + end response = parser.parse("A004 OK [APPENDUID 1 101:200] Done\r\n") uidplus = response.data.code.data assert_instance_of Net::IMAP::UIDPlusData, uidplus assert_equal 100, uidplus.assigned_uids.size end + test "APPENDUID with parser_use_deprecated_uidplus_data = :up_to_max_size" do + parser = Net::IMAP::ResponseParser.new(config: { + parser_use_deprecated_uidplus_data: :up_to_max_size, + parser_max_deprecated_uidplus_data_size: 100 + }) + response = parser.parse("A004 OK [APPENDUID 1 101:200] Done\r\n") + assert_instance_of Net::IMAP::UIDPlusData, response.data.code.data + response = parser.parse("A004 OK [APPENDUID 1 100:200] Done\r\n") + assert_instance_of Net::IMAP::AppendUIDData, response.data.code.data + end + test "APPENDUID with parser_use_deprecated_uidplus_data = false" do parser = Net::IMAP::ResponseParser.new(config: { parser_use_deprecated_uidplus_data: false, + parser_max_deprecated_uidplus_data_size: 10_000_000, }) response = parser.parse("A004 OK [APPENDUID 1 10] Done\r\n") assert_instance_of Net::IMAP::AppendUIDData, response.data.code.data @@ -262,17 +289,47 @@ def test_fetch_binary_and_binary_size test "COPYUID with parser_use_deprecated_uidplus_data = true" do parser = Net::IMAP::ResponseParser.new(config: { parser_use_deprecated_uidplus_data: true, + parser_max_deprecated_uidplus_data_size: 10_000, }) + assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set is too large/ do + parser.parse( + "A004 OK [copyUID 1 10000:20000,1 1:10001] Done\r\n" + ) + end + response = parser.parse("A004 OK [copyUID 1 100:200 1:101] Done\r\n") + uidplus = response.data.code.data + assert_equal 101, uidplus.assigned_uids.size + assert_equal 101, uidplus.source_uids.size + parser.config.parser_max_deprecated_uidplus_data_size = 100 + assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set is too large/ do + parser.parse( + "A004 OK [copyUID 1 100:200 1:101] Done\r\n" + ) + end response = parser.parse("A004 OK [copyUID 1 101:200 1:100] Done\r\n") uidplus = response.data.code.data assert_instance_of Net::IMAP::UIDPlusData, uidplus assert_equal 100, uidplus.assigned_uids.size assert_equal 100, uidplus.source_uids.size end + test "COPYUID with parser_use_deprecated_uidplus_data = :up_to_max_size" do + parser = Net::IMAP::ResponseParser.new(config: { + parser_use_deprecated_uidplus_data: :up_to_max_size, + parser_max_deprecated_uidplus_data_size: 100 + }) + response = parser.parse("A004 OK [COPYUID 1 101:200 1:100] Done\r\n") + copyuid = response.data.code.data + assert_instance_of Net::IMAP::UIDPlusData, copyuid + response = parser.parse("A004 OK [COPYUID 1 100:200 1:101] Done\r\n") + copyuid = response.data.code.data + assert_instance_of Net::IMAP::CopyUIDData, copyuid + end + test "COPYUID with parser_use_deprecated_uidplus_data = false" do parser = Net::IMAP::ResponseParser.new(config: { parser_use_deprecated_uidplus_data: false, + parser_max_deprecated_uidplus_data_size: 10_000_000, }) response = parser.parse("A004 OK [COPYUID 1 101 1] Done\r\n") assert_instance_of Net::IMAP::CopyUIDData, response.data.code.data
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
10- github.com/advisories/GHSA-7fc5-f82f-cx69ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-25186ghsaADVISORY
- github.com/ruby/net-imap/commit/70e3ddd071a94e450b3238570af482c296380b35nvdWEB
- github.com/ruby/net-imap/commit/c8c5a643739d2669f0c9a6bb9770d0c045fd74a3nvdWEB
- github.com/ruby/net-imap/commit/cb92191b1ddce2d978d01b56a0883b6ecf0b1022nvdWEB
- github.com/ruby/net-imap/security/advisories/GHSA-7fc5-f82f-cx69nvdWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/net-imap/CVE-2025-25186.ymlghsaWEB
- ruby.github.io/net-imap/Net/IMAP/AppendUIDData.htmlghsaWEB
- ruby.github.io/net-imap/Net/IMAP/CopyUIDData.htmlghsaWEB
- ruby.github.io/net-imap/Net/IMAP/UIDPlusData.htmlghsaWEB
News mentions
0No linked articles in our index yet.