Skip to content

Commit a02f64a

Browse files
authored
Merge pull request #4437 from DataDog/appsec-fix-active-record-instrumentation-for-rails4
Fix AppSec ActiveRecord instrumentation for Rails 4 and ruby < 2.7
2 parents 7e57abb + 82cb155 commit a02f64a

File tree

5 files changed

+101
-19
lines changed

5 files changed

+101
-19
lines changed

Matrixfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@
264264
'redis-3' => '✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',
265265
},
266266
'appsec:active_record' => {
267-
'relational_db' => ' 2.5 / 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',
267+
'relational_db' => ' 2.5 / 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',
268268
},
269269
'appsec:rack' => {
270270
'rack-latest' => '✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',

lib/datadog/appsec/contrib/active_record/instrumentation.rb

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def detect_sql_injection(sql, adapter_name)
4343
end
4444
end
4545

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

55-
# patch for postgres adapter in ActiveRecord < 7.1
55+
# patch for mysql2, sqlite3, and postgres+jdbc adapters in ActiveRecord < 7.1
56+
module ExecQueryAdapterPatch
57+
def exec_query(sql, *args, **rest)
58+
Instrumentation.detect_sql_injection(sql, adapter_name)
59+
60+
super
61+
end
62+
end
63+
64+
# patch for mysql2, sqlite3, and postgres+jdbc db adapters in ActiveRecord 4
65+
module Rails4ExecQueryAdapterPatch
66+
def exec_query(sql, *args)
67+
Instrumentation.detect_sql_injection(sql, adapter_name)
68+
69+
super
70+
end
71+
end
72+
73+
# patch for non-jdbc postgres adapter in ActiveRecord > 4
5674
module ExecuteAndClearAdapterPatch
5775
def execute_and_clear(sql, *args, **rest)
5876
Instrumentation.detect_sql_injection(sql, adapter_name)
@@ -61,10 +79,9 @@ def execute_and_clear(sql, *args, **rest)
6179
end
6280
end
6381

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

7087
super

lib/datadog/appsec/contrib/active_record/patcher.rb

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,81 @@ def target_version
1919
end
2020

2121
def patch
22+
# Rails 7.0 intruduced new on-load hooks for sqlite3 and postgresql adapters
23+
# The load hook for mysql2 adapter was introduced in Rails 7.1
24+
#
25+
# If the adapter is not loaded when the :active_record load hook is called,
26+
# we need to add a load hook for the adapter
2227
ActiveSupport.on_load :active_record do
23-
instrumentation_module = if ::ActiveRecord.gem_version >= Gem::Version.new('7.1')
24-
Instrumentation::InternalExecQueryAdapterPatch
25-
else
26-
Instrumentation::ExecQueryAdapterPatch
27-
end
28-
2928
if defined?(::ActiveRecord::ConnectionAdapters::SQLite3Adapter)
30-
::ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend(instrumentation_module)
29+
::Datadog::AppSec::Contrib::ActiveRecord::Patcher.patch_sqlite3_adapter
30+
else
31+
ActiveSupport.on_load :active_record_sqlite3adapter do
32+
::Datadog::AppSec::Contrib::ActiveRecord::Patcher.patch_sqlite3_adapter
33+
end
3134
end
3235

3336
if defined?(::ActiveRecord::ConnectionAdapters::Mysql2Adapter)
34-
::ActiveRecord::ConnectionAdapters::Mysql2Adapter.prepend(instrumentation_module)
37+
::Datadog::AppSec::Contrib::ActiveRecord::Patcher.patch_mysql2_adapter
38+
else
39+
ActiveSupport.on_load :active_record_mysql2adapter do
40+
::Datadog::AppSec::Contrib::ActiveRecord::Patcher.patch_mysql2_adapter
41+
end
3542
end
3643

3744
if defined?(::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
38-
unless defined?(::ActiveRecord::ConnectionAdapters::JdbcAdapter)
39-
instrumentation_module = Instrumentation::ExecuteAndClearAdapterPatch
45+
::Datadog::AppSec::Contrib::ActiveRecord::Patcher.patch_postgresql_adapter
46+
else
47+
ActiveSupport.on_load :active_record_postgresqladapter do
48+
::Datadog::AppSec::Contrib::ActiveRecord::Patcher.patch_postgresql_adapter
4049
end
41-
42-
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(instrumentation_module)
4350
end
4451
end
4552
end
53+
54+
def patch_sqlite3_adapter
55+
instrumentation_module = if ::ActiveRecord.gem_version >= Gem::Version.new('7.1')
56+
Instrumentation::InternalExecQueryAdapterPatch
57+
elsif ::ActiveRecord.gem_version.segments.first == 4
58+
Instrumentation::Rails4ExecQueryAdapterPatch
59+
else
60+
Instrumentation::ExecQueryAdapterPatch
61+
end
62+
63+
::ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend(instrumentation_module)
64+
end
65+
66+
def patch_mysql2_adapter
67+
instrumentation_module = if ::ActiveRecord.gem_version >= Gem::Version.new('7.1')
68+
Instrumentation::InternalExecQueryAdapterPatch
69+
elsif ::ActiveRecord.gem_version.segments.first == 4
70+
Instrumentation::Rails4ExecQueryAdapterPatch
71+
else
72+
Instrumentation::ExecQueryAdapterPatch
73+
end
74+
75+
::ActiveRecord::ConnectionAdapters::Mysql2Adapter.prepend(instrumentation_module)
76+
end
77+
78+
def patch_postgresql_adapter
79+
instrumentation_module = if ::ActiveRecord.gem_version.segments.first == 4
80+
Instrumentation::Rails4ExecuteAndClearAdapterPatch
81+
else
82+
Instrumentation::ExecuteAndClearAdapterPatch
83+
end
84+
85+
if defined?(::ActiveRecord::ConnectionAdapters::JdbcAdapter)
86+
instrumentation_module = if ::ActiveRecord.gem_version >= Gem::Version.new('7.1')
87+
Instrumentation::InternalExecQueryAdapterPatch
88+
elsif ::ActiveRecord.gem_version.segments.first == 4
89+
Instrumentation::Rails4ExecQueryAdapterPatch
90+
else
91+
Instrumentation::ExecQueryAdapterPatch
92+
end
93+
end
94+
95+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(instrumentation_module)
96+
end
4697
end
4798
end
4899
end

sig/datadog/appsec/contrib/active_record/instrumentation.rbs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ module Datadog
1616
module ExecQueryAdapterPatch
1717
def exec_query: (String sql, *untyped args, **untyped rest) -> untyped
1818
end
19+
20+
module Rails4ExecuteAndClearAdapterPatch
21+
def execute_and_clear: (String sql, String name, untyped binds) -> untyped
22+
end
23+
24+
module Rails4ExecQueryAdapterPatch
25+
def exec_query: (String sql, *untyped args) -> untyped
26+
end
1927
end
2028
end
2129
end

sig/datadog/appsec/contrib/active_record/patcher.rbs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ module Datadog
88
def self?.target_version: () -> Gem::Version?
99

1010
def self?.patch: () -> void
11+
12+
def self?.patch_sqlite3_adapter: () -> void
13+
14+
def self?.patch_mysql2_adapter: () -> void
15+
16+
def self?.patch_postgresql_adapter: () -> void
1117
end
1218
end
1319
end

0 commit comments

Comments
 (0)