diff --git a/CVE-2022-44566.patch b/CVE-2022-44566.patch new file mode 100644 index 0000000..39c77ee --- /dev/null +++ b/CVE-2022-44566.patch @@ -0,0 +1,133 @@ +From 82bcdc011e2ff674e7dd8fd8cee3a831c908d29b Mon Sep 17 00:00:00 2001 +From: Zack Deveau +Date: Mon, 21 Nov 2022 17:11:31 -0500 +Subject: [PATCH] Added integer width check to PostgreSQL::Quoting + +Given a value outside the range for a 64bit signed integer type +PostgreSQL will treat the column type as numeric. +Comparing integer values against numeric values can result +in a slow sequential scan. + +This behavior is configurable via +ActiveRecord.raise_int_wider_than_64bit which defaults to true. + +[CVE-2022-44566] +--- + activerecord-7.0.4/lib/active_record.rb | 8 ++++++ + .../connection_adapters/postgresql/quoting.rb | 26 +++++++++++++++++ + .../cases/adapters/postgresql/quoting_test.rb | 28 +++++++++++++++++++ + 3 files changed, 85 insertions(+) + +diff --git a/activerecord-7.0.4/lib/active_record.rb b/activerecord-7.0.4/lib/active_record.rb +index d553fe5c7c..4f6e5493e7 100644 +--- a/activerecord-7.0.4/lib/active_record.rb ++++ b/activerecord-7.0.4/lib/active_record.rb +@@ -347,6 +347,14 @@ def self.global_executor_concurrency # :nodoc: + singleton_class.attr_accessor :use_yaml_unsafe_load + self.use_yaml_unsafe_load = false + ++ ## ++ # :singleton-method: ++ # Application configurable boolean that denotes whether or not to raise ++ # an exception when the PostgreSQLAdapter is provided with an integer that ++ # is wider than signed 64bit representation ++ singleton_class.attr_accessor :raise_int_wider_than_64bit ++ self.raise_int_wider_than_64bit = true ++ + ## + # :singleton-method: + # Application configurable array that provides additional permitted classes +diff --git a/activerecord-7.0.4/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord-7.0.4/lib/active_record/connection_adapters/postgresql/quoting.rb +index 0d1cd8b02d..d5591dbe00 100644 +--- a/activerecord-7.0.4/lib/active_record/connection_adapters/postgresql/quoting.rb ++++ b/activerecord-7.0.4/lib/active_record/connection_adapters/postgresql/quoting.rb +@@ -4,6 +4,12 @@ + module ConnectionAdapters + module PostgreSQL + module Quoting ++ class IntegerOutOf64BitRange < StandardError ++ def initialize(msg) ++ super(msg) ++ end ++ end ++ + # Escapes binary strings for bytea input to the database. + def escape_bytea(value) + @connection.escape_bytea(value) if value +@@ -16,7 +22,27 @@ def unescape_bytea(value) + @connection.unescape_bytea(value) if value + end + ++ def check_int_in_range(value) ++ if value.to_int > 9223372036854775807 || value.to_int < -9223372036854775808 ++ exception = <<~ERROR ++ Provided value outside of the range of a signed 64bit integer. ++ ++ PostgreSQL will treat the column type in question as a numeric. ++ This may result in a slow sequential scan due to a comparison ++ being performed between an integer or bigint value and a numeric value. ++ ++ To allow for this potentially unwanted behavior, set ++ ActiveRecord.raise_int_wider_than_64bit to false. ++ ERROR ++ raise IntegerOutOf64BitRange.new exception ++ end ++ end ++ + def quote(value) # :nodoc: ++ if ActiveRecord.raise_int_wider_than_64bit && value.is_a?(Integer) ++ check_int_in_range(value) ++ end ++ + case value + when OID::Xml::Data + "xml '#{quote_string(value.to_s)}'" +diff --git a/test/cases/adapters/postgresql/quoting_test.rb b/test/cases/adapters/postgresql/quoting_test.rb +index d571355a9c..7e01defd96 100644 +--- a/test/cases/adapters/postgresql/quoting_test.rb ++++ b/test/cases/adapters/postgresql/quoting_test.rb +@@ -8,6 +8,7 @@ + class QuotingTest < ActiveRecord::PostgreSQLTestCase + def setup + @conn = ActiveRecord::Base.connection ++ @raise_int_wider_than_64bit = ActiveRecord.raise_int_wider_than_64bit + end + + def test_type_cast_true +@@ -44,6 +45,33 @@ def test_quote_table_name_with_spaces + value = "user posts" + assert_equal "\"user posts\"", @conn.quote_table_name(value) + end ++ ++ def test_raise_when_int_is_wider_than_64bit ++ value = 9223372036854775807 + 1 ++ assert_raise ActiveRecord::ConnectionAdapters::PostgreSQL::Quoting::IntegerOutOf64BitRange do ++ @conn.quote(value) ++ end ++ ++ value = -9223372036854775808 - 1 ++ assert_raise ActiveRecord::ConnectionAdapters::PostgreSQL::Quoting::IntegerOutOf64BitRange do ++ @conn.quote(value) ++ end ++ end ++ ++ def test_do_not_raise_when_int_is_not_wider_than_64bit ++ value = 9223372036854775807 ++ assert_equal "9223372036854775807", @conn.quote(value) ++ ++ value = -9223372036854775808 ++ assert_equal "-9223372036854775808", @conn.quote(value) ++ end ++ ++ def test_do_not_raise_when_raise_int_wider_than_64bit_is_false ++ ActiveRecord.raise_int_wider_than_64bit = false ++ value = 9223372036854775807 + 1 ++ assert_equal "9223372036854775808", @conn.quote(value) ++ ActiveRecord.raise_int_wider_than_64bit = @raise_int_wider_than_64bit ++ end + end + end + end +-- +2.35.1 + diff --git a/CVE-2023-22794.patch b/CVE-2023-22794.patch new file mode 100644 index 0000000..e0fca48 --- /dev/null +++ b/CVE-2023-22794.patch @@ -0,0 +1,171 @@ +From d7aba06953f9fa789c411676b941d20df8ef73de Mon Sep 17 00:00:00 2001 +From: John Hawthorn +Date: Tue, 6 Sep 2022 15:49:26 -0700 +Subject: [PATCH] Make sanitize_as_sql_comment more strict + +Though this method was likely never meant to take user input, it was +attempting sanitization. That sanitization could be bypassed with +carefully crafted input. + +This commit makes the sanitization more robust by replacing any +occurrances of "/*" or "*/" with "/ *" or "* /". It also performs a +first pass to remove one surrounding comment to avoid compatibility +issues for users relying on the existing removal. + +This also clarifies in the documentation of annotate that it should not +be provided user input. + +[CVE-2023-22794] +--- + .../connection_adapters/abstract/quoting.rb | 11 ++++++++++- + activerecord-7.0.4/lib/active_record/query_logs.rb | 13 ++++++++++++- + .../lib/active_record/relation/query_methods.rb | 2 ++ + activerecord-7.0.4/test/cases/annotate_test.rb | 11 ++++++++--- + activerecord-7.0.4/test/cases/query_logs_test.rb | 5 +++-- + activerecord-7.0.4/test/cases/relation_test.rb | 10 +++------- + 6 files changed, 38 insertions(+), 14 deletions(-) + +diff --git a/activerecord-7.0.4/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord-7.0.4/lib/active_record/connection_adapters/abstract/quoting.rb +index dda3145bdd..3b7819eb56 100644 +--- a/activerecord-7.0.4/lib/active_record/connection_adapters/abstract/quoting.rb ++++ b/activerecord-7.0.4/lib/active_record/connection_adapters/abstract/quoting.rb +@@ -146,7 +146,16 @@ def quoted_binary(value) # :nodoc: + end + + def sanitize_as_sql_comment(value) # :nodoc: +- value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "") ++ # Sanitize a string to appear within a SQL comment ++ # For compatibility, this also surrounding "/*+", "/*", and "*/" ++ # charcacters, possibly with single surrounding space. ++ # Then follows that by replacing any internal "*/" or "/ *" with ++ # "* /" or "/ *" ++ comment = value.to_s.dup ++ comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "") ++ comment.gsub!("*/", "* /") ++ comment.gsub!("/*", "/ *") ++ comment + end + + def column_name_matcher # :nodoc: +diff --git a/activerecord-7.0.4/lib/active_record/query_logs.rb b/activerecord-7.0.4/lib/active_record/query_logs.rb +index f116a154dd..2fd6ca3640 100644 +--- a/activerecord-7.0.4/lib/active_record/query_logs.rb ++++ b/activerecord-7.0.4/lib/active_record/query_logs.rb +@@ -33,6 +33,8 @@ + # want to add to the comment. Dynamic content can be created by setting a proc or lambda value in a hash, + # and can reference any value stored in the +context+ object. + # ++ # Escaping is performed on the string returned, however untrusted user input should not be used. ++ # + # Example: + # + # tags = [ +@@ -109,7 +111,16 @@ def uncached_comment + end + + def escape_sql_comment(content) +- content.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "") ++ # Sanitize a string to appear within a SQL comment ++ # For compatibility, this also surrounding "/*+", "/*", and "*/" ++ # charcacters, possibly with single surrounding space. ++ # Then follows that by replacing any internal "*/" or "/ *" with ++ # "* /" or "/ *" ++ comment = content.to_s.dup ++ comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "") ++ comment.gsub!("*/", "* /") ++ comment.gsub!("/*", "/ *") ++ comment + end + + def tag_content +diff --git a/activerecord-7.0.4/lib/active_record/relation/query_methods.rb b/activerecord-7.0.4/lib/active_record/relation/query_methods.rb +index 25136331f9..cf7c524291 100644 +--- a/activerecord-7.0.4/lib/active_record/relation/query_methods.rb ++++ b/activerecord-7.0.4/lib/active_record/relation/query_methods.rb +@@ -1216,6 +1216,8 @@ def skip_preloading! # :nodoc: + # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */ + # + # The SQL block comment delimiters, "/*" and "*/", will be added automatically. ++ # ++ # Some escaping is performed, however untrusted user input should not be used. + def annotate(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.annotate!(*args) +diff --git a/test/cases/annotate_test.rb b/test/cases/annotate_test.rb +index b0802ca559..ed1d846178 100644 +--- a/test/cases/annotate_test.rb ++++ b/test/cases/annotate_test.rb +@@ -18,17 +18,22 @@ def test_annotate_wraps_content_in_an_inline_comment + def test_annotate_is_sanitized + quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts") + +- assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do ++ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \* /foo/ \* \*/}i) do + posts = Post.select(:id).annotate("*/foo/*") + assert posts.first + end + +- assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do ++ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \*\* //foo// \*\* \*/}i) do + posts = Post.select(:id).annotate("**//foo//**") + assert posts.first + end + +- assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/ /\* bar \*/}i) do ++ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \* \* //foo// \* \* \*/}i) do ++ posts = Post.select(:id).annotate("* *//foo//* *") ++ assert posts.first ++ end ++ ++ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \* /foo/ \* \*/ /\* \* /bar \*/}i) do + posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar") + assert posts.first + end +diff --git a/test/cases/query_logs_test.rb b/test/cases/query_logs_test.rb +index 05207f17e3..09ca530417 100644 +--- a/test/cases/query_logs_test.rb ++++ b/test/cases/query_logs_test.rb +@@ -42,8 +42,9 @@ def test_escaping_good_comment + end + + def test_escaping_bad_comments +- assert_equal "; DROP TABLE USERS;", ActiveRecord::QueryLogs.send(:escape_sql_comment, "*/; DROP TABLE USERS;/*") +- assert_equal "; DROP TABLE USERS;", ActiveRecord::QueryLogs.send(:escape_sql_comment, "**//; DROP TABLE USERS;/*") ++ assert_equal "* /; DROP TABLE USERS;/ *", ActiveRecord::QueryLogs.send(:escape_sql_comment, "*/; DROP TABLE USERS;/*") ++ assert_equal "** //; DROP TABLE USERS;/ *", ActiveRecord::QueryLogs.send(:escape_sql_comment, "**//; DROP TABLE USERS;/*") ++ assert_equal "* * //; DROP TABLE USERS;// * *", ActiveRecord::QueryLogs.send(:escape_sql_comment, "* *//; DROP TABLE USERS;//* *") + end + + def test_basic_commenting +diff --git a/test/cases/relation_test.rb b/test/cases/relation_test.rb +index 1da95bd3ae..0aed326678 100644 +--- a/test/cases/relation_test.rb ++++ b/test/cases/relation_test.rb +@@ -345,7 +345,7 @@ def test_relation_with_annotation_chains_sql_comments + + def test_relation_with_annotation_filters_sql_comment_delimiters + post_with_annotation = Post.where(id: 1).annotate("**//foo//**") +- assert_match %r{= 1 /\* foo \*/}, post_with_annotation.to_sql ++ assert_includes post_with_annotation.to_sql, "= 1 /* ** //foo// ** */" + end + + def test_relation_with_annotation_includes_comment_in_count_query +@@ -367,13 +367,9 @@ def test_relation_without_annotation_does_not_include_an_empty_comment + + def test_relation_with_optimizer_hints_filters_sql_comment_delimiters + post_with_hint = Post.where(id: 1).optimizer_hints("**//BADHINT//**") +- assert_match %r{BADHINT}, post_with_hint.to_sql +- assert_no_match %r{\*/BADHINT}, post_with_hint.to_sql +- assert_no_match %r{\*//BADHINT}, post_with_hint.to_sql +- assert_no_match %r{BADHINT/\*}, post_with_hint.to_sql +- assert_no_match %r{BADHINT//\*}, post_with_hint.to_sql ++ assert_includes post_with_hint.to_sql, "/*+ ** //BADHINT// ** */" + post_with_hint = Post.where(id: 1).optimizer_hints("/*+ BADHINT */") +- assert_match %r{/\*\+ BADHINT \*/}, post_with_hint.to_sql ++ assert_includes post_with_hint.to_sql, "/*+ BADHINT */" + end + + def test_does_not_duplicate_optimizer_hints_on_merge +-- +2.35.1 + diff --git a/rubygem-activerecord.spec b/rubygem-activerecord.spec index 907672f..975d154 100644 --- a/rubygem-activerecord.spec +++ b/rubygem-activerecord.spec @@ -2,7 +2,7 @@ Name: rubygem-%{gem_name} Epoch: 1 Version: 7.0.4 -Release: 1 +Release: 2 Summary: Object-relational mapper framework (part of Rails) License: MIT URL: http://rubyonrails.org @@ -24,7 +24,8 @@ Patch0: rubygem-activerecord-7.0.2.3-Fix-assert_called_with-with-em Patch1: rubygem-activerecord-7.0.2.3-Remove-the-multi-call-form-of-assert_called_with.patch # https://github.com/rails/rails/pull/45370 Patch2: rubygem-activerecord-7.0.2.3-Fix-tests-for-minitest-5.16.patch - +Patch3: CVE-2022-44566.patch +Patch4: CVE-2023-22794.patch Suggests: %{_bindir}/sqlite3 BuildRequires: rubygems-devel rubygem(bcrypt) rubygem(activesupport) = %{version} BuildRequires: rubygem(activemodel) = %{version} rubygem(builder) rubygem(sqlite3) @@ -52,6 +53,8 @@ Documentation for %{name}. pushd %{_builddir} %patch1 -p2 %patch2 -p2 +%patch3 -p1 +%patch4 -p1 popd %build gem build ../%{gem_name}-%{version}.gemspec @@ -104,6 +107,9 @@ popd %{gem_instdir}/examples %changelog +* Wed Feb 22 2023 wushaozheng - 1:7.0.4-2 +- fix CVE-2022-44566 CVE-2023-22794 + * Thu Jan 19 2023 yanxiaobing - 1:7.0.4-1 - Upgrade to version 7.0.4