Skip to content

Commit

Permalink
Added integer width check to PostgreSQL::Quoting
Browse files Browse the repository at this point in the history
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]
  • Loading branch information
fresh-eggs authored and jhawthorn committed Jan 17, 2023
1 parent 8015c2c commit 82bcdc0
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 0 deletions.
8 changes: 8 additions & 0 deletions activerecord/lib/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ module ActiveRecord
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
Expand All @@ -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)}'"
Expand Down
28 changes: 28 additions & 0 deletions activerecord/test/cases/adapters/postgresql/quoting_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class PostgreSQLAdapter
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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions guides/source/configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,12 @@ Defaults to `[Symbol]`. Allows applications to include additional permitted clas

Defaults to `false`. Allows applications to opt into using `unsafe_load` on the `ActiveRecord::Coders::YAMLColumn`.

#### `config.active_record.raise_int_wider_than_64bit`

Defaults to `true`. Determines whether to raise an exception or not when
the PostgreSQL adapter is provided an integer that is wider than signed
64bit representation.

#### `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans`

Controls whether the Active Record MySQL adapter will consider all `tinyint(1)` columns as booleans. Defaults to `true`.
Expand Down
17 changes: 17 additions & 0 deletions railties/test/application/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1711,6 +1711,23 @@ def index
assert ActiveRecord.use_yaml_unsafe_load
end

test "config.active_record.raise_int_wider_than_64bit is true by default" do
app "production"
assert ActiveRecord.raise_int_wider_than_64bit
end

test "config.active_record.raise_int_wider_than_64bit can be configured" do
remove_from_config '.*config\.load_defaults.*\n'

app_file "config/initializers/dont_raise.rb", <<-RUBY
Rails.application.config.active_record.raise_int_wider_than_64bit = false
RUBY

app "production"
assert_not ActiveRecord.raise_int_wider_than_64bit
end


test "config.active_record.yaml_column_permitted_classes is [Symbol] by default" do
app "production"
assert_equal([Symbol], ActiveRecord.yaml_column_permitted_classes)
Expand Down

0 comments on commit 82bcdc0

Please sign in to comment.