From 7b51f8d7788b8766bb541fea1231e1096f5ff038 Mon Sep 17 00:00:00 2001 From: AlmaLinux RelEng Bot Date: Wed, 5 Aug 2026 08:10:07 -0400 Subject: [PATCH] import UBI ruby-3.3.12-14.el10_2 --- .gitignore | 2 +- ...0-Enable-configuration-of-archlibdir.patch | 2 +- ...ed-paths-when-empty-version-string-i.patch | 2 +- ruby-2.1.0-always-use-i386.patch | 2 +- ruby-2.1.0-custom-rubygems-location.patch | 4 +- ruby-2.3.0-ruby_version.patch | 6 +- ...3.0-Disable-syntax-suggest-test-case.patch | 6 +- ...nd-from-exits-YJIT-stat-for-proc-tes.patch | 32 ++ ...GS-to-a-special-hardenflags-variable.patch | 4 +- ruby.spec | 58 +-- ...eserialization-bypass-CVE-2026-41316.patch | 90 ----- ...-via-Symbol-Arguments-CVE-2026-42258.patch | 209 ----------- ...rafted-IMAP-responses-CVE-2026-42245.patch | 340 ------------------ ...MITM-attack-bypassing-TLS-2026-42246.patch | 116 ------ sources | 2 +- 15 files changed, 65 insertions(+), 810 deletions(-) create mode 100644 ruby-3.3.12-Allow-up-to-1-send-from-exits-YJIT-stat-for-proc-tes.patch delete mode 100644 rubygem-erb-4.0.3.1-Fix-arbitrary-code-execution-via-deserialization-bypass-CVE-2026-41316.patch delete mode 100644 rubygem-net-imap-0.4.24-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch delete mode 100644 rubygem-net-imap-0.4.24-DoS-via-crafted-IMAP-responses-CVE-2026-42245.patch delete mode 100644 rubygem-net-imap-0.4.24-Information-disclosure-via-MITM-attack-bypassing-TLS-2026-42246.patch diff --git a/.gitignore b/.gitignore index 62f9440..378a457 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -ruby-3.3.10.tar.xz +ruby-3.3.12.tar.xz diff --git a/ruby-2.1.0-Enable-configuration-of-archlibdir.patch b/ruby-2.1.0-Enable-configuration-of-archlibdir.patch index 5461f1d..7b70917 100644 --- a/ruby-2.1.0-Enable-configuration-of-archlibdir.patch +++ b/ruby-2.1.0-Enable-configuration-of-archlibdir.patch @@ -11,7 +11,7 @@ diff --git a/configure.ac b/configure.ac index d261ea57b5..3c13076b82 100644 --- a/configure.ac +++ b/configure.ac -@@ -3480,6 +3480,11 @@ AS_IF([test ${multiarch+set}], [ +@@ -3477,6 +3477,11 @@ AS_IF([test ${multiarch+set}], [ ]) archlibdir='${libdir}/${arch}' diff --git a/ruby-2.1.0-Prevent-duplicated-paths-when-empty-version-string-i.patch b/ruby-2.1.0-Prevent-duplicated-paths-when-empty-version-string-i.patch index eeea8a0..e96ce39 100644 --- a/ruby-2.1.0-Prevent-duplicated-paths-when-empty-version-string-i.patch +++ b/ruby-2.1.0-Prevent-duplicated-paths-when-empty-version-string-i.patch @@ -14,7 +14,7 @@ diff --git a/configure.ac b/configure.ac index c42436c23d..d261ea57b5 100644 --- a/configure.ac +++ b/configure.ac -@@ -4319,7 +4319,8 @@ AS_CASE(["$ruby_version_dir_name"], +@@ -4317,7 +4317,8 @@ AS_CASE(["$ruby_version_dir_name"], ruby_version_dir=/'${ruby_version_dir_name}' if test -z "${ruby_version_dir_name}"; then diff --git a/ruby-2.1.0-always-use-i386.patch b/ruby-2.1.0-always-use-i386.patch index 9d5fff4..c990ede 100644 --- a/ruby-2.1.0-always-use-i386.patch +++ b/ruby-2.1.0-always-use-i386.patch @@ -11,7 +11,7 @@ diff --git a/configure.ac b/configure.ac index 3c13076b82..93af30321d 100644 --- a/configure.ac +++ b/configure.ac -@@ -4383,6 +4383,8 @@ AC_SUBST(vendorarchdir)dnl +@@ -4381,6 +4381,8 @@ AC_SUBST(vendorarchdir)dnl AC_SUBST(CONFIGURE, "`echo $0 | sed 's|.*/||'`")dnl AC_SUBST(configure_args, "`echo "${ac_configure_args}" | sed 's/\\$/$$/g'`")dnl diff --git a/ruby-2.1.0-custom-rubygems-location.patch b/ruby-2.1.0-custom-rubygems-location.patch index 30292e9..dd81419 100644 --- a/ruby-2.1.0-custom-rubygems-location.patch +++ b/ruby-2.1.0-custom-rubygems-location.patch @@ -15,7 +15,7 @@ diff --git a/configure.ac b/configure.ac index 93af30321d..bc13397e0e 100644 --- a/configure.ac +++ b/configure.ac -@@ -4355,6 +4355,10 @@ AC_ARG_WITH(vendorarchdir, +@@ -4353,6 +4353,10 @@ AC_ARG_WITH(vendorarchdir, [vendorarchdir=$withval], [vendorarchdir=${multiarch+'${rubysitearchprefix}/vendor_ruby'${ruby_version_dir}}${multiarch-'${vendorlibdir}/${sitearch}'}]) @@ -26,7 +26,7 @@ index 93af30321d..bc13397e0e 100644 AS_IF([test "${LOAD_RELATIVE+set}"], [ AC_DEFINE_UNQUOTED(LOAD_RELATIVE, $LOAD_RELATIVE) RUBY_EXEC_PREFIX='' -@@ -4379,6 +4383,7 @@ AC_SUBST(sitearchdir)dnl +@@ -4377,6 +4381,7 @@ AC_SUBST(sitearchdir)dnl AC_SUBST(vendordir)dnl AC_SUBST(vendorlibdir)dnl AC_SUBST(vendorarchdir)dnl diff --git a/ruby-2.3.0-ruby_version.patch b/ruby-2.3.0-ruby_version.patch index af424a8..4bb47d9 100644 --- a/ruby-2.3.0-ruby_version.patch +++ b/ruby-2.3.0-ruby_version.patch @@ -20,7 +20,7 @@ diff --git a/configure.ac b/configure.ac index 80b137e380..63cd3b4f8b 100644 --- a/configure.ac +++ b/configure.ac -@@ -4269,9 +4269,6 @@ AS_CASE(["$target_os"], +@@ -4267,9 +4267,6 @@ AS_CASE(["$target_os"], rubyw_install_name='$(RUBYW_INSTALL_NAME)' ]) @@ -30,7 +30,7 @@ index 80b137e380..63cd3b4f8b 100644 rubyarchprefix=${multiarch+'${archlibdir}/${RUBY_BASE_NAME}'}${multiarch-'${rubylibprefix}/${arch}'} AC_ARG_WITH(rubyarchprefix, AS_HELP_STRING([--with-rubyarchprefix=DIR], -@@ -4294,57 +4291,63 @@ AC_ARG_WITH(ridir, +@@ -4292,57 +4289,63 @@ AC_ARG_WITH(ridir, AC_SUBST(ridir) AC_SUBST(RI_BASE_NAME) @@ -122,7 +122,7 @@ index 80b137e380..63cd3b4f8b 100644 AS_IF([test "${LOAD_RELATIVE+set}"], [ AC_DEFINE_UNQUOTED(LOAD_RELATIVE, $LOAD_RELATIVE) -@@ -4361,6 +4364,7 @@ AC_SUBST(sitearchincludedir)dnl +@@ -4359,6 +4362,7 @@ AC_SUBST(sitearchincludedir)dnl AC_SUBST(arch)dnl AC_SUBST(sitearch)dnl AC_SUBST(ruby_version)dnl diff --git a/ruby-3.3.0-Disable-syntax-suggest-test-case.patch b/ruby-3.3.0-Disable-syntax-suggest-test-case.patch index cbfe623..1d8f81b 100644 --- a/ruby-3.3.0-Disable-syntax-suggest-test-case.patch +++ b/ruby-3.3.0-Disable-syntax-suggest-test-case.patch @@ -12,9 +12,9 @@ diff --git a/common.mk b/common.mk index d55d1788aa..73755f6ccd 100644 --- a/common.mk +++ b/common.mk -@@ -1607,8 +1607,6 @@ yes-test-syntax-suggest: $(PREPARE_SYNTAX_SUGGEST) - $(ACTIONS_ENDGROUP) - no-test-syntax-suggest: +@@ -1585,8 +1585,6 @@ test-bundled-gems-run: $(PREPARE_BUNDLED_GEMS) + + test-syntax-suggest: -check: $(DOT_WAIT) $(PREPARE_SYNTAX_SUGGEST) test-syntax-suggest - diff --git a/ruby-3.3.12-Allow-up-to-1-send-from-exits-YJIT-stat-for-proc-tes.patch b/ruby-3.3.12-Allow-up-to-1-send-from-exits-YJIT-stat-for-proc-tes.patch new file mode 100644 index 0000000..eed3485 --- /dev/null +++ b/ruby-3.3.12-Allow-up-to-1-send-from-exits-YJIT-stat-for-proc-tes.patch @@ -0,0 +1,32 @@ +From feee246753aa097e836a79ea8437f4b8c73df7e8 Mon Sep 17 00:00:00 2001 +From: Jarek Prokop +Date: Tue, 21 Jul 2026 10:32:29 +0200 +Subject: [PATCH] Allow up to 1 send from exits YJIT stat for proc test. + +Add an allowed range of 0..1 for exits send stat. + +In some case, the stats contain extra stat under 'exits' for 'send'. + +In any other case, the stat is 0, and test passes. +From RPM build of Ruby for Fedora distro family, the exits' stats emit 1 extra send. + +The commit that introduced fix and the offending test +0cba8cf414dfd8ab30edc3081645405c165a81d5 for [Bug #21266] +prevents a crash, which seems prevented here as well. +--- + test/ruby/test_yjit.rb | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb +index 0d40d38872..df1b41aa16 100644 +--- a/test/ruby/test_yjit.rb ++++ b/test/ruby/test_yjit.rb +@@ -1528,7 +1528,7 @@ def test_disable_stats + + def test_proc_block_with_kwrest + # When the bug was present this required --yjit-stats to trigger. +- assert_compiles(<<~RUBY, result: {extra: 5}) ++ assert_compiles(<<~RUBY, result: {extra: 5}, exits: {send: 0..1}) + def foo = bar(w: 1, x: 2, y: 3, z: 4, extra: 5, &proc { _1 }) + def bar(w:, x:, y:, z:, **kwrest) = yield kwrest + diff --git a/ruby-3.4.0-Extract-hardening-CFLAGS-to-a-special-hardenflags-variable.patch b/ruby-3.4.0-Extract-hardening-CFLAGS-to-a-special-hardenflags-variable.patch index f2bce8b..e2ec141 100644 --- a/ruby-3.4.0-Extract-hardening-CFLAGS-to-a-special-hardenflags-variable.patch +++ b/ruby-3.4.0-Extract-hardening-CFLAGS-to-a-special-hardenflags-variable.patch @@ -171,7 +171,7 @@ index f35fad6a362611..0da15772d36671 100644 AC_CACHE_CHECK([whether compiler has statement and declarations in expressions], rb_cv_have_stmt_and_decl_in_expr, [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]],[[ __extension__ ({ int a = 0; a; }); ]])], -@@ -4213,12 +4270,13 @@ AS_IF([test "${ARCH_FLAG}"], [ +@@ -4211,12 +4268,13 @@ AS_IF([test "${ARCH_FLAG}"], [ rb_cv_warnflags=`echo "$rb_cv_warnflags" | sed 's/^ *//;s/ *$//'` warnflags="$rb_cv_warnflags" AC_SUBST(cppflags)dnl @@ -186,7 +186,7 @@ index f35fad6a362611..0da15772d36671 100644 AC_SUBST(XCFLAGS)dnl AC_SUBST(XLDFLAGS)dnl AC_SUBST(EXTLDFLAGS)dnl -@@ -4686,6 +4744,7 @@ config_summary "DLDFLAGS" "$DLDFLAGS" +@@ -4684,6 +4742,7 @@ config_summary "DLDFLAGS" "$DLDFLAGS" config_summary "optflags" "$optflags" config_summary "debugflags" "$debugflags" config_summary "warnflags" "$warnflags" diff --git a/ruby.spec b/ruby.spec index 55c33e8..65e7987 100644 --- a/ruby.spec +++ b/ruby.spec @@ -1,6 +1,6 @@ %global major_version 3 %global minor_version 3 -%global teeny_version 10 +%global teeny_version 12 %global major_minor_version %{major_version}.%{minor_version} %global ruby_version %{major_minor_version}.%{teeny_version} @@ -36,7 +36,7 @@ %global rubygems_securerandom_version 0.3.1 %global rubygems_timeout_version 0.4.1 %global rubygems_tsort_version 0.2.0 -%global rubygems_uri_version 0.13.1 +%global rubygems_uri_version 0.13.3 # Default gems. %global bundler_version 2.5.22 @@ -47,7 +47,7 @@ %global bundler_securerandom_version 0.3.1 %global bundler_thor_version 1.3.0 %global bundler_tsort_version 0.2.0 -%global bundler_uri_version 0.13.1 +%global bundler_uri_version 0.13.3 %global abbrev_version 0.1.2 %global base64_version 0.2.0 @@ -79,14 +79,14 @@ %global nkf_version 0.1.3 %global observer_version 0.1.2 %global open3_version 0.2.1 -%global openssl_version 3.2.2 +%global openssl_version 3.2.4 %global open_uri_version 0.4.1 %global optparse_version 0.4.0 %global ostruct_version 0.6.0 %global pathname_version 0.3.0 %global pp_version 0.5.0 %global prettyprint_version 0.2.0 -%global pstore_version 0.1.3 +%global pstore_version 0.2.1 %global readline_version 0.0.4 %global reline_version 0.5.10 %global resolv_version 0.3.1 @@ -99,7 +99,7 @@ %global singleton_version 0.2.0 %global stringio_version 3.1.1 %global strscan_version 3.0.9 -%global syntax_suggest_version 2.0.1 +%global syntax_suggest_version 2.0.3 %global syslog_version 0.1.2 %global tempfile_version 0.2.1 %global time_version 0.3.0 @@ -112,7 +112,7 @@ %global win32ole_version 1.8.10 %global yaml_version 0.3.0 %global prism_version 0.19.0 -%global zlib_version 3.1.1 +%global zlib_version 3.1.2 # Gemified default gems. %global bigdecimal_version 3.1.5 @@ -125,7 +125,7 @@ # Bundled gems. %global debug_version 1.9.2 %global net_ftp_version 0.3.4 -%global net_imap_version 0.4.21 +%global net_imap_version 0.4.25 %global net_pop_version 0.1.2 %global net_smtp_version 0.5.1 %global matrix_version 0.4.2 @@ -173,7 +173,7 @@ Summary: An interpreter of object-oriented scripting language Name: ruby Version: %{ruby_version}%{?development_release} -Release: 13%{?dist} +Release: 14%{?dist} # Licenses, which are likely not included in binary RPMs: # Apache-2.0: # benchmark/gc/redblack.rb @@ -293,30 +293,10 @@ Patch15: ruby-3.4.0-openssl-fix-test-provider-in-fips.patch # Fix the tests using SHA-1 Probabilistic Signature Scheme (PSS) parameters. # https://github.com/ruby/openssl/pull/879 Patch16: ruby-3.4.2-openssl-Fix-SHA-1-PSS-tests.patch -# Fix arbitrary code execution via deserialization bypass in ERB. (CVE-2026-41316) -# Include the version bump here as that's the only change from 4.0.3 to 4.0.3.1 -# and is expected to be included in next Ruby 3.3. -# https://github.com/ruby/ruby/commit/a53f3d57d1c70f35534c457de2c471d84a55956a -# https://github.com/ruby/ruby/commit/2f223e90edf40f5d537760cb26c77b608bddff36 -Patch17: 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 -Patch18: 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 -Patch19: 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 -Patch20: rubygem-net-imap-0.4.24-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch +# Workaround for a weird failure that happens only during mockbuild of Ruby src.rpm +# Cannot reproduce sufficiently so far for the issue to appear outside that environment. +# See patch's commit message for more information. +Patch17: ruby-3.3.12-Allow-up-to-1-send-from-exits-YJIT-stat-for-proc-tes.patch Requires: %{name}-libs%{?_isa} = %{version}-%{release} %{?with_rubypick:Suggests: rubypick} @@ -802,12 +782,6 @@ analysis result in RBS format, a standard type description format for Ruby %patch 16 -p1 %patch 17 -p1 -pushd .bundle/gems/net-imap-%{net_imap_version} -%patch 18 -p1 -%patch 19 -p1 -%patch 20 -p1 -popd - # Provide an example of usage of the tapset: cp -a %{SOURCE3} . @@ -1580,7 +1554,6 @@ make -C %{_vpath_builddir} runruby TESTRUN_SCRIPT=" \ %files doc -f .ruby-doc.en -f .ruby-doc.ja %doc README.md -%doc ChangeLog %{?with_systemtap:%doc ruby-exercise.stp} %{_datadir}/ri @@ -1812,6 +1785,11 @@ make -C %{_vpath_builddir} runruby TESTRUN_SCRIPT=" \ %changelog +* Thu Jul 23 2026 Jarek Prokop - 3.3.12-14 +- Upgrade to Ruby 3.3.12. + Resolves: RHEL-211300 + Resolves: RHEL-193659 + * Thu Jun 11 2026 Jarek Prokop - 3.3.10-13 - Fix DoS via crafted IMAP responses in net-imap. (CVE-2026-42245) Resolves: RHEL-181687 diff --git a/rubygem-erb-4.0.3.1-Fix-arbitrary-code-execution-via-deserialization-bypass-CVE-2026-41316.patch b/rubygem-erb-4.0.3.1-Fix-arbitrary-code-execution-via-deserialization-bypass-CVE-2026-41316.patch deleted file mode 100644 index 0e0fcd9..0000000 --- a/rubygem-erb-4.0.3.1-Fix-arbitrary-code-execution-via-deserialization-bypass-CVE-2026-41316.patch +++ /dev/null @@ -1,90 +0,0 @@ -From a53f3d57d1c70f35534c457de2c471d84a55956a Mon Sep 17 00:00:00 2001 -From: Takashi Kokubun -Date: Tue, 21 Apr 2026 16:27:44 +0900 -Subject: [PATCH 1/2] [ruby/erb] Prohibit def_method on marshal-loaded ERB - instances - -Extends the @_init guard to def_method so that an ERB object created -via Marshal.load (which bypasses initialize) raises ArgumentError -instead of evaluating arbitrary source. def_module and def_class both -delegate to def_method and are covered by the same check. - -Co-authored-by: Tristan Madani ---- - lib/erb.rb | 3 +++ - test/erb/test_erb.rb | 27 +++++++++++++++++++++++++++ - 2 files changed, 30 insertions(+) - -diff --git a/lib/erb.rb b/lib/erb.rb -index bc1615d7da..a7317c0856 100644 ---- a/lib/erb.rb -+++ b/lib/erb.rb -@@ -463,6 +463,9 @@ def new_toplevel(vars = nil) - # erb.def_method(MyClass, 'render(arg1, arg2)', filename) - # print MyClass.new.render('foo', 123) - def def_method(mod, methodname, fname='(ERB)') -+ unless @_init.equal?(self.class.singleton_class) -+ raise ArgumentError, "not initialized" -+ end - src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n" - mod.module_eval do - eval(src, binding, fname, -1) -diff --git a/test/erb/test_erb.rb b/test/erb/test_erb.rb -index 555345a140..1266b64e41 100644 ---- a/test/erb/test_erb.rb -+++ b/test/erb/test_erb.rb -@@ -714,6 +714,33 @@ def test_prohibited_marshal_load - assert_raise(ArgumentError) {erb.result} - end - -+ def test_prohibited_marshal_load_def_method -+ erb = ERB.allocate -+ erb.instance_variable_set(:@src, "") -+ erb.instance_variable_set(:@lineno, 1) -+ erb.instance_variable_set(:@_init, true) -+ erb = Marshal.load(Marshal.dump(erb)) -+ assert_raise(ArgumentError) {erb.def_method(Class.new, 'render')} -+ end -+ -+ def test_prohibited_marshal_load_def_module -+ erb = ERB.allocate -+ erb.instance_variable_set(:@src, "") -+ erb.instance_variable_set(:@lineno, 1) -+ erb.instance_variable_set(:@_init, true) -+ erb = Marshal.load(Marshal.dump(erb)) -+ assert_raise(ArgumentError) {erb.def_module} -+ end -+ -+ def test_prohibited_marshal_load_def_class -+ erb = ERB.allocate -+ erb.instance_variable_set(:@src, "") -+ erb.instance_variable_set(:@lineno, 1) -+ erb.instance_variable_set(:@_init, true) -+ erb = Marshal.load(Marshal.dump(erb)) -+ assert_raise(ArgumentError) {erb.def_class} -+ end -+ - def test_multi_line_comment_lineno - erb = ERB.new(<<~EOS) - <%= __LINE__ %> - -From 2f223e90edf40f5d537760cb26c77b608bddff36 Mon Sep 17 00:00:00 2001 -From: Takashi Kokubun -Date: Tue, 21 Apr 2026 17:14:06 +0900 -Subject: [PATCH 2/2] [ruby/erb] Version 4.0.3.1 - ---- - lib/erb/version.rb | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/lib/erb/version.rb b/lib/erb/version.rb -index 295fc5fa6f..85e2a79def 100644 ---- a/lib/erb/version.rb -+++ b/lib/erb/version.rb -@@ -1,5 +1,5 @@ - # frozen_string_literal: true - class ERB -- VERSION = '4.0.3' -+ VERSION = '4.0.3.1' - private_constant :VERSION - end diff --git a/rubygem-net-imap-0.4.24-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch b/rubygem-net-imap-0.4.24-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch deleted file mode 100644 index 2c1d493..0000000 --- a/rubygem-net-imap-0.4.24-Command-Injection-via-Symbol-Arguments-CVE-2026-42258.patch +++ /dev/null @@ -1,209 +0,0 @@ -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/rubygem-net-imap-0.4.24-DoS-via-crafted-IMAP-responses-CVE-2026-42245.patch b/rubygem-net-imap-0.4.24-DoS-via-crafted-IMAP-responses-CVE-2026-42245.patch deleted file mode 100644 index 047db54..0000000 --- a/rubygem-net-imap-0.4.24-DoS-via-crafted-IMAP-responses-CVE-2026-42245.patch +++ /dev/null @@ -1,340 +0,0 @@ -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/rubygem-net-imap-0.4.24-Information-disclosure-via-MITM-attack-bypassing-TLS-2026-42246.patch b/rubygem-net-imap-0.4.24-Information-disclosure-via-MITM-attack-bypassing-TLS-2026-42246.patch deleted file mode 100644 index 59ca527..0000000 --- a/rubygem-net-imap-0.4.24-Information-disclosure-via-MITM-attack-bypassing-TLS-2026-42246.patch +++ /dev/null @@ -1,116 +0,0 @@ -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/sources b/sources index 816497d..6fd3016 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (ruby-3.3.10.tar.xz) = 8b81cab7b98acb6ff7bdf864da5e97596ee1efa441e2a65991e12a7e3f3ad3d83b1b5c65ae108484252ec8f6d85db60eb381a174c759023beb202b5a0d20818a +SHA512 (ruby-3.3.12.tar.xz) = 1755901f5d1e66250e4f9f4e3a0bf31ec25055f1cb86fafc68281f6d108e9cf5329a6c74c221b1a31aa7de78e9c2d8aa7b76faf39acfce44c5ed94cf7e766f3f