contrast-agent 4.12.0 → 4.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/cs__assess_module/cs__assess_module.c +48 -0
- data/ext/cs__assess_module/cs__assess_module.h +7 -0
- data/ext/cs__common/cs__common.c +5 -0
- data/ext/cs__common/cs__common.h +8 -0
- data/ext/cs__contrast_patch/cs__contrast_patch.c +16 -1
- data/ext/cs__os_information/cs__os_information.c +31 -0
- data/ext/cs__os_information/cs__os_information.h +7 -0
- data/ext/cs__os_information/extconf.rb +5 -0
- data/lib/contrast/agent/assess/policy/propagation_method.rb +2 -116
- data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
- data/lib/contrast/agent/assess/policy/source_method.rb +2 -71
- data/lib/contrast/agent/assess/policy/trigger_method.rb +4 -106
- data/lib/contrast/agent/assess/property/tagged.rb +2 -128
- data/lib/contrast/agent/deadzone/policy/policy.rb +1 -1
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -0
- data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
- data/lib/contrast/agent/middleware.rb +22 -0
- data/lib/contrast/agent/patching/policy/patch.rb +28 -235
- data/lib/contrast/agent/patching/policy/patcher.rb +2 -41
- data/lib/contrast/agent/request_handler.rb +7 -3
- data/lib/contrast/agent/startup_metrics_telemetry_event.rb +71 -0
- data/lib/contrast/agent/static_analysis.rb +4 -2
- data/lib/contrast/agent/telemetry.rb +129 -0
- data/lib/contrast/agent/telemetry_event.rb +34 -0
- data/lib/contrast/agent/thread_watcher.rb +43 -14
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +6 -0
- data/lib/contrast/components/api.rb +34 -0
- data/lib/contrast/components/app_context.rb +24 -0
- data/lib/contrast/components/config.rb +90 -11
- data/lib/contrast/components/contrast_service.rb +6 -0
- data/lib/contrast/config/api_configuration.rb +22 -0
- data/lib/contrast/config/env_variables.rb +25 -0
- data/lib/contrast/config/root_configuration.rb +1 -0
- data/lib/contrast/config/service_configuration.rb +2 -1
- data/lib/contrast/config.rb +1 -0
- data/lib/contrast/configuration.rb +3 -0
- data/lib/contrast/framework/manager.rb +14 -12
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
- data/lib/contrast/framework/rails/patch/support.rb +31 -29
- data/lib/contrast/logger/application.rb +4 -0
- data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
- data/lib/contrast/utils/assess/property/tagged_utils.rb +142 -0
- data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
- data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
- data/lib/contrast/utils/exclude_key.rb +20 -0
- data/lib/contrast/utils/metrics_hash.rb +59 -0
- data/lib/contrast/utils/os.rb +23 -0
- data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
- data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
- data/lib/contrast/utils/requests_client.rb +150 -0
- data/lib/contrast/utils/telemetry.rb +78 -0
- data/lib/contrast/utils/telemetry_identifier.rb +137 -0
- data/lib/contrast.rb +18 -0
- data/ruby-agent.gemspec +2 -1
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +32 -10
@@ -5,6 +5,7 @@ require 'contrast/agent/assess/tag'
|
|
5
5
|
require 'contrast/utils/object_share'
|
6
6
|
require 'contrast/utils/string_utils'
|
7
7
|
require 'contrast/utils/tag_util'
|
8
|
+
require 'contrast/utils/assess/property/tagged_utils'
|
8
9
|
|
9
10
|
module Contrast
|
10
11
|
module Agent
|
@@ -13,6 +14,7 @@ module Contrast
|
|
13
14
|
# This module serves to hold the functionality required for the
|
14
15
|
# management of our dataflow tags.
|
15
16
|
module Tagged
|
17
|
+
include Contrast::Utils::Assess::TaggedUtils
|
16
18
|
# Is any tag present?
|
17
19
|
# Creating Tags is expensive and we check for Tags all the time on
|
18
20
|
# untracked things. ALWAYS!!! call this method before checking if an
|
@@ -45,134 +47,6 @@ module Contrast
|
|
45
47
|
false
|
46
48
|
end
|
47
49
|
|
48
|
-
# Find all of the ranges that span a given index. This is used
|
49
|
-
# in propagation when we need to shift tags about. For instance, in
|
50
|
-
# the append method when we need to see if any tag at the end needs
|
51
|
-
# to be expanded out to the size of the new String.
|
52
|
-
#
|
53
|
-
# Note: Tags do not know their key, so this is only the range covered
|
54
|
-
#
|
55
|
-
# @param idx [Integer] the index to check for tags
|
56
|
-
# @return [Array<Contrast::Agent::Assess::Tag>] a set of all the Tags
|
57
|
-
# covering the given index. This is only the ranges, not the names.
|
58
|
-
def tags_at idx
|
59
|
-
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?
|
60
|
-
|
61
|
-
at = []
|
62
|
-
tags.each_value do |tag_array|
|
63
|
-
tag_array.each do |tag|
|
64
|
-
if tag.covers?(idx)
|
65
|
-
at << tag
|
66
|
-
elsif tag.above?(idx)
|
67
|
-
break
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
at
|
72
|
-
end
|
73
|
-
|
74
|
-
# given a range, select all tags in that range the selected tags are
|
75
|
-
# shifted such that the start index of the new tag (0) aligns with
|
76
|
-
# the given start index in the range
|
77
|
-
#
|
78
|
-
# current tags: 5-15
|
79
|
-
# range : 5-10
|
80
|
-
# result : 0-05
|
81
|
-
#
|
82
|
-
# Note that we disable Lint/DuplicateBranch in this branch in order
|
83
|
-
# list out all tag range cases in the proper order to make this
|
84
|
-
# easier to understand
|
85
|
-
#
|
86
|
-
# @param range [Range] the span to check, inclusive to exclusive
|
87
|
-
# @return [Hash{String => Contrast::Agent::Assess::Tag}] the hash of
|
88
|
-
# key to tags
|
89
|
-
def tags_at_range range
|
90
|
-
return Contrast::Utils::ObjectShare::EMPTY_HASH unless tracked?
|
91
|
-
|
92
|
-
at = Hash.new { |h, k| h[k] = [] }
|
93
|
-
tags.each_pair do |key, value|
|
94
|
-
add = nil
|
95
|
-
value.each do |tag|
|
96
|
-
within_range = resize_to_range(tag, range)
|
97
|
-
if within_range
|
98
|
-
add ||= []
|
99
|
-
add << within_range
|
100
|
-
end
|
101
|
-
end
|
102
|
-
next unless add&.any?
|
103
|
-
|
104
|
-
at[key] = add
|
105
|
-
end
|
106
|
-
at
|
107
|
-
end
|
108
|
-
|
109
|
-
# Given a tag name and range object, add a new tag to this
|
110
|
-
# collection. If the given range touches an existing tag,
|
111
|
-
# we'll combine the two, adjusting the existing one and
|
112
|
-
# dropping this new one.
|
113
|
-
#
|
114
|
-
# @param label [String] the name of the tag
|
115
|
-
# @param range [Range] the Range that the tag covers, inclusive to
|
116
|
-
# exclusive
|
117
|
-
def add_tag label, range
|
118
|
-
length = range.end - range.begin
|
119
|
-
tag = Contrast::Agent::Assess::Tag.new(label, length, range.begin)
|
120
|
-
existing = fetch_tag(label)
|
121
|
-
tags[label] = Contrast::Utils::TagUtil.ordered_merge(existing, tag)
|
122
|
-
end
|
123
|
-
|
124
|
-
def set_tags label, tag_ranges
|
125
|
-
tags[label] = tag_ranges
|
126
|
-
end
|
127
|
-
|
128
|
-
# Returns a list of all current tags.
|
129
|
-
#
|
130
|
-
# @return [Hash<Contrast::Agent::Assess::Tag>]
|
131
|
-
def get_tags # rubocop:disable Naming/AccessorMethodName
|
132
|
-
return Contrast::Utils::ObjectShare::EMPTY_HASH unless tracked?
|
133
|
-
|
134
|
-
tags
|
135
|
-
end
|
136
|
-
|
137
|
-
# We'll use this as a helper method to retrieve tags from the hash.
|
138
|
-
# Because the hash auto-populates an empty array when we try to
|
139
|
-
# access a tag in it, we cannot use the [] method without side
|
140
|
-
# effect. To get around this, we'll use a fetch work around.
|
141
|
-
#
|
142
|
-
# @param label [Symbol] the label to look up
|
143
|
-
# @return [Array<Contrast::Agent::Assess::Tag>] all the tags with
|
144
|
-
# that label
|
145
|
-
def fetch_tag label
|
146
|
-
get_tags.fetch(label, nil) if tracked?
|
147
|
-
end
|
148
|
-
|
149
|
-
# Remove all tags with a given label
|
150
|
-
def delete_tags label
|
151
|
-
tags.delete(label) if tracked?
|
152
|
-
end
|
153
|
-
|
154
|
-
# Reset the tag hash
|
155
|
-
def clear_tags
|
156
|
-
tags.clear if tracked?
|
157
|
-
end
|
158
|
-
|
159
|
-
# Returns a list of all current tag labels, most likely to be used for
|
160
|
-
# a splat operation
|
161
|
-
def tag_keys
|
162
|
-
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?
|
163
|
-
|
164
|
-
tags.keys
|
165
|
-
end
|
166
|
-
|
167
|
-
# Calls merge to combine touching or overlapping tags
|
168
|
-
# Deletes empty tags
|
169
|
-
def cleanup_tags
|
170
|
-
return unless tracked?
|
171
|
-
|
172
|
-
Contrast::Utils::TagUtil.merge_tags(tags)
|
173
|
-
tags.delete_if { |_, value| value.empty? }
|
174
|
-
end
|
175
|
-
|
176
50
|
# Remove all tags within the given ranges.
|
177
51
|
# This does not delete an entire tag if part of that tag is
|
178
52
|
# outside this range, meaning we may reduce sizes of tags
|
@@ -38,7 +38,7 @@ module Contrast
|
|
38
38
|
def validate
|
39
39
|
return if class_name
|
40
40
|
|
41
|
-
raise(ArgumentError, "#{
|
41
|
+
raise(ArgumentError, "#{ node_class } #{ id } did not have a proper class name. Unable to create.")
|
42
42
|
end
|
43
43
|
|
44
44
|
def module_names
|
@@ -71,6 +71,7 @@ module Contrast
|
|
71
71
|
|
72
72
|
# Populate the library_usages field of the Activity message using the data stored in the @gemdigest_cache.
|
73
73
|
#
|
74
|
+
# TODO: RUBY-1355
|
74
75
|
# @param activity [Contrast::Api::Dtm::Activity] the message to which to append the usage data
|
75
76
|
def generate_library_usage activity
|
76
77
|
return unless enabled?
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/utils/metrics_hash'
|
5
|
+
require 'contrast/agent/telemetry_event'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
# This class will hold the basic information for a Telemetry Event
|
10
|
+
class MetricTelemetryEvent < Contrast::Agent::TelemetryEvent
|
11
|
+
include Contrast::Utils
|
12
|
+
|
13
|
+
attr_reader :fields
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
super
|
17
|
+
@fields = MetricsHash.new(Numeric)
|
18
|
+
@fields['_filter'] = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_json **_args
|
22
|
+
super.merge!({ fields: @fields })
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -10,8 +10,10 @@ require 'contrast/utils/object_share'
|
|
10
10
|
require 'contrast/components/logger'
|
11
11
|
require 'contrast/components/scope'
|
12
12
|
require 'contrast/utils/heap_dump_util'
|
13
|
+
require 'contrast/utils/telemetry'
|
13
14
|
require 'contrast/agent/request_handler'
|
14
15
|
require 'contrast/agent/static_analysis'
|
16
|
+
require 'contrast/agent/startup_metrics_telemetry_event'
|
15
17
|
|
16
18
|
require 'contrast/utils/timer'
|
17
19
|
|
@@ -68,6 +70,7 @@ module Contrast
|
|
68
70
|
::Contrast::SETTINGS.reset_state
|
69
71
|
|
70
72
|
inform_deprecations
|
73
|
+
telemetry_disclaimer
|
71
74
|
|
72
75
|
if ::Contrast::CONFIG.invalid?
|
73
76
|
::Contrast::AGENT.disable!
|
@@ -89,6 +92,13 @@ module Contrast
|
|
89
92
|
Contrast::Agent.thread_watcher.ensure_running?
|
90
93
|
end
|
91
94
|
|
95
|
+
if Contrast::Agent::Telemetry.enabled?
|
96
|
+
logger.debug_with_time('middleware: sending startup metrics telemetry event') do
|
97
|
+
event = Contrast::Agent::StartupMetricsTelemetryEvent.new
|
98
|
+
Contrast::Agent.thread_watcher.telemetry_queue.send_event(event)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
92
102
|
logger.debug_with_time('middleware: instrument shared libraries and patch') do
|
93
103
|
Contrast::Agent::Patching::Policy::Patcher.patch
|
94
104
|
end
|
@@ -225,6 +235,18 @@ module Contrast
|
|
225
235
|
Kernel.warn('[Contrast Security] [DEPRECATION] Support for Ruby 2.5 will be removed in April 2021. '\
|
226
236
|
'Please contact Customer Support prior if you require continued support.')
|
227
237
|
end
|
238
|
+
|
239
|
+
# Displays Telemetry disclaimer if Telemetry is enabled.
|
240
|
+
# if .telemetry file doesn't exist we create one and then show the disclaimer.
|
241
|
+
# if the file already exists we do nothing.
|
242
|
+
def telemetry_disclaimer
|
243
|
+
return unless Contrast::Agent::Telemetry.enabled?
|
244
|
+
return unless Contrast::Utils::Telemetry.create_telemetry_file
|
245
|
+
|
246
|
+
logger.info Contrast::Utils::Telemetry.disclaimer
|
247
|
+
$stdout.print Contrast::Utils::Telemetry.disclaimer
|
248
|
+
true
|
249
|
+
end
|
228
250
|
end
|
229
251
|
end
|
230
252
|
end
|
@@ -4,6 +4,7 @@
|
|
4
4
|
require 'monitor'
|
5
5
|
require 'contrast/components/logger'
|
6
6
|
require 'contrast/components/scope'
|
7
|
+
require 'contrast/utils/patching/policy/patch_utils'
|
7
8
|
|
8
9
|
require 'contrast/agent'
|
9
10
|
require 'contrast/logger/log'
|
@@ -32,6 +33,8 @@ module Contrast
|
|
32
33
|
# provides a map for which methods our renamed functions need to call
|
33
34
|
# and how.
|
34
35
|
module Patch
|
36
|
+
extend Contrast::Utils::Patching::PatchUtils
|
37
|
+
|
35
38
|
class << self
|
36
39
|
include Contrast::Agent::Assess::Policy::SourceMethod
|
37
40
|
include Contrast::Agent::Assess::Policy::PropagationMethod
|
@@ -57,226 +60,6 @@ module Contrast
|
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|
60
|
-
# THIS IS CALLED FROM C. Do not change the signature lightly.
|
61
|
-
#
|
62
|
-
# This method functions to call the infilter methods from our
|
63
|
-
# patches, allowing for analysis and reporting at the point just
|
64
|
-
# before the patched code is invoked.
|
65
|
-
#
|
66
|
-
# @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
|
67
|
-
# Mapping of the triggers on the given method.
|
68
|
-
# @param method [Symbol] The method into which we're patching
|
69
|
-
# @param exception [StandardError] Any exception raised during the
|
70
|
-
# call of the patched method.
|
71
|
-
# @param object [Object] The object on which the method is invoked,
|
72
|
-
# typically what would be returned by self.
|
73
|
-
# @param args [Array<Object>] The arguments passed to the method
|
74
|
-
# being invoked.
|
75
|
-
def apply_pre_patch method_policy, method, exception, object, args
|
76
|
-
apply_protect(method_policy, method, exception, object, args)
|
77
|
-
apply_inventory(method_policy, method, exception, object, args)
|
78
|
-
rescue Contrast::SecurityException => e
|
79
|
-
# We were told to block something, so we gotta. Don't catch this
|
80
|
-
# one, let it get back to our Middleware or even all the way out to
|
81
|
-
# the framework
|
82
|
-
raise e
|
83
|
-
rescue StandardError => e
|
84
|
-
# Anything else was our bad and we gotta catch that to allow for
|
85
|
-
# normal application flow
|
86
|
-
logger.error('Unable to apply pre patch to method.', e)
|
87
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
88
|
-
# This is something like NoMemoryError that we can't
|
89
|
-
# hope to handle. Nonetheless, shouldn't leak scope.
|
90
|
-
exit_contrast_scope!
|
91
|
-
raise e
|
92
|
-
end
|
93
|
-
|
94
|
-
# THIS IS CALLED FROM C. Do not change the signature lightly.
|
95
|
-
#
|
96
|
-
# This method functions to call the infilter methods from our
|
97
|
-
# patches, allowing for analysis and reporting at the point just
|
98
|
-
# after the patched code is invoked
|
99
|
-
#
|
100
|
-
# @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
|
101
|
-
# Mapping of the triggers on the given method.
|
102
|
-
# @param preshift [Contrast::Agent::Assess::PreShift] The capture
|
103
|
-
# of the state of the code just prior to the invocation of the
|
104
|
-
# patched method.
|
105
|
-
# @param object [Object] The object on which the method was
|
106
|
-
# invoked, typically what would be returned by self.
|
107
|
-
# @param ret [Object] The return of the method that was invoked.
|
108
|
-
# @param args [Array<Object>] The arguments passed to the method
|
109
|
-
# being invoked.
|
110
|
-
# @param block [Proc] The block passed to the method that was
|
111
|
-
# invoked.
|
112
|
-
def apply_post_patch method_policy, preshift, object, ret, args, block
|
113
|
-
apply_assess(method_policy, preshift, object, ret, args, block)
|
114
|
-
rescue StandardError => e
|
115
|
-
logger.error('Unable to apply post patch to method.', e)
|
116
|
-
end
|
117
|
-
|
118
|
-
# Apply the Protect patch which applies to the given method.
|
119
|
-
#
|
120
|
-
# @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
|
121
|
-
# Mapping of the triggers on the given method.
|
122
|
-
# @param method [Symbol] The method into which we're patching
|
123
|
-
# @param exception [StandardError] Any exception raised during the
|
124
|
-
# call of the patched method.
|
125
|
-
# @param object [Object] The object on which the method is invoked,
|
126
|
-
# typically what would be returned by self.
|
127
|
-
# @param args [Array<Object>] The arguments passed to the method
|
128
|
-
# being invoked.
|
129
|
-
def apply_protect method_policy, method, exception, object, args
|
130
|
-
return unless ::Contrast::AGENT.enabled?
|
131
|
-
return unless ::Contrast::PROTECT.enabled?
|
132
|
-
|
133
|
-
apply_trigger_only(method_policy&.protect_node, method, exception, object, args)
|
134
|
-
end
|
135
|
-
|
136
|
-
# Apply the Inventory patch which applies to the given method.
|
137
|
-
#
|
138
|
-
# @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
|
139
|
-
# Mapping of the triggers on the given method.
|
140
|
-
# @param method [Symbol] The method into which we're patching
|
141
|
-
# @param exception [StandardError] Any exception raised during the
|
142
|
-
# call of the patched method.
|
143
|
-
# @param object [Object] The object on which the method is invoked,
|
144
|
-
# typically what would be returned by self.
|
145
|
-
# @param args [Array<Object>] The arguments passed to the method
|
146
|
-
# being invoked.
|
147
|
-
def apply_inventory method_policy, method, exception, object, args
|
148
|
-
return unless ::Contrast::INVENTORY.enabled?
|
149
|
-
|
150
|
-
apply_trigger_only(method_policy&.inventory_node, method, exception, object, args)
|
151
|
-
end
|
152
|
-
|
153
|
-
# Apply the Assess patches which apply to the given method.
|
154
|
-
#
|
155
|
-
# @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
|
156
|
-
# Mapping of the triggers on the given method.
|
157
|
-
# @param preshift [Contrast::Agent::Assess::PreShift] The capture
|
158
|
-
# of the state of the code just prior to the invocation of the
|
159
|
-
# patched method.
|
160
|
-
# @param object [Object] The object on which the method was
|
161
|
-
# invoked, typically what would be returned by self.
|
162
|
-
# @param ret [Object] The return of the method that was invoked.
|
163
|
-
# @param args [Array<Object>] The arguments passed to the method
|
164
|
-
# being invoked.
|
165
|
-
# @param block [Proc] The block passed to the method that was
|
166
|
-
# invoked.
|
167
|
-
def apply_assess method_policy, preshift, object, ret, args, block
|
168
|
-
source_ret = nil
|
169
|
-
propagated_ret = nil
|
170
|
-
return ret unless method_policy && ::Contrast::ASSESS.enabled?
|
171
|
-
|
172
|
-
current_context = Contrast::Agent::REQUEST_TRACKER.current
|
173
|
-
return ret if current_context && !current_context.analyze_request?
|
174
|
-
|
175
|
-
trigger_node = method_policy.trigger_node
|
176
|
-
if trigger_node
|
177
|
-
Contrast::Agent::Assess::Policy::TriggerMethod.apply_trigger_rule(trigger_node, object, ret, args)
|
178
|
-
end
|
179
|
-
if method_policy.source_node
|
180
|
-
# If we were given a frozen return, and it was the target of a
|
181
|
-
# source, and we have frozen sources enabled, we'll need to
|
182
|
-
# replace the return. Note, this is not the default case.
|
183
|
-
source_ret = Contrast::Agent::Assess::Policy::SourceMethod.source_patchers(method_policy, object, ret,
|
184
|
-
args)
|
185
|
-
end
|
186
|
-
if method_policy.propagation_node
|
187
|
-
propagated_ret = Contrast::Agent::Assess::Policy::PropagationMethod.apply_propagation(
|
188
|
-
method_policy,
|
189
|
-
preshift,
|
190
|
-
object,
|
191
|
-
source_ret || ret,
|
192
|
-
args,
|
193
|
-
block)
|
194
|
-
end
|
195
|
-
handle_return(propagated_ret, source_ret, ret)
|
196
|
-
rescue StandardError => e
|
197
|
-
logger.error('Unable to assess method call.', e)
|
198
|
-
handle_return(propagated_ret, source_ret, ret)
|
199
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
200
|
-
logger.error('Unable to assess method call.', e)
|
201
|
-
handle_return(propagated_ret, source_ret, ret)
|
202
|
-
raise e
|
203
|
-
end
|
204
|
-
|
205
|
-
# Generic invocation of the Inventory or Protect patch which apply
|
206
|
-
# to the given method.
|
207
|
-
#
|
208
|
-
# @param trigger_node [Contrast::Agent::Inventory::Policy::TriggerNode]
|
209
|
-
# Mapping of the specific trigger on the given method.
|
210
|
-
# @param method [Symbol] The method into which we're patching
|
211
|
-
# @param exception [StandardError] Any exception raised during the
|
212
|
-
# call of the patched method.
|
213
|
-
# @param object [Object] The object on which the method is invoked,
|
214
|
-
# typically what would be returned by self.
|
215
|
-
# @param args [Array<Object>] The arguments passed to the method
|
216
|
-
# being invoked.
|
217
|
-
def apply_trigger_only trigger_node, method, exception, object, args
|
218
|
-
return unless trigger_node
|
219
|
-
|
220
|
-
# If that rule only applies in the case of an exception being
|
221
|
-
# thrown and there's no exception here, move along, or vice versa
|
222
|
-
return if trigger_node.on_exception && !exception
|
223
|
-
return if !trigger_node.on_exception && exception
|
224
|
-
|
225
|
-
# Each patch has an applicator that handles logic for it. Think
|
226
|
-
# of this as being similar to propagator actions, most closely
|
227
|
-
# resembling CUSTOM - they all have a common interface but their
|
228
|
-
# own logic based on what's in the method(s) they've been patched
|
229
|
-
# into.
|
230
|
-
# Each patch also knows the method of its applicator. Some
|
231
|
-
# things, like AppliesXxeRule, have different methods depending
|
232
|
-
# on the library patched. This lets us handle the boilerplate of
|
233
|
-
# patching while still allowing for custom handling of the
|
234
|
-
# methods.
|
235
|
-
applicator_method = trigger_node.applicator_method
|
236
|
-
# By calling send like this, we can reuse all the patching.
|
237
|
-
# We `send` to the given method of the given class
|
238
|
-
# (applicator) since they all accept the same inputs
|
239
|
-
trigger_node.applicator.send(applicator_method, method, exception, trigger_node.properties, object, args)
|
240
|
-
end
|
241
|
-
|
242
|
-
# Method to choose which replaced return from the post_patch to
|
243
|
-
# actually return
|
244
|
-
#
|
245
|
-
# @param propagated_ret [Object, nil] The replaced return from the
|
246
|
-
# propagation patch.
|
247
|
-
# @param source_ret [Object, nil] The replaced return from the
|
248
|
-
# source patch.
|
249
|
-
# @param ret [Object, nil] The original return of the patched
|
250
|
-
# method.
|
251
|
-
# @return [Object, nil] The thing to return from the post patch.
|
252
|
-
def handle_return propagated_ret, source_ret, ret
|
253
|
-
safe_return = propagated_ret || source_ret || ret
|
254
|
-
safe_return.rewind if Contrast::Utils::IOUtil.should_rewind?(safe_return)
|
255
|
-
safe_return
|
256
|
-
end
|
257
|
-
|
258
|
-
# Given a module and method, construct an expected name for the
|
259
|
-
# alias by which Contrast will reference the original.
|
260
|
-
#
|
261
|
-
# @param patched_class [Module] the module being patched
|
262
|
-
# @param patched_method [Symbol] the method being patched
|
263
|
-
# @return [Symbol]
|
264
|
-
def build_method_name patched_class, patched_method
|
265
|
-
(Contrast::Utils::ObjectShare::CONTRAST_PATCHED_METHOD_START +
|
266
|
-
patched_class.cs__name.gsub('::', '_').downcase +
|
267
|
-
Contrast::Utils::ObjectShare::UNDERSCORE +
|
268
|
-
patched_method.to_s).to_sym
|
269
|
-
end
|
270
|
-
|
271
|
-
# Given a method, return a symbol in the format
|
272
|
-
# <method_start>_unbound_<method_name>
|
273
|
-
def build_unbound_method_name patcher_method
|
274
|
-
(Contrast::Utils::ObjectShare::CONTRAST_PATCHED_METHOD_START +
|
275
|
-
'unbound' +
|
276
|
-
Contrast::Utils::ObjectShare::UNDERSCORE +
|
277
|
-
patcher_method.to_s).to_sym
|
278
|
-
end
|
279
|
-
|
280
63
|
# @param mod [Module] the module in which the patch should be
|
281
64
|
# placed.
|
282
65
|
# @param methods [Array(Symbol)] all the instance or singleton
|
@@ -337,7 +120,7 @@ module Contrast
|
|
337
120
|
# :prepend -> prepend instance method of module
|
338
121
|
# [prepending singleton is easily supported too, just not implemented yet.]
|
339
122
|
# @return [Symbol] new alias for the underlying method (presumably, so the patched method can call it)
|
340
|
-
def register_c_patch target_module_name, unbound_method, impl = :alias_instance
|
123
|
+
def register_c_patch target_module_name, unbound_method, impl = :alias_instance
|
341
124
|
# These could be set as AfterLoadPatches.
|
342
125
|
method_name = unbound_method.name.to_sym # rubocop:disable Security/Module/Name -- ruby built in attribute.
|
343
126
|
underlying_method_name = build_unbound_method_name(method_name).to_sym
|
@@ -360,6 +143,30 @@ module Contrast
|
|
360
143
|
ERR
|
361
144
|
end
|
362
145
|
|
146
|
+
reflect_implementation impl, target_module, unbound_method, visibility
|
147
|
+
# Ougai::Logger.create_item_with_2args calls Hash#[]=, so we
|
148
|
+
# can't invoke this logging method or we'll seg fault as we'd
|
149
|
+
# change the method definition mid-call
|
150
|
+
# if method_name != :[]= &&
|
151
|
+
# Contrast::Agent::Logger.defined!
|
152
|
+
# logger.trace(
|
153
|
+
# 'Registered C-defined patch',
|
154
|
+
# implementation: impl,
|
155
|
+
# module: target_mod,
|
156
|
+
# method: method_name,
|
157
|
+
# visibility: visibility)
|
158
|
+
# end
|
159
|
+
underlying_method_name
|
160
|
+
end
|
161
|
+
|
162
|
+
# @param impl [Symbol] Strategy for applying the patch: { :alias_instance, :alias_singleton, or :prepend }:
|
163
|
+
# @param target_module [Module] The targeted module
|
164
|
+
# @param unbound_method [UnboundMethod] An unbound method, to be patched into target_module.
|
165
|
+
# @param visibility [Symbol] method visibility
|
166
|
+
def reflect_implementation impl, target_module, unbound_method, visibility
|
167
|
+
method_name = unbound_method.name.to_sym # rubocop:disable Security/Module/Name -- ruby built in attribute.
|
168
|
+
underlying_method_name = build_unbound_method_name(method_name).to_sym
|
169
|
+
|
363
170
|
case impl
|
364
171
|
when :alias_instance, :alias_singleton
|
365
172
|
# Core to patching. Ignore define method usage cop.
|
@@ -383,20 +190,6 @@ module Contrast
|
|
383
190
|
target_module.prepend prepending_module
|
384
191
|
# rubocop:enable Performance/Kernel/DefineMethod
|
385
192
|
end
|
386
|
-
|
387
|
-
# Ougai::Logger.create_item_with_2args calls Hash#[]=, so we
|
388
|
-
# can't invoke this logging method or we'll seg fault as we'd
|
389
|
-
# change the method definition mid-call
|
390
|
-
# if method_name != :[]= &&
|
391
|
-
# Contrast::Agent::Logger.defined!
|
392
|
-
# logger.trace(
|
393
|
-
# 'Registered C-defined patch',
|
394
|
-
# implementation: impl,
|
395
|
-
# module: target_module_name,
|
396
|
-
# method: method_name,
|
397
|
-
# visibility: visibility)
|
398
|
-
# end
|
399
|
-
underlying_method_name
|
400
193
|
end
|
401
194
|
|
402
195
|
# @return [Boolean]
|
@@ -10,6 +10,7 @@ require 'contrast/agent/patching/policy/module_policy'
|
|
10
10
|
require 'contrast/components/logger'
|
11
11
|
require 'contrast/components/scope'
|
12
12
|
require 'contrast/utils/class_util'
|
13
|
+
require 'contrast/utils/patching/policy/patcher_utils'
|
13
14
|
|
14
15
|
# assess
|
15
16
|
require 'contrast/agent/assess/policy/policy'
|
@@ -44,6 +45,7 @@ module Contrast
|
|
44
45
|
# and how.
|
45
46
|
module Patcher
|
46
47
|
extend Contrast::Agent::Patching::Policy::AfterLoadPatcher
|
48
|
+
extend Contrast::Utils::Patching::PatcherUtils
|
47
49
|
extend Contrast::Components::Logger::InstanceMethods
|
48
50
|
extend Contrast::Components::Scope::InstanceMethods
|
49
51
|
|
@@ -77,47 +79,6 @@ module Contrast
|
|
77
79
|
end
|
78
80
|
end
|
79
81
|
|
80
|
-
# This method is called by TracePointHook to instrument a specific class during a require
|
81
|
-
# or eval of dynamic class definition
|
82
|
-
def patch_specific_module mod
|
83
|
-
with_contrast_scope do
|
84
|
-
mod_name = mod.cs__name
|
85
|
-
return unless Contrast::Utils::ClassUtil.truly_defined?(mod_name)
|
86
|
-
return if ::Contrast::AGENT.skip_instrumentation?(mod_name)
|
87
|
-
|
88
|
-
load_patches_for_module(mod_name)
|
89
|
-
|
90
|
-
return if all_module_names.none?(mod_name)
|
91
|
-
|
92
|
-
module_data = Contrast::Agent::ModuleData.new(mod, mod_name)
|
93
|
-
patch_into_module(module_data)
|
94
|
-
all_module_names.delete(mod_name) if status_type.get_status(mod).patched?
|
95
|
-
rescue StandardError => e
|
96
|
-
logger.error('Unable to patch module', e, module: mod_name)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# We did it, team. We found a patcher(s) that applies to the given
|
101
|
-
# class (or module) and the given method. Time to do some tracking.
|
102
|
-
#
|
103
|
-
# @param mod [Module] the module in which the patch should be
|
104
|
-
# placed.
|
105
|
-
# @param methods [Array(Symbol)] all the instance or singleton
|
106
|
-
# methods in this mod.
|
107
|
-
# @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
|
108
|
-
# the policy that applies to the given method_name.
|
109
|
-
# @return [Boolean] if patched, either by this invocation or a
|
110
|
-
# previous, or not
|
111
|
-
def patch_method mod, methods, method_policy
|
112
|
-
return false unless methods&.any? # don't even build the name if there are no methods
|
113
|
-
|
114
|
-
if Contrast::Utils::ClassUtil.prepended_method?(mod, method_policy)
|
115
|
-
Contrast::Agent::Patching::Policy::Patch.instrument_with_prepend(mod, method_policy)
|
116
|
-
else
|
117
|
-
Contrast::Agent::Patching::Policy::Patch.instrument_with_alias(mod, methods, method_policy)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
82
|
private
|
122
83
|
|
123
84
|
POLICIES = [
|
@@ -6,19 +6,23 @@ require 'contrast/components/scope'
|
|
6
6
|
|
7
7
|
module Contrast
|
8
8
|
module Agent
|
9
|
-
# This class is instantiated when we receive a request and the agent is enabled to process
|
10
|
-
#
|
11
|
-
# prefilter and postfilter).
|
9
|
+
# This class is instantiated when we receive a request and the agent is enabled to process that request. It holds
|
10
|
+
# the ruleset that we perform filtering operations on (currently prefilter and postfilter).
|
12
11
|
class RequestHandler
|
13
12
|
include Contrast::Components::Logger::InstanceMethods
|
14
13
|
|
15
14
|
attr_reader :ruleset, :context
|
16
15
|
|
16
|
+
# @param context [Contrast::Agent::RequestContext] the context of the request for which this handler applies
|
17
17
|
def initialize context
|
18
18
|
@context = context
|
19
19
|
@ruleset = ::Contrast::AGENT.ruleset
|
20
20
|
end
|
21
21
|
|
22
|
+
# TODO: RUBY-1353
|
23
|
+
# TODO: RUBY-1355
|
24
|
+
# TODO: RUBY-1357
|
25
|
+
# TODO: RUBY-1358
|
22
26
|
def send_activity_messages
|
23
27
|
Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage(context.activity)
|
24
28
|
[context.server_activity, context.activity, context.observed_route].each do |message|
|