diff --git a/SOURCES/rubygem-net-imap-0.3.10-Fix-Information-disclosure-via-man-in-the-middle-attack-bypassing-TLS-CVE-2026-42246.patch b/SOURCES/rubygem-net-imap-0.3.10-Fix-Information-disclosure-via-man-in-the-middle-attack-bypassing-TLS-CVE-2026-42246.patch new file mode 100644 index 0000000..c0290b5 --- /dev/null +++ b/SOURCES/rubygem-net-imap-0.3.10-Fix-Information-disclosure-via-man-in-the-middle-attack-bypassing-TLS-CVE-2026-42246.patch @@ -0,0 +1,324 @@ +From 5cf70183a4be110336d45fd955d456667b0d776f Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Sun, 29 Mar 2026 12:26:48 -0400 +Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=8D=92=20pick=20257ede0df:=20?= + =?UTF-8?q?=F0=9F=A5=85=20Re-raise=20`#starttls`=20error=20from=20receiver?= + =?UTF-8?q?=20thread=20[backports=20#395]?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Backports #395 to `v0.3-stable`. The tests required an additional +rescue-and-ignore for the server thread in `starttls_test`, which was +already present in all later branches. + +--------- + +When `start_tls_session` raises an exception, that's caught in the +receiver thread, but not re-raised. Fortunately, `@sock` will now be +a permanently broken SSLSocket, so I don't think this can lead to +accidentally using an insecure connection. + +Even so, `#starttls` should disconnect the socket and re-raise the error +immediately. + +Failing test case was provided by @rhenium in #394. + +Co-authored-by: Kazuki Yamaguchi +--- + lib/net/imap.rb | 10 +++++++++- + test/net/imap/test_imap.rb | 15 +++++++++++++++ + 2 files changed, 24 insertions(+), 1 deletion(-) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index d45304f289..e3eddc69d5 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -378,7 +378,8 @@ def logout + + # Sends a STARTTLS command to start TLS session. + def starttls(options = {}, verify = true) +- send_command("STARTTLS") do |resp| ++ error = nil ++ ok = send_command("STARTTLS") do |resp| + if resp.kind_of?(TaggedResponse) && resp.name == "OK" + begin + # for backward compatibility +@@ -388,7 +389,14 @@ def starttls(options = {}, verify = true) + end + start_tls_session(options) + end ++ rescue Exception => error ++ raise # note that the error backtrace is in the receiver_thread + end ++ if error ++ disconnect ++ raise error ++ end ++ ok + end + + # Sends an AUTHENTICATE command to authenticate the client. +diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb +index 85fb71d440..7f05db2ecb 100644 +--- a/test/net/imap/test_imap.rb ++++ b/test/net/imap/test_imap.rb +@@ -113,6 +113,20 @@ def test_imaps_post_connection_check + end + + if defined?(OpenSSL::SSL) ++ def test_starttls_unknown_ca ++ imap = nil ++ ex = nil ++ starttls_test do |port| ++ imap = Net::IMAP.new("localhost", port: port) ++ begin ++ imap.starttls ++ rescue => ex ++ end ++ imap ++ end ++ assert_kind_of(OpenSSL::SSL::SSLError, ex) ++ end ++ + def test_starttls + imap = nil + starttls_test do |port| +@@ -831,6 +845,7 @@ def starttls_test + sock.gets + sock.print("* BYE terminating connection\r\n") + sock.print("RUBY0002 OK LOGOUT completed\r\n") ++ rescue OpenSSL::SSL::SSLError + ensure + sock.close + server.close + +From 09e762a1c7bdfcd1e1964d00328fdd2719127147 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Tue, 14 Feb 2023 00:13:11 -0500 +Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=8D=92=20pick=20609acd9fa:=20?= + =?UTF-8?q?=F0=9F=A5=85=20Add=20new=20`InvalidResponseError`=20exception?= + =?UTF-8?q?=20class=20[backport=20#198]?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This copies the InvalidResponseError from #198 and the rdoc update to +UnknownResponseError. The behavioral changes from that PR have _not_ +been copied. +--- + lib/net/imap.rb | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index e3eddc69d5..a33a054e89 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -3729,7 +3729,27 @@ class BadResponseError < ResponseError + class ByeResponseError < ResponseError + end + ++ # Error raised when the server sends an invalid response. ++ # ++ # This is different from UnknownResponseError: the response has been ++ # rejected. Although it may be parsable, the server is forbidden from ++ # sending it in the current context. The client should automatically ++ # disconnect, abruptly (without logout). ++ # ++ # Note that InvalidResponseError does not inherit from ResponseError: it ++ # can be raised before the response is fully parsed. A related ++ # ResponseParseError or ResponseError may be the #cause. ++ class InvalidResponseError < Error ++ end ++ + # Error raised upon an unknown response from the server. ++ # ++ # This is different from InvalidResponseError: the response may be a ++ # valid extension response and the server may be allowed to send it in ++ # this context, but Net::IMAP either does not know how to parse it or ++ # how to handle it. This could result from enabling unknown or ++ # unhandled extensions. The connection may still be usable, ++ # but—depending on context—it may be prudent to disconnect. + class UnknownResponseError < ResponseError + end + + +From 88cc24d757c46677cf029f655d95c22336b084cd Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Fri, 27 Mar 2026 17:16:25 -0400 +Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=8D=92=20pick=2046636cae8:=20?= + =?UTF-8?q?=E2=9D=8C=F0=9F=94=92=20Add=20failing=20test=20for=20STARTTLS?= + =?UTF-8?q?=20stripping=20[backport=20#664]?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +I'm putting this in its own commit to simplify testing across backports. +Also, I'm taking a "belt-and-suspenders" approach, and I'm going to test +that either of the two fixes passes the tests. +--- + test/net/imap/test_imap.rb | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb +index 7f05db2ecb..a109e289f3 100644 +--- a/test/net/imap/test_imap.rb ++++ b/test/net/imap/test_imap.rb +@@ -151,6 +151,40 @@ def test_starttls_stripping + imap + end + end ++ ++ def test_starttls_stripping_ok_sent_before_response ++ # to coordinate between threads (better than sleep) ++ server_to_client, client_to_server = Queue.new, Queue.new ++ imap = nil ++ server = create_tcp_server ++ port = server.addr[1] ++ start_server do ++ sock = server.accept ++ begin ++ sock.print("* OK test server\r\n") ++ assert_equal :send_malicious_response, client_to_server.pop ++ sock.print("RUBY0001 OK hahaha, fooled you!\r\n") ++ server_to_client << :malicious_response_sent ++ sock.gets ++ ensure ++ sock.close ++ server.close ++ end ++ end ++ begin ++ imap = Net::IMAP.new("localhost", :port => port) ++ client_to_server << :send_malicious_response ++ assert_equal :malicious_response_sent, server_to_client.pop ++ sleep 0.010 # to be sure the network buffers have flushed, etc ++ assert_raise(Net::IMAP::InvalidResponseError) do ++ imap.starttls(:ca_file => CA_FILE) ++ end ++ assert imap.disconnected? ++ ensure ++ imap.disconnect if imap && !imap.disconnected? ++ end ++ assert imap.disconnected? ++ end + end + + def start_server + +From f2a8c666cbfa7ef67b1d09de923d7dc74338d7a5 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Fri, 27 Mar 2026 17:31:11 -0400 +Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=8D=92=20pick=2062eea6ffe:=20?= + =?UTF-8?q?=F0=9F=94=92=F0=9F=A5=85=20Ensure=20STARTTLS=20tagged=20respons?= + =?UTF-8?q?e=20was=20handled=20[backport=20#664]?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Taking a "belt-and-suspenders" approach to a STARTTLS stripping attack: + +This handles `STARTTLS` as a special-case: if the `STARTTLS` handler +did not run, for _whatever_ reason, an exception _must_ be raised and +the connection dropped. + +_No_ command should ever receive a tagged `OK` prior to completely +sending the command. But `STARTTLS` is security-sensitive enough to +warrant this special-case handler. +--- + lib/net/imap.rb | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index a33a054e89..c45c4612d6 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -378,9 +378,11 @@ def logout + + # Sends a STARTTLS command to start TLS session. + def starttls(options = {}, verify = true) ++ handled = false + error = nil + ok = send_command("STARTTLS") do |resp| + if resp.kind_of?(TaggedResponse) && resp.name == "OK" ++ handled = true + begin + # for backward compatibility + certs = options.to_str +@@ -396,6 +398,13 @@ def starttls(options = {}, verify = true) + disconnect + raise error + end ++ unless handled ++ disconnect ++ raise InvalidResponseError, ++ "STARTTLS handler was bypassed, although server responded %p" % [ ++ ok.raw_data.chomp ++ ] ++ end + ok + end + + +From 577bc7ddb780a10e1ba44f42404f14a24e541b01 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Fri, 27 Mar 2026 18:00:09 -0400 +Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=8D=92=20pick=2024d5c773d:=20?= + =?UTF-8?q?=F0=9F=94=92=F0=9F=A5=85=20Handle=20tagged=20"OK"=20to=20incomp?= + =?UTF-8?q?lete=20command=20[backport=20#664]?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Taking a "belt-and-suspenders" approach: + +This is a potential problem for any command which registers a response +handler: a malicious server can easily guess what the next tag will be, +and send an `OK` response _before_ the client the response handler is +attached. + +`STARTTLS` is an extreme example of this issue: if the `STARTTLS` +handler does not run, then `#starttls` will not start the TLS session, +and the connection is not secured, _but no error is raised._ + +We should _also_ attach the response handler before sending the `CRLF`, +but that is neither necessary (the response handler will added before +the `synchronize` mutex is unlocked) nor sufficient (the fake `OK` can +be sent _much_ earlier). + +On the other hand, it _is_ okay for the server to send an error tagged +response (`NO` or `BAD`), before the sending the command has completed. +--- + lib/net/imap.rb | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index c45c4612d6..1a2afc0ea7 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -1284,6 +1284,7 @@ def send_command(cmd, *args, &block) + put_string(" ") + send_data(i, tag) + end ++ guard_against_tagged_response_skipping_handler!(tag) + put_string(CRLF) + if cmd == "LOGOUT" + @logout_command_tag = tag +@@ -1299,6 +1300,17 @@ def send_command(cmd, *args, &block) + end + end + end ++ rescue InvalidResponseError ++ disconnect ++ raise ++ end ++ ++ def guard_against_tagged_response_skipping_handler!(tag) ++ return unless (resp = @tagged_responses[tag])&.name&.upcase == "OK" ++ raise(InvalidResponseError, ++ "Server sent tagged 'OK' before command was finished: %p. " \ ++ "This could indicate a malicious server or client-side " \ ++ "command injection. Disconnecting." % [resp.raw_data.chomp]) + end + + def generate_tag diff --git a/SOURCES/rubygem-net-imap-0.4.24-Fix-IMAP-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch b/SOURCES/rubygem-net-imap-0.4.24-Fix-IMAP-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch new file mode 100644 index 0000000..d76759d --- /dev/null +++ b/SOURCES/rubygem-net-imap-0.4.24-Fix-IMAP-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch @@ -0,0 +1,241 @@ +From ec11376b38dbfbd1dde52d3410def338c7656170 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Wed, 22 Apr 2026 11:25:06 -0400 +Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=8D=92=20edit=20ca72ac45:=20=E2=99=BB?= + =?UTF-8?q?=EF=B8=8F=20Extract=20superclass=20for=20(internal)=20command?= + =?UTF-8?q?=20data?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Unlike the cherry-picked commit (ca72ac45), this _only_ makes +`CommandData` the superclass for `Literal` and `Atom`. Because those +are the classes that will be modified by later cherry-picked commits. +This allows those other commits to merge more cleanly, and work with +fewer modifications. +--- + lib/net/imap.rb | 50 +++++++++++++++++++++++++------------------------ + 1 file changed, 26 insertions(+), 24 deletions(-) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index 1a2afc0ea7..92ff9a0fbe 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -1571,33 +1571,44 @@ def start_tls_session(params = {}) + end + end + +- class RawData # :nodoc: +- def send_data(imap, tag) +- imap.__send__(:put_string, @data) ++ # simplistic emulation of CommandData = Data.define(:data) ++ class CommandData # :nodoc: ++ class << self ++ def new(arg = nil, data: arg) super(data: data) end ++ alias :[] :new + end + +- def validate ++ def initialize(data:) ++ @data = data ++ freeze + end + +- private ++ attr_reader :data + +- def initialize(data) +- @data = data +- end +- end ++ def to_h(&block) block ? to_h.to_h(&block) : { data: data } end ++ def ==(other) self.class === other && to_h == other.to_h end ++ def eql?(other) self.class === other && to_h.eql?(other.to_h) end ++ ++ # following class definition goes beyond the basic Data.define(:data) ++ ## + +- class Atom # :nodoc: + def send_data(imap, tag) +- imap.__send__(:put_string, @data) ++ raise NoMethodError, "#{self.class} must implement #{__method__}" + end + + def validate + end ++ end + +- private ++ class RawData < CommandData # :nodoc: ++ def send_data(imap, tag) ++ imap.__send__(:put_string, @data) ++ end ++ end + +- def initialize(data) +- @data = data ++ class Atom < CommandData # :nodoc: ++ def send_data(imap, tag) ++ imap.__send__(:put_string, @data) + end + end + +@@ -1616,19 +1627,10 @@ def initialize(data) + end + end + +- class Literal # :nodoc: ++ class Literal < CommandData # :nodoc: + def send_data(imap, tag) + imap.__send__(:send_literal, @data, tag) + end +- +- def validate +- end +- +- private +- +- def initialize(data) +- @data = data +- end + end + + class MessageSet # :nodoc: + +From f793ce76e5f417c011a437418feb8378039e4a37 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Thu, 19 Feb 2026 15:06:08 -0500 +Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=8D=92=20pick=209db3e9d60:=20?= + =?UTF-8?q?=F0=9F=A5=85=20Strictly=20validate=20symbol=20(\flag)=20argumen?= + =?UTF-8?q?ts=20[backports=20#657]?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Flags should not allow `atom-specials`. + +Previously, no validation was done on symbol data. Sending atom or flag +args which contain atom specials could lead to various errors. + +Although this could theoretically include injection attacks, this is not +considered to be a critical vulnerability in `net-imap`, for the +following reason: Valid "system flag" inputs are restricted to an +enumerated set of RFC-defined flag types. User-defined "keyword" flags +are sent as atoms, not flags, which use string inputs (strings which +can't be sent as an atom will be quoted or sent as a literal). `\Seen` +as a flag (symbol argument) is semantically different from `Seen` as a +keyword (string argument). So there is no scenario where it is +appropriate to call `#to_sym` on unvetted user input. Any code which +calls `#to_sym` indiscriminately on user-input is already buggy. + +Nevertheless, users should reasonably be able to rely on `net-imap` to +do very basic input validation on its basic input types. +--- + lib/net/imap.rb | 33 +++++++++++++++++++++++++++------ + 1 file changed, 27 insertions(+), 6 deletions(-) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index 92ff9a0fbe..5dc0638e2f 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -1349,6 +1349,7 @@ def validate_data(data) + end + when Time + when Symbol ++ Flag.validate(data) + else + data.validate + end +@@ -1367,7 +1368,7 @@ def send_data(data, tag = nil) + when Time + send_time_data(data) + when Symbol +- send_symbol_data(data) ++ Flag[data].send_data(self, tag) + else + data.send_data(self, tag) + end +@@ -1437,10 +1438,6 @@ def send_time_data(time) + put_string(s) + end + +- def send_symbol_data(symbol) +- put_string("\\" + symbol.to_s) +- end +- + def search_internal(cmd, keys, charset) + if keys.instance_of?(String) + keys = [RawData.new(keys)] +@@ -1592,6 +1589,12 @@ def eql?(other) self.class === other && to_h.eql?(other.to_h) end + # following class definition goes beyond the basic Data.define(:data) + ## + ++ def self.validate(...) ++ data = new(...) ++ data.validate ++ data ++ end ++ + def send_data(imap, tag) + raise NoMethodError, "#{self.class} must implement #{__method__}" + end +@@ -1607,8 +1610,26 @@ def send_data(imap, tag) + end + + class Atom < CommandData # :nodoc: ++ def initialize(**) ++ super ++ validate ++ end ++ ++ def validate ++ data.to_s.ascii_only? \ ++ or raise DataFormatError, "#{self.class} must be ASCII only" ++ data.match?(ResponseParser::Patterns::ATOM_SPECIALS) \ ++ and raise DataFormatError, "#{self.class} must not contain atom-specials" ++ end ++ + def send_data(imap, tag) +- imap.__send__(:put_string, @data) ++ imap.__send__(:put_string, data.to_s) ++ end ++ end ++ ++ class Flag < Atom # :nodoc: ++ def send_data(imap, tag) ++ imap.__send__(:put_string, "\\#{data}") + end + end + + +From e407aa69f28b71f20eb83145be14ff8a2e57436f Mon Sep 17 00:00:00 2001 +From: Jarek Prokop +Date: Tue, 9 Jun 2026 18:14:51 +0200 +Subject: [PATCH 3/3] Cherry-pick ATOM_SPECIALS for validation regex. + +The previous commit relies on ResponseParsers::Patterns::ATOM_SPECIALS +to be available. We could either hardcode the regex in the line where +the matching happens, but instead have it live in its own constant for +easier reference and less edits to the actual commit backport. + +The line ported from: +https://github.com/ruby/net-imap/commit/92db350b24c388d2a2104f36cac9caa49a1044df +--- + lib/net/imap.rb | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index 5dc0638e2f..07af17c008 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -2222,6 +2222,10 @@ def multipart? + end + + class ResponseParser # :nodoc: ++ module Patterns ++ ATOM_SPECIALS = /[(){ \x00-\x1f\x7f%*"\\\]]/n ++ end ++ + def initialize + @str = nil + @pos = nil diff --git a/SPECS/ruby.spec b/SPECS/ruby.spec index 891e8bb..91cb7cc 100644 --- a/SPECS/ruby.spec +++ b/SPECS/ruby.spec @@ -22,7 +22,7 @@ %endif -%global release 166 +%global release 167 %{!?release_string:%define release_string %{?development_release:0.}%{release}%{?development_release:.%{development_release}}%{?dist}} # The RubyGems library has to stay out of Ruby directory tree, since the @@ -347,6 +347,19 @@ Patch80: ruby-3.4.0-ruby-net-http-test_https.rb-fix-test_session_reuse_but_expir # Fix arbitrary code execution via deserialization bypass in ERB. (CVE-2026-41316) # https://github.com/ruby/erb/commit/ef61b591b270f8ba58d47f12472a1c53a77b4d61 Patch81: rubygem-erb-4.0.3.1-Fix-arbitrary-code-execution-via-deserialization-bypass-CVE-2026-41316.patch +# Fix information disclosure via man in the middle attack bypassing TLS. (CVE-2026-42246) +# https://github.com/ruby/net-imap/commit/e3a6ba4 +# https://github.com/ruby/net-imap/commit/2068d46 +# https://github.com/ruby/net-imap/commit/f96ab6c +# https://github.com/ruby/net-imap/commit/d16e994 +# https://github.com/ruby/net-imap/commit/6b2fda5 +Patch82: rubygem-net-imap-0.3.10-Fix-Information-disclosure-via-man-in-the-middle-attack-bypassing-TLS-CVE-2026-42246.patch +# Fix CVE-2026-42258 IMAP Command Injection via Symbol Arguments. +# https://github.com/ruby/net-imap/commit/1eb27278a601be1135910dae6ab6e517559a2e4a +# https://github.com/ruby/net-imap/commit/bbd9eb7ecca506fa43b656368f7aebef8ac09182 +# Additionally, backport validation regex ATOM_SPECIALS that's needed, from +# https://github.com/ruby/net-imap/commit/92db350b24c388d2a2104f36cac9caa49a1044df +Patch83: rubygem-net-imap-0.4.24-Fix-IMAP-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch Requires: %{name}-libs%{?_isa} = %{version}-%{release} Suggests: rubypick @@ -830,6 +843,8 @@ rm -rf ext/fiddle/libffi* %patch79 -p1 %patch80 -p1 %patch81 -p1 +%patch82 -p1 +%patch83 -p1 # Instead of adjusting patch's directory, use the following form where # we first enter the correct directory, this allows more general application @@ -1612,9 +1627,16 @@ make runruby TESTRUN_SCRIPT=" \ %changelog +* Thu Jun 11 2026 Jarek Prokop - 3.0.7-167 +- Fix information disclosure via MITM attack bypassing TLS in net-imap. + (CVE-2026-42246) + Resolves: RHEL-181763 +- Fix command injection via Symbol arguments in net-imap. (CVE-2026-42258) + Resolves: RHEL-181802 + * Mon Apr 27 2026 Jarek Prokop - 3.0.7-166 - Fix arbitrary code execution via deserialization bypass in ERB. (CVE-2026-41316) - Resolves: RHEL-171254 + Resolves: RHEL-171256 * Fri Apr 11 2025 Jarek Prokop - 3.0.7-165 - Fix Denial of Service in CGI::Cookie.parse. (CVE-2025-27219)