-
Notifications
You must be signed in to change notification settings - Fork 375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a force disable of appsec when using Ruby >= 3.3 with old ffi #3969
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #3969 +/- ##
==========================================
- Coverage 97.87% 97.86% -0.01%
==========================================
Files 1313 1313
Lines 78352 78366 +14
Branches 3886 3888 +2
==========================================
+ Hits 76684 76690 +6
- Misses 1668 1676 +8 ☔ View full report in Codecov by Sentry. |
BenchmarksBenchmark execution time: 2024-10-07 09:43:42 Comparing candidate commit 03f3bc9 in PR branch Found 0 performance improvements and 1 performance regressions! Performance is the same for 22 metrics, 2 unstable metrics. scenario:profiler - profiler gc
|
lib/datadog/appsec/component.rb
Outdated
'Appsec is not supported in Ruby versions above 3.3.0 when using `ffi` versions older than 1.16.0, ' \ | ||
'and will be forcibly disabled. This is due to a memory leak in `ffi` versions lower than 1.16.0.' \ | ||
'Please upgrade your `ffi` version to 1.16.0 or higher.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT if we join the reason to the main sentence?
'Appsec is not supported in Ruby versions above 3.3.0 when using `ffi` versions older than 1.16.0, ' \ | |
'and will be forcibly disabled. This is due to a memory leak in `ffi` versions lower than 1.16.0.' \ | |
'Please upgrade your `ffi` version to 1.16.0 or higher.' | |
'AppSec is not supported in Ruby versions above 3.3.0 when using `ffi` versions older than 1.16.0, ' \ | |
'and will be forcibly disabled due to a memory leak in `ffi`. ' \ | |
'Please upgrade your `ffi` version to 1.16.0 or higher.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, this is definitely better, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed: 51a803f
lib/datadog/appsec/component.rb
Outdated
@@ -28,6 +29,22 @@ def build_appsec_component(settings, telemetry:) | |||
|
|||
private | |||
|
|||
def enable_appsec?(settings) | |||
return false unless settings.respond_to?(:appsec) && settings.appsec.enabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think inverting unless &&
makes sense here? I find it hard to understand (negate logic with addition to &&
)
return false unless settings.respond_to?(:appsec) && settings.appsec.enabled | |
return false if !settings.respond_to?(:appsec) || !settings.appsec.enabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, this will improve readability
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the record I think the unless
& &&
version was easier to follow.
lib/datadog/appsec/component.rb
Outdated
@@ -12,6 +12,7 @@ class Component | |||
class << self | |||
def build_appsec_component(settings, telemetry:) | |||
return unless settings.respond_to?(:appsec) && settings.appsec.enabled | |||
return unless enable_appsec?(settings) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the method with ?
is doing more than 1 thing. WDYT on splitting it on something like this
return unless enable_appsec?(settings) | |
return unless appsec_enabled?(settings) | |
return if incompatible_ffi_version? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed appsec_enabled?
method and added incompatible_ffi_version?
here: 51a803f
Ruby 3.3 and newer has a memory leak when using libddwaf with `ffi` gem older than 1.16.0. We want to avoid forcing our customers to update `ffi` gem, by setting the minimum version in `libddwaf-rb`, since prebuilt `ffi` extension is incompatible with rubygems version supplied with ruby 2.7 and below.
7c15366
to
51a803f
Compare
lib/datadog/appsec/component.rb
Outdated
@@ -28,6 +29,19 @@ def build_appsec_component(settings, telemetry:) | |||
|
|||
private | |||
|
|||
def incompatible_ffi_version? | |||
ffi_version = Gem.loaded_specs['ffi'] && Gem.loaded_specs['ffi'].version | |||
return false unless RUBY_VERSION >= '3.3.0' && ffi_version < '1.16.0' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you would need to parse the FFI version correctly, string comparison works for Ruby versions only because their components are kept to single digits.
I would also check against 3.3 not 3.3.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added Gem::Version
for all version comparisons here: 03f3bc9
context 'when using old ffi version with Ruby 3.3.x' do | ||
before do | ||
stub_const('RUBY_VERSION', '3.3.0') | ||
allow(Gem).to receive(:loaded_specs).and_return('ffi' => double(version: Gem::Version.new('1.15.4'))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use 1.9.0 for the FFI version the test will fail right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, it is still green:
Gem::Version.new('1.9.0') < '1.16.0'
=> true
I think this is because we are comparing Gem::Version
with a String
(not sure this will work correctly in older ruby versions though)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can dismantle version on major, minor and test piece-by-piece (just in case)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, https://github.com/rubygems/rubygems/blob/master/lib/rubygems/version.rb#L358, added in rubygems/rubygems@7e0dbb7 2 years ago, which sounds like this functionality is probably not going to exist in Ruby 2.5?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rails/rails#47480 conveniently says that the threshold for this feature is Ruby 3.1.
ffi_version = Gem.loaded_specs['ffi'] && Gem.loaded_specs['ffi'].version | ||
return false unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3') && | ||
ffi_version < Gem::Version.new('1.16.0') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how it could be relevant and don't want to over complicate it, but if we check that Gem.loaded_specs['ffi']
not nil
, what if it's nil
?
Then entire ffi_version
is nil
, right? And it could fail next check
❯❯❯ irb
irb(main):001> ffi_version = Gem.loaded_specs['ffi'] && Gem.loaded_specs['ffi'].version
=> nil
irb(main):002* Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3') &&
irb(main):003> ffi_version < Gem::Version.new('1.16.0')
(irb):3:in `<main>': undefined method `<' for nil (NoMethodError)
ffi_version < Gem::Version.new('1.16.0')
^
from <internal:kernel>:187:in `loop'```
But I made it up, maybe it never happen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will still add a guard clause for this, just in case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
although I'm not even sure what to do in this case - we don't want to create a processor, and we probably don't want to use the same warning message. Do we want to silently return true in such case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be fine to just not warn in this situation and carry on to subsequent code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can consider it to be "not there" ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I merged to early. Here is a follow-up PR: #3978
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's good, maybe with a few adjustments (if needed)
Ruby 3.3.0 and above has a memory leak when using
libddwaf-rb
withffi
gem older than 1.16.0.We want to avoid forcing our customers to update
ffi
gem by setting the minimum version inlibddwaf-rb
, since prebuiltffi
extension is incompatible with rubygems version supplied with ruby 2.7 and below.The memory leak happens because
ffi
gem used untyped DATA API prior to this commit (which is part of 1.16.0 release):ffi/ffi@ca2744a
Motivation:
Memory leak that occurs when enabling
appsec
for applications running on ruby 3.3.x and an old version offfi
gem locked in Gemfile.lock (older than1.16.0
).