Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Matrixfile
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@
'redis-3' => '✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',
},
'appsec:active_record' => {
'relational_db' => ' 2.5 / 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',
'relational_db' => ' 2.5 / 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',
},
'appsec:rack' => {
'rack-latest' => '✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',
Expand Down
29 changes: 23 additions & 6 deletions lib/datadog/appsec/contrib/active_record/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def detect_sql_injection(sql, adapter_name)
end
end

# patch for all adapters in ActiveRecord >= 7.1
# patch for mysql2, sqlite3, and postgres+jdbc adapters in ActiveRecord >= 7.1
module InternalExecQueryAdapterPatch
def internal_exec_query(sql, *args, **rest)
Instrumentation.detect_sql_injection(sql, adapter_name)
Expand All @@ -52,7 +52,25 @@ def internal_exec_query(sql, *args, **rest)
end
end

# patch for postgres adapter in ActiveRecord < 7.1
# patch for mysql2, sqlite3, and postgres+jdbc adapters in ActiveRecord < 7.1
module ExecQueryAdapterPatch
def exec_query(sql, *args, **rest)
Instrumentation.detect_sql_injection(sql, adapter_name)

super
end
end

# patch for mysql2, sqlite3, and postgres+jdbc db adapters in ActiveRecord 4
module Rails4ExecQueryAdapterPatch
def exec_query(sql, *args)
Instrumentation.detect_sql_injection(sql, adapter_name)

super
end
end

# patch for non-jdbc postgres adapter in ActiveRecord > 4
module ExecuteAndClearAdapterPatch
def execute_and_clear(sql, *args, **rest)
Instrumentation.detect_sql_injection(sql, adapter_name)
Expand All @@ -61,10 +79,9 @@ def execute_and_clear(sql, *args, **rest)
end
end

# patch for mysql2 and sqlite3 adapters in ActiveRecord < 7.1
# this patch is also used when using JDBC adapter
module ExecQueryAdapterPatch
def exec_query(sql, *args, **rest)
# patch for non-jdbc postgres adapter in ActiveRecord 4
module Rails4ExecuteAndClearAdapterPatch
def execute_and_clear(sql, name, binds)
Instrumentation.detect_sql_injection(sql, adapter_name)

super
Expand Down
75 changes: 63 additions & 12 deletions lib/datadog/appsec/contrib/active_record/patcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,81 @@ def target_version
end

def patch
# Rails 7.0 intruduced new on-load hooks for sqlite3 and postgresql adapters
# The load hook for mysql2 adapter was introduced in Rails 7.1
#
# If the adapter is not loaded when the :active_record load hook is called,
# we need to add a load hook for the adapter
ActiveSupport.on_load :active_record do
instrumentation_module = if ::ActiveRecord.gem_version >= Gem::Version.new('7.1')
Instrumentation::InternalExecQueryAdapterPatch
else
Instrumentation::ExecQueryAdapterPatch
end

if defined?(::ActiveRecord::ConnectionAdapters::SQLite3Adapter)
::ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend(instrumentation_module)
::Datadog::AppSec::Contrib::ActiveRecord::Patcher.patch_sqlite3_adapter
else
ActiveSupport.on_load :active_record_sqlite3adapter do
::Datadog::AppSec::Contrib::ActiveRecord::Patcher.patch_sqlite3_adapter
end
end

if defined?(::ActiveRecord::ConnectionAdapters::Mysql2Adapter)
::ActiveRecord::ConnectionAdapters::Mysql2Adapter.prepend(instrumentation_module)
::Datadog::AppSec::Contrib::ActiveRecord::Patcher.patch_mysql2_adapter
else
ActiveSupport.on_load :active_record_mysql2adapter do
::Datadog::AppSec::Contrib::ActiveRecord::Patcher.patch_mysql2_adapter
end
end

if defined?(::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
unless defined?(::ActiveRecord::ConnectionAdapters::JdbcAdapter)
instrumentation_module = Instrumentation::ExecuteAndClearAdapterPatch
::Datadog::AppSec::Contrib::ActiveRecord::Patcher.patch_postgresql_adapter
else
ActiveSupport.on_load :active_record_postgresqladapter do
::Datadog::AppSec::Contrib::ActiveRecord::Patcher.patch_postgresql_adapter
end

::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(instrumentation_module)
end
end
end

def patch_sqlite3_adapter
instrumentation_module = if ::ActiveRecord.gem_version >= Gem::Version.new('7.1')
Instrumentation::InternalExecQueryAdapterPatch
elsif ::ActiveRecord.gem_version.segments.first == 4
Instrumentation::Rails4ExecQueryAdapterPatch
else
Instrumentation::ExecQueryAdapterPatch
end

::ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend(instrumentation_module)
end

def patch_mysql2_adapter
instrumentation_module = if ::ActiveRecord.gem_version >= Gem::Version.new('7.1')
Instrumentation::InternalExecQueryAdapterPatch
elsif ::ActiveRecord.gem_version.segments.first == 4
Instrumentation::Rails4ExecQueryAdapterPatch
else
Instrumentation::ExecQueryAdapterPatch
end

::ActiveRecord::ConnectionAdapters::Mysql2Adapter.prepend(instrumentation_module)
end

def patch_postgresql_adapter
instrumentation_module = if ::ActiveRecord.gem_version.segments.first == 4
Instrumentation::Rails4ExecuteAndClearAdapterPatch
else
Instrumentation::ExecuteAndClearAdapterPatch
end

if defined?(::ActiveRecord::ConnectionAdapters::JdbcAdapter)
instrumentation_module = if ::ActiveRecord.gem_version >= Gem::Version.new('7.1')
Instrumentation::InternalExecQueryAdapterPatch
elsif ::ActiveRecord.gem_version.segments.first == 4
Instrumentation::Rails4ExecQueryAdapterPatch
else
Instrumentation::ExecQueryAdapterPatch
end
end

::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(instrumentation_module)
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ module Datadog
module ExecQueryAdapterPatch
def exec_query: (String sql, *untyped args, **untyped rest) -> untyped
end

module Rails4ExecuteAndClearAdapterPatch
def execute_and_clear: (String sql, String name, untyped binds) -> untyped
end

module Rails4ExecQueryAdapterPatch
def exec_query: (String sql, *untyped args) -> untyped
end
end
end
end
Expand Down
6 changes: 6 additions & 0 deletions sig/datadog/appsec/contrib/active_record/patcher.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ module Datadog
def self?.target_version: () -> Gem::Version?

def self?.patch: () -> void

def self?.patch_sqlite3_adapter: () -> void

def self?.patch_mysql2_adapter: () -> void

def self?.patch_postgresql_adapter: () -> void
end
end
end
Expand Down
Loading