diff --git a/SOURCES/rubygem-net-imap-0.4.24-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch b/SOURCES/rubygem-net-imap-0.4.24-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch new file mode 100644 index 0000000..2c1d493 --- /dev/null +++ b/SOURCES/rubygem-net-imap-0.4.24-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch @@ -0,0 +1,209 @@ +From e55d73b170eb4a5523a411cdc9bd5cf13121694e Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Wed, 22 Apr 2026 11:25:06 -0400 +Subject: [PATCH 1/2] =?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/command_data.rb | 50 +++++++++++++++++++----------------- + 1 file changed, 26 insertions(+), 24 deletions(-) + +diff --git a/lib/net/imap/command_data.rb b/lib/net/imap/command_data.rb +index 180a254..91c7336 100644 +--- a/lib/net/imap/command_data.rb ++++ b/lib/net/imap/command_data.rb +@@ -119,33 +119,44 @@ module Net + put_string("\\" + symbol.to_s) + 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 + +@@ -164,19 +175,10 @@ module Net + 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 d1c362631589b20e59f5e64fe1ee9222b3ea07f9 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Thu, 19 Feb 2026 15:06:08 -0500 +Subject: [PATCH 2/2] =?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/command_data.rb | 33 +++++++++++++++++++++++++++------ + 1 file changed, 27 insertions(+), 6 deletions(-) + +diff --git a/lib/net/imap/command_data.rb b/lib/net/imap/command_data.rb +index 91c7336..499002f 100644 +--- a/lib/net/imap/command_data.rb ++++ b/lib/net/imap/command_data.rb +@@ -25,6 +25,7 @@ module Net + end + when Time, Date, DateTime + when Symbol ++ Flag.validate(data) + else + data.validate + end +@@ -45,7 +46,7 @@ module Net + when Date + send_date_data(data) + when Symbol +- send_symbol_data(data) ++ Flag[data].send_data(self, tag) + else + data.send_data(self, tag) + end +@@ -115,10 +116,6 @@ module Net + def send_date_data(date) put_string Net::IMAP.encode_date(date) end + def send_time_data(time) put_string Net::IMAP.encode_time(time) end + +- def send_symbol_data(symbol) +- put_string("\\" + symbol.to_s) +- end +- + # simplistic emulation of CommandData = Data.define(:data) + class CommandData # :nodoc: + class << self +@@ -140,6 +137,12 @@ module Net + # 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 +@@ -155,8 +158,26 @@ module Net + 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 + diff --git a/SOURCES/rubygem-net-imap-0.4.24-DoS-via-crafted-IMAP-responses-CVE-2026-42245.patch b/SOURCES/rubygem-net-imap-0.4.24-DoS-via-crafted-IMAP-responses-CVE-2026-42245.patch new file mode 100644 index 0000000..047db54 --- /dev/null +++ b/SOURCES/rubygem-net-imap-0.4.24-DoS-via-crafted-IMAP-responses-CVE-2026-42245.patch @@ -0,0 +1,340 @@ +From cd5ccc6948eac1a0b5d5da625ab19f56df6cfaa5 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Tue, 31 Mar 2026 19:28:20 -0400 +Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=8D=92=20pick=20341ab6281:=20?= + =?UTF-8?q?=E2=9A=A1=EF=B8=8F=F0=9F=94=92=EF=B8=8F=20Fix=20non-linear=20pe?= + =?UTF-8?q?rformance=20in=20ResponseReader?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +A very large response with many small repeated literals can trigger +super-linear time. This happens because the regular expression that +checks for literal continuation matches from the beginning of the buffer +every time. + +This could be mitigated by searching from an offset, based on what has +already been processed, or only searching the most recent line (before +merging it with the buffer), but that is still `O(n)` on line length. + +The regexp is anchored to the end of the string, so searching in reverse +from the end of the string should be `O(1)`. This is accomplished by +converting `=~` to `rindex`. + +Note that this _does_ slow down the "no literals" scenario. + +``` +$ benchmark-driver benchmarks/response_reader.yml --filter KiB +Warming up -------------------------------------- + 1KiB with no literals 143.564k i/s - 153.197k times in 1.067099s (6.97μs/i) + 10KiB with no literals 27.394k i/s - 28.864k times in 1.053670s (36.50μs/i) +100KiB with no literals 2.926k i/s - 3.157k times in 1.079109s (341.81μs/i) + 1KiB of 25B literals 2.786k i/s - 2.970k times in 1.066159s (358.98μs/i) + 10KiB of 25B literals 263.498 i/s - 286.000 times in 1.085396s (3.80ms/i) +100KiB of 25B literals 19.470 i/s - 20.000 times in 1.027203s (51.36ms/i) + 1KiB of 0B literals 530.014 i/s - 530.000 times in 0.999974s (1.89ms/i) + 10KiB of 0B literals 45.239 i/s - 50.000 times in 1.105233s (22.10ms/i) +100KiB of 0B literals 3.075 i/s - 4.000 times in 1.300721s (325.18ms/i) +Calculating ------------------------------------- + local YJIT + 1KiB with no literals 137.049k 159.971k i/s - 430.691k times in 3.142607s 2.692304s + 10KiB with no literals 27.272k 28.101k i/s - 82.181k times in 3.013413s 2.924470s +100KiB with no literals 2.941k 2.937k i/s - 8.776k times in 2.984095s 2.988129s + 1KiB of 25B literals 2.803k 4.136k i/s - 8.357k times in 2.981249s 2.020772s + 10KiB of 25B literals 262.978 385.394 i/s - 790.000 times in 3.004055s 2.049850s +100KiB of 25B literals 18.355 22.549 i/s - 58.000 times in 3.159962s 2.572152s + 1KiB of 0B literals 505.733 759.572 i/s - 1.590k times in 3.143953s 2.093285s + 10KiB of 0B literals 45.414 67.569 i/s - 135.000 times in 2.972648s 1.997962s +100KiB of 0B literals 2.722 3.510 i/s - 9.000 times in 3.306786s 2.564007s + +Comparison: + 1KiB with no literals + YJIT: 159971.1 i/s + local: 137049.0 i/s - 1.17x slower + + 10KiB with no literals + YJIT: 28101.2 i/s + local: 27271.7 i/s - 1.03x slower + + 100KiB with no literals + local: 2940.9 i/s + YJIT: 2937.0 i/s - 1.00x slower + + 1KiB of 25B literals + YJIT: 4135.5 i/s + local: 2803.2 i/s - 1.48x slower + + 10KiB of 25B literals + YJIT: 385.4 i/s + local: 263.0 i/s - 1.47x slower + + 100KiB of 25B literals + YJIT: 22.5 i/s + local: 18.4 i/s - 1.23x slower + + 1KiB of 0B literals + YJIT: 759.6 i/s + local: 505.7 i/s - 1.50x slower + + 10KiB of 0B literals + YJIT: 67.6 i/s + local: 45.4 i/s - 1.49x slower + + 100KiB of 0B literals + YJIT: 3.5 i/s + local: 2.7 i/s - 1.29x slower +``` + +For responses that are larger than 10KiB, the benchmarks do take another +dip. Despite that, I believe the algorithm _is_ still linear, and that +the performance hit on large responses is probably due to the large +strings inducing memory locality (paging/caching) bottlenecks. +--- + lib/net/imap/response_reader.rb | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/net/imap/response_reader.rb b/lib/net/imap/response_reader.rb +index fd7561f..d59d672 100644 +--- a/lib/net/imap/response_reader.rb ++++ b/lib/net/imap/response_reader.rb +@@ -32,7 +32,7 @@ module Net + def empty?; buff.empty? end + def done?; line_done? && !get_literal_size end + def line_done?; buff.end_with?(CRLF) end +- def get_literal_size; /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i end ++ def get_literal_size; buff.rindex(/\{(\d+)\}\r\n\z/n) && $1.to_i end + + def read_line + buff << (@sock.gets(CRLF, read_limit) or throw :eof) + +From c6493156d14dbac9e46a0b14e7d01ee5b83c70a9 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Wed, 15 Apr 2026 10:00:24 -0400 +Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=8D=92=20pick=2049c516d62:=20?= + =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Faster=20ResponseParser:=20short-circuit?= + =?UTF-8?q?=20no=20literal?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +I was suprised at how much slower `buff.rindex` is (vs `=~`) when it +doesn't match. This speeds up that case significantly (it's now faster +than it was prior to the `rindex` change), with only a small impact in +the case when it does match. + +``` +$ benchmark-driver benchmarks/response_reader.yml --filter KiB +Warming up -------------------------------------- + 1KiB with no literals 202.754k i/s - 210.144k times in 1.036449s (4.93μs/i) + 10KiB with no literals 55.683k i/s - 57.541k times in 1.033362s (17.96μs/i) +100KiB with no literals 6.654k i/s - 7.176k times in 1.078491s (150.29μs/i) + 1KiB of 25B literals 2.780k i/s - 2.959k times in 1.064363s (359.70μs/i) + 10KiB of 25B literals 260.357 i/s - 286.000 times in 1.098491s (3.84ms/i) +100KiB of 25B literals 19.485 i/s - 20.000 times in 1.026445s (51.32ms/i) + 1KiB of 0B literals 506.675 i/s - 550.000 times in 1.085508s (1.97ms/i) + 10KiB of 0B literals 44.384 i/s - 45.000 times in 1.013872s (22.53ms/i) +100KiB of 0B literals 3.063 i/s - 4.000 times in 1.305939s (326.48ms/i) +Calculating ------------------------------------- + local YJIT + 1KiB with no literals 194.355k 247.756k i/s - 608.261k times in 3.129645s 2.455086s + 10KiB with no literals 55.733k 58.585k i/s - 167.049k times in 2.997311s 2.851414s +100KiB with no literals 6.553k 6.453k i/s - 19.961k times in 3.045870s 3.093461s + 1KiB of 25B literals 2.732k 4.061k i/s - 8.340k times in 3.052737s 2.053682s + 10KiB of 25B literals 256.552 379.524 i/s - 781.000 times in 3.044220s 2.057840s +100KiB of 25B literals 17.804 23.286 i/s - 58.000 times in 3.257733s 2.490779s + 1KiB of 0B literals 467.714 703.446 i/s - 1.520k times in 3.249846s 2.160791s + 10KiB of 0B literals 45.376 65.876 i/s - 133.000 times in 2.931045s 2.018955s +100KiB of 0B literals 3.072 3.840 i/s - 9.000 times in 2.929458s 2.343586s + +Comparison: + 1KiB with no literals + YJIT: 247755.5 i/s + local: 194354.7 i/s - 1.27x slower + + 10KiB with no literals + YJIT: 58584.6 i/s + local: 55733.0 i/s - 1.05x slower + + 100KiB with no literals + local: 6553.5 i/s + YJIT: 6452.6 i/s - 1.02x slower + + 1KiB of 25B literals + YJIT: 4061.0 i/s + local: 2732.0 i/s - 1.49x slower + + 10KiB of 25B literals + YJIT: 379.5 i/s + local: 256.6 i/s - 1.48x slower + + 100KiB of 25B literals + YJIT: 23.3 i/s + local: 17.8 i/s - 1.31x slower + + 1KiB of 0B literals + YJIT: 703.4 i/s + local: 467.7 i/s - 1.50x slower + + 10KiB of 0B literals + YJIT: 65.9 i/s + local: 45.4 i/s - 1.45x slower + + 100KiB of 0B literals + YJIT: 3.8 i/s + local: 3.1 i/s - 1.25x slower +``` +--- + lib/net/imap/response_reader.rb | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/lib/net/imap/response_reader.rb b/lib/net/imap/response_reader.rb +index d59d672..024d67a 100644 +--- a/lib/net/imap/response_reader.rb ++++ b/lib/net/imap/response_reader.rb +@@ -32,7 +32,10 @@ module Net + def empty?; buff.empty? end + def done?; line_done? && !get_literal_size end + def line_done?; buff.end_with?(CRLF) end +- def get_literal_size; buff.rindex(/\{(\d+)\}\r\n\z/n) && $1.to_i end ++ ++ def get_literal_size ++ buff.end_with?("}\r\n") && buff.rindex(/\{(\d+)\}\r\n\z/n) && $1.to_i ++ end + + def read_line + buff << (@sock.gets(CRLF, read_limit) or throw :eof) + +From 2e9d425aae61801deb18384fbd295cee6a9850a3 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Tue, 14 Apr 2026 14:40:48 -0400 +Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=8D=92=20pick=206f82e28f7:=20?= + =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Faster=20ResponseReader:=20parse=20literal?= + =?UTF-8?q?=20from=20line?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Unfortunately, neither `#rindex` nor even `#end_with?` appear to be +truly `O(1)` for very large strings (100K+). I'm guessing that this is +due to memory locality and caching issues. But, by parsing the literal +from the latest `line` (rather than the full buffer), we mostly avoid +that problem. + +Also, by explicitly parsing literal_size immediately after reading the +line, we don't need to parse it again in `#done?`. + +``` +$ benchmark-driver benchmarks/response_reader.yml --filter KiB +Warming up -------------------------------------- + 1KiB with no literals 202.846k i/s - 214.181k times in 1.055878s (4.93μs/i) + 10KiB with no literals 55.699k i/s - 57.354k times in 1.029717s (17.95μs/i) +100KiB with no literals 6.622k i/s - 6.688k times in 1.009943s (151.01μs/i) + 1KiB of 25B literals 3.428k i/s - 3.751k times in 1.094065s (291.67μs/i) + 10KiB of 25B literals 342.733 i/s - 350.000 times in 1.021202s (2.92ms/i) +100KiB of 25B literals 34.343 i/s - 36.000 times in 1.048234s (29.12ms/i) + 1KiB of 0B literals 683.800 i/s - 690.000 times in 1.009066s (1.46ms/i) + 10KiB of 0B literals 69.186 i/s - 70.000 times in 1.011759s (14.45ms/i) +100KiB of 0B literals 6.914 i/s - 7.000 times in 1.012449s (144.64ms/i) +Calculating ------------------------------------- + local YJIT + 1KiB with no literals 193.622k 250.330k i/s - 608.539k times in 3.142929s 2.430944s + 10KiB with no literals 55.944k 58.881k i/s - 167.096k times in 2.986849s 2.837843s +100KiB with no literals 6.550k 6.480k i/s - 19.866k times in 3.033041s 3.065821s + 1KiB of 25B literals 3.445k 5.520k i/s - 10.285k times in 2.985693s 1.863057s + 10KiB of 25B literals 338.578 548.670 i/s - 1.028k times in 3.036224s 1.873620s +100KiB of 25B literals 33.829 55.860 i/s - 103.000 times in 3.044728s 1.843900s + 1KiB of 0B literals 626.970 1.103k i/s - 2.051k times in 3.271287s 1.860275s + 10KiB of 0B literals 66.065 108.347 i/s - 207.000 times in 3.133301s 1.910523s +100KiB of 0B literals 6.720 8.159 i/s - 20.000 times in 2.976273s 2.451265s + +Comparison: + 1KiB with no literals + YJIT: 250330.3 i/s + local: 193621.6 i/s - 1.29x slower + + 10KiB with no literals + YJIT: 58881.3 i/s + local: 55943.9 i/s - 1.05x slower + + 100KiB with no literals + local: 6549.9 i/s + YJIT: 6479.8 i/s - 1.01x slower + + 1KiB of 25B literals + YJIT: 5520.5 i/s + local: 3444.8 i/s - 1.60x slower + + 10KiB of 25B literals + YJIT: 548.7 i/s + local: 338.6 i/s - 1.62x slower + + 100KiB of 25B literals + YJIT: 55.9 i/s + local: 33.8 i/s - 1.65x slower + + 1KiB of 0B literals + YJIT: 1102.5 i/s + local: 627.0 i/s - 1.76x slower + + 10KiB of 0B literals + YJIT: 108.3 i/s + local: 66.1 i/s - 1.64x slower + + 100KiB of 0B literals + YJIT: 8.2 i/s + local: 6.7 i/s - 1.21x slower +``` +--- + lib/net/imap/response_reader.rb | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +diff --git a/lib/net/imap/response_reader.rb b/lib/net/imap/response_reader.rb +index 024d67a..56958eb 100644 +--- a/lib/net/imap/response_reader.rb ++++ b/lib/net/imap/response_reader.rb +@@ -8,6 +8,7 @@ module Net + + def initialize(client, sock) + @client, @sock = client, sock ++ @buff = @literal_size = nil + end + + def read_response_buffer +@@ -15,13 +16,13 @@ module Net + catch :eof do + while true + read_line +- break unless (@literal_size = get_literal_size) ++ break unless literal_size + read_literal + end + end + buff + ensure +- @buff = nil ++ @buff = @literal_size = nil + end + + private +@@ -30,16 +31,18 @@ module Net + + def bytes_read; buff.bytesize end + def empty?; buff.empty? end +- def done?; line_done? && !get_literal_size end ++ def done?; line_done? && !literal_size end + def line_done?; buff.end_with?(CRLF) end + +- def get_literal_size ++ def get_literal_size(buff) + buff.end_with?("}\r\n") && buff.rindex(/\{(\d+)\}\r\n\z/n) && $1.to_i + end + + def read_line +- buff << (@sock.gets(CRLF, read_limit) or throw :eof) ++ line = (@sock.gets(CRLF, read_limit) or throw :eof) ++ buff << line + max_response_remaining! unless line_done? ++ @literal_size = get_literal_size(line) + end + + def read_literal diff --git a/SOURCES/rubygem-net-imap-0.4.24-Information-disclosure-via-MITM-attack-bypassing-TLS-2026-42246.patch b/SOURCES/rubygem-net-imap-0.4.24-Information-disclosure-via-MITM-attack-bypassing-TLS-2026-42246.patch new file mode 100644 index 0000000..59ca527 --- /dev/null +++ b/SOURCES/rubygem-net-imap-0.4.24-Information-disclosure-via-MITM-attack-bypassing-TLS-2026-42246.patch @@ -0,0 +1,116 @@ +From 4244a308dc09682079ef461b68b08ad4ad9e5a57 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Fri, 27 Mar 2026 17:31:11 -0400 +Subject: [PATCH 1/2] =?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 84bc094..a8ac7d8 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -1294,9 +1294,11 @@ module Net + # + def starttls(**options) + @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options) ++ handled = false + error = nil + ok = send_command("STARTTLS") do |resp| + if resp.kind_of?(TaggedResponse) && resp.name == "OK" ++ handled = true + clear_cached_capabilities + clear_responses + start_tls_session +@@ -1308,6 +1310,13 @@ module Net + 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 00192b9a410251c5ed76c96be564d739cba67008 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Fri, 27 Mar 2026 18:00:09 -0400 +Subject: [PATCH 2/2] =?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 a8ac7d8..b9072b1 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -3001,6 +3001,7 @@ module Net + put_string(" ") + send_data(i, tag) + end ++ guard_against_tagged_response_skipping_handler!(tag) + put_string(CRLF) + if cmd == "LOGOUT" + @logout_command_tag = tag +@@ -3016,6 +3017,17 @@ module Net + 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/SPECS/ruby.spec b/SPECS/ruby.spec index d17dd77..8af28a9 100644 --- a/SPECS/ruby.spec +++ b/SPECS/ruby.spec @@ -169,7 +169,7 @@ Summary: An interpreter of object-oriented scripting language Name: ruby Version: %{ruby_version}%{?development_release} -Release: 6%{?dist} +Release: 7%{?dist} # Licenses, which are likely not included in binary RPMs: # Apache-2.0: # benchmark/gc/redblack.rb @@ -287,6 +287,24 @@ Patch13: ruby-3.4.2-openssl-Fix-SHA-1-PSS-tests.patch # https://github.com/ruby/ruby/commit/a53f3d57d1c70f35534c457de2c471d84a55956a # https://github.com/ruby/ruby/commit/2f223e90edf40f5d537760cb26c77b608bddff36 Patch14: rubygem-erb-4.0.3.1-Fix-arbitrary-code-execution-via-deserialization-bypass-CVE-2026-41316.patch +# Test commits from PR were dropped. The gem does not have tests in the ruby tar. +# Backported from +# https://github.com/ruby/net-imap/commit/f46fe38 +# https://github.com/ruby/net-imap/commit/0560d26 +# https://github.com/ruby/net-imap/commit/bfdae21 +Patch15: rubygem-net-imap-0.4.24-DoS-via-crafted-IMAP-responses-CVE-2026-42245.patch +# Test commits from PR were dropped. The gem does not have tests in the ruby tar. +# Backported from +# https://github.com/ruby/net-imap/commit/705aa59 +# https://github.com/ruby/net-imap/commit/038ae35 +Patch16: rubygem-net-imap-0.4.24-Information-disclosure-via-MITM-attack-bypassing-TLS-2026-42246.patch +# Tests not included, the gem does not have tests in the ruby tar. +# Original source PR#663 but it bunches more fixes together, so we backport less. +# https://github.com/ruby/net-imap/pull/663 +# Backported from the following, the first commit below is requirement for the actual fix: +# https://github.com/ruby/net-imap/commit/1eb27278a601be1135910dae6ab6e517559a2e4a +# https://github.com/ruby/net-imap/commit/bbd9eb7ecca506fa43b656368f7aebef8ac09182 +Patch17: rubygem-net-imap-0.4.24-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch Requires: %{name}-libs%{?_isa} = %{version}-%{release} %{?with_rubypick:Suggests: rubypick} @@ -765,6 +783,12 @@ analysis result in RBS format, a standard type description format for Ruby %patch -P 13 -p1 %patch -P 14 -p1 +pushd .bundle/gems/net-imap-%{net_imap_version} +%patch -P 15 -p1 +%patch -P 16 -p1 +%patch -P 17 -p1 +popd + # Provide an example of usage of the tapset: cp -a %{SOURCE3} . @@ -1762,17 +1786,26 @@ make -C %{_vpath_builddir} runruby TESTRUN_SCRIPT=" \ %changelog +* Thu Jun 11 2026 Jarek Prokop - 3.3.10-7 +- Fix DoS via crafted IMAP responses in net-imap. (CVE-2026-42245) + Resolves: RHEL-181693 +- Fix information disclosure via MITM attack bypassing TLS in net-imap. + (CVE-2026-42246) + Resolves: RHEL-181779 +- Fix command injection via Symbol arguments in net-imap. (CVE-2026-42258) + Resolves: RHEL-181811 + * Tue Apr 28 2026 Jarek Prokop - 3.3.10-6 - Fix arbitrary code execution via deserialization bypass in ERB. (CVE-2026-41316) - Resolves: RHEL-171255 + Resolves: RHEL-171257 * Wed Nov 05 2025 Jun Aruga - 3.3.10-5 - Upgrade to Ruby 3.3.10. - Resolves: RHEL-127912 + Resolves: RHEL-106821 - Fix possible denial of service in resolv gem (CVE-2025-24294) - Fix URI Credential Leakage Bypass previous fixes. (CVE-2025-61594) - Fix REXML denial of service. (CVE-2025-58767) - Resolves: RHEL-122015 + Resolves: RHEL-126728 * Fri Apr 11 2025 Jarek Prokop - 3.3.8-4 - Upgrade to Ruby 3.3.8.