From 4f44aa9d514e701ada92b5cf08beccf566eeaebf Mon Sep 17 00:00:00 2001 From: Zack Deveau Date: Tue, 22 Nov 2022 09:48:59 -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::Base.raise_int_wider_than_64bit which defaults to true. [CVE-2022-44566] --- .../connection_adapters/postgresql/quoting.rb | 26 +++++++++++++++++ activerecord/lib/active_record/core.rb | 7 +++++ .../cases/adapters/postgresql/quoting_test.rb | 28 +++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/activerecord-6.1.4.1/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord-6.1.4.1/lib/active_record/connection_adapters/postgresql/quoting.rb index 3d94d4bb36..4db5f8f528 100644 --- a/activerecord-6.1.4.1/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord-6.1.4.1/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 @@ -120,7 +126,27 @@ def lookup_cast_type(sql_type) super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i) 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::Base.raise_int_wider_than_64bit to false. + ERROR + raise IntegerOutOf64BitRange.new exception + end + end + def _quote(value) + if ActiveRecord::Base.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/activerecord-6.1.4.1/lib/active_record/core.rb b/activerecord-6.1.4.1/lib/active_record/core.rb index 9f1584d46b..d3bfd4929e 100644 --- a/activerecord-6.1.4.1/lib/active_record/core.rb +++ b/activerecord-6.1.4.1/lib/active_record/core.rb @@ -163,6 +163,13 @@ def self.configurations # to Psych safe_load in the YAML Coder mattr_accessor :yaml_column_permitted_classes, instance_writer: false, default: [Symbol] + ## + # :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 + mattr_accessor :raise_int_wider_than_64bit, instance_writer: false, default: true + self.filter_attributes = [] def self.connection_handler diff --git a/test/cases/adapters/postgresql/quoting_test.rb b/test/cases/adapters/postgresql/quoting_test.rb index d571355a9c..125565f9c8 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::Base.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::Base.raise_int_wider_than_64bit = false + value = 9223372036854775807 + 1 + assert_equal "9223372036854775808", @conn.quote(value) + ActiveRecord::Base.raise_int_wider_than_64bit = @raise_int_wider_than_64bit + end end end end -- 2.35.1