contrast-agent 4.12.0 → 4.14.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +1 -0
  3. data/ext/cs__assess_module/cs__assess_module.c +48 -0
  4. data/ext/cs__assess_module/cs__assess_module.h +7 -0
  5. data/ext/cs__common/cs__common.c +5 -0
  6. data/ext/cs__common/cs__common.h +8 -0
  7. data/ext/cs__contrast_patch/cs__contrast_patch.c +16 -1
  8. data/ext/cs__os_information/cs__os_information.c +31 -0
  9. data/ext/cs__os_information/cs__os_information.h +7 -0
  10. data/ext/cs__os_information/extconf.rb +5 -0
  11. data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
  12. data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
  13. data/lib/contrast/agent/assess/policy/propagation_method.rb +2 -116
  14. data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
  15. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
  16. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
  17. data/lib/contrast/agent/assess/policy/source_method.rb +2 -71
  18. data/lib/contrast/agent/assess/policy/trigger_method.rb +45 -110
  19. data/lib/contrast/agent/assess/policy/trigger_node.rb +14 -6
  20. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
  21. data/lib/contrast/agent/assess/property/tagged.rb +53 -185
  22. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
  23. data/lib/contrast/agent/deadzone/policy/policy.rb +1 -1
  24. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -0
  25. data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
  26. data/lib/contrast/agent/middleware.rb +14 -62
  27. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -89
  28. data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
  29. data/lib/contrast/agent/patching/policy/patch.rb +28 -235
  30. data/lib/contrast/agent/patching/policy/patcher.rb +14 -49
  31. data/lib/contrast/agent/reporting/report.rb +21 -0
  32. data/lib/contrast/agent/reporting/reporter.rb +142 -0
  33. data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
  34. data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
  35. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
  36. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
  37. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
  38. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
  39. data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
  40. data/lib/contrast/agent/request.rb +2 -81
  41. data/lib/contrast/agent/request_context.rb +4 -128
  42. data/lib/contrast/agent/request_context_extend.rb +138 -0
  43. data/lib/contrast/agent/request_handler.rb +7 -3
  44. data/lib/contrast/agent/response.rb +2 -73
  45. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +94 -0
  46. data/lib/contrast/agent/static_analysis.rb +5 -3
  47. data/lib/contrast/agent/telemetry.rb +137 -0
  48. data/lib/contrast/agent/telemetry_event.rb +33 -0
  49. data/lib/contrast/agent/thread_watcher.rb +66 -11
  50. data/lib/contrast/agent/version.rb +1 -1
  51. data/lib/contrast/agent.rb +21 -0
  52. data/lib/contrast/api/communication/connection_status.rb +10 -7
  53. data/lib/contrast/api/communication/messaging_queue.rb +37 -3
  54. data/lib/contrast/api/communication/response_processor.rb +15 -8
  55. data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
  56. data/lib/contrast/api/communication/socket.rb +6 -8
  57. data/lib/contrast/api/communication/socket_client.rb +29 -12
  58. data/lib/contrast/api/communication/speedracer.rb +37 -1
  59. data/lib/contrast/api/communication/tcp_socket.rb +4 -3
  60. data/lib/contrast/api/communication/unix_socket.rb +1 -0
  61. data/lib/contrast/api/decorators/finding.rb +45 -0
  62. data/lib/contrast/components/api.rb +90 -0
  63. data/lib/contrast/components/app_context.rb +10 -41
  64. data/lib/contrast/components/app_context_extend.rb +78 -0
  65. data/lib/contrast/components/base.rb +23 -0
  66. data/lib/contrast/components/config.rb +92 -13
  67. data/lib/contrast/components/contrast_service.rb +11 -0
  68. data/lib/contrast/components/sampling.rb +2 -2
  69. data/lib/contrast/config/agent_configuration.rb +1 -1
  70. data/lib/contrast/config/api_configuration.rb +27 -0
  71. data/lib/contrast/config/api_proxy_configuration.rb +14 -0
  72. data/lib/contrast/config/application_configuration.rb +2 -3
  73. data/lib/contrast/config/assess_configuration.rb +3 -3
  74. data/lib/contrast/config/base_configuration.rb +17 -28
  75. data/lib/contrast/config/certification_configuration.rb +15 -0
  76. data/lib/contrast/config/env_variables.rb +18 -0
  77. data/lib/contrast/config/heap_dump_configuration.rb +6 -6
  78. data/lib/contrast/config/inventory_configuration.rb +1 -5
  79. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  80. data/lib/contrast/config/request_audit_configuration.rb +18 -0
  81. data/lib/contrast/config/root_configuration.rb +1 -0
  82. data/lib/contrast/config/ruby_configuration.rb +6 -6
  83. data/lib/contrast/config/service_configuration.rb +2 -2
  84. data/lib/contrast/config.rb +1 -1
  85. data/lib/contrast/configuration.rb +4 -2
  86. data/lib/contrast/extension/assess/array.rb +5 -7
  87. data/lib/contrast/extension/thread.rb +31 -12
  88. data/lib/contrast/framework/manager.rb +22 -44
  89. data/lib/contrast/framework/manager_extend.rb +50 -0
  90. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
  91. data/lib/contrast/framework/rails/patch/support.rb +31 -29
  92. data/lib/contrast/framework/rails/railtie.rb +1 -1
  93. data/lib/contrast/framework/sinatra/support.rb +2 -1
  94. data/lib/contrast/logger/application.rb +4 -0
  95. data/lib/contrast/logger/log.rb +8 -103
  96. data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
  97. data/lib/contrast/utils/assess/property/tagged_utils.rb +165 -0
  98. data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
  99. data/lib/contrast/utils/assess/tracking_util.rb +20 -15
  100. data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
  101. data/lib/contrast/utils/class_util.rb +18 -14
  102. data/lib/contrast/utils/exclude_key.rb +20 -0
  103. data/lib/contrast/utils/findings.rb +62 -0
  104. data/lib/contrast/utils/hash_digest.rb +10 -73
  105. data/lib/contrast/utils/hash_digest_extend.rb +86 -0
  106. data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
  107. data/lib/contrast/utils/heap_dump_util.rb +2 -65
  108. data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
  109. data/lib/contrast/utils/io_util.rb +1 -1
  110. data/lib/contrast/utils/log_utils.rb +108 -0
  111. data/lib/contrast/utils/metrics_hash.rb +59 -0
  112. data/lib/contrast/utils/middleware_utils.rb +87 -0
  113. data/lib/contrast/utils/net_http_base.rb +158 -0
  114. data/lib/contrast/utils/object_share.rb +1 -0
  115. data/lib/contrast/utils/os.rb +23 -0
  116. data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
  117. data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
  118. data/lib/contrast/utils/request_utils.rb +88 -0
  119. data/lib/contrast/utils/response_utils.rb +97 -0
  120. data/lib/contrast/utils/substitution_utils.rb +167 -0
  121. data/lib/contrast/utils/tag_util.rb +9 -9
  122. data/lib/contrast/utils/telemetry.rb +79 -0
  123. data/lib/contrast/utils/telemetry_client.rb +90 -0
  124. data/lib/contrast/utils/telemetry_identifier.rb +130 -0
  125. data/lib/contrast.rb +18 -0
  126. data/ruby-agent.gemspec +7 -6
  127. data/service_executables/VERSION +1 -1
  128. data/service_executables/linux/contrast-service +0 -0
  129. data/service_executables/mac/contrast-service +0 -0
  130. metadata +69 -22
  131. data/lib/contrast/config/default_value.rb +0 -17
@@ -4,6 +4,7 @@
4
4
  require 'contrast/agent/assess/policy/trigger_method'
5
5
  require 'contrast/components/logger'
6
6
  require 'contrast/extension/module'
7
+ require 'contrast/agent/reporting/report'
7
8
 
8
9
  module Contrast
9
10
  module Agent
@@ -66,7 +67,8 @@ module Contrast
66
67
  # if it looks like a placeholder / pointer to a config, skip it
67
68
  next unless value_passes?(value)
68
69
 
69
- build_finding(clazz, constant_string)
70
+ new_finding_and_reporting clazz, constant_string
71
+ build_finding clazz, constant_string
70
72
  end
71
73
  end
72
74
 
@@ -138,7 +140,8 @@ module Contrast
138
140
  # sort. We leave it to each rule to properly handle these nodes.
139
141
  return unless value_node_passes?(value)
140
142
 
141
- build_finding(mod, name)
143
+ new_finding_and_reporting mod, name
144
+ build_finding mod, name
142
145
  end
143
146
 
144
147
  # Constants can be set as frozen directly. We need to account for
@@ -161,6 +164,14 @@ module Contrast
161
164
  def build_finding clazz, constant_string
162
165
  class_name = clazz.cs__name
163
166
 
167
+ finding = assign_finding class_name, constant_string
168
+ Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding)
169
+ rescue StandardError => e
170
+ logger.error('Unable to build a finding for Hardcoded Rule', e)
171
+ nil
172
+ end
173
+
174
+ def assign_finding class_name, constant_string
164
175
  finding = Contrast::Api::Dtm::Finding.new
165
176
  finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(rule_id)
166
177
  finding.version = Contrast::Agent::Assess::Policy::TriggerMethod::CURRENT_FINDING_VERSION
@@ -173,10 +184,33 @@ module Contrast
173
184
  hash = Contrast::Utils::HashDigest.generate_class_scanning_hash(finding)
174
185
  finding.hash_code = Contrast::Utils::StringUtils.protobuf_safe_string(hash)
175
186
  finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
176
- Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding)
177
- rescue StandardError => e
178
- logger.error('Unable to build a finding for Hardcoded Rule', e)
179
- nil
187
+ finding
188
+ end
189
+
190
+ def new_finding_and_reporting clazz, constant_string
191
+ return unless Contrast::Agent::Reporter.enabled?
192
+
193
+ # sent to reporter
194
+ # and add logger message for the report of the preflight
195
+ new_preflight = Contrast::Agent::Reporting::Preflight.new
196
+ new_preflight_message = Contrast::Agent::Reporting::PreflightMessage.new
197
+ new_preflight_message.hash_code = hash
198
+ new_preflight_message.data = "#{ rule_id },#{ hash }"
199
+ new_preflight.messages << new_preflight_message
200
+
201
+ # extract to new method
202
+ # here we will generate new type of finding
203
+ ruby_finding = Contrast::Agent::Reporting::Finding.new rule_id
204
+ ruby_finding.hash_code = hash
205
+ ruby_finding.properties[SOURCE_KEY] = clazz.cs__name
206
+ ruby_finding.properties[CONSTANT_NAME_KEY] = constant_string
207
+ ruby_finding.properties[CODE_SOURCE_KEY] = constant_string + redacted_marker
208
+ save_and_report_finding ruby_finding, new_preflight
209
+ end
210
+
211
+ def save_and_report_finding ruby_finding, new_preflight
212
+ Contrast::Agent::Reporting::ReportingStorage[hash] = ruby_finding
213
+ Contrast::Agent.reporter_queue.send_event_immediately(new_preflight)
180
214
  end
181
215
  end
182
216
  end
@@ -38,7 +38,7 @@ module Contrast
38
38
  def validate
39
39
  return if class_name
40
40
 
41
- raise(ArgumentError, "#{ @node_class } #{ id } did not have a proper class name. Unable to create.")
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['_filler'] = 0
19
+ end
20
+
21
+ def to_hash **_args
22
+ super.merge!({ fields: @fields })
23
+ end
24
+ end
25
+ end
26
+ end
@@ -10,8 +10,11 @@ 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'
17
+ require 'contrast/utils/middleware_utils'
15
18
 
16
19
  require 'contrast/utils/timer'
17
20
 
@@ -23,6 +26,7 @@ module Contrast
23
26
  class Middleware
24
27
  include Contrast::Components::Logger::InstanceMethods
25
28
  include Contrast::Components::Scope::InstanceMethods
29
+ include Contrast::Utils::MiddlewareUtils
26
30
 
27
31
  attr_reader :app
28
32
 
@@ -64,21 +68,6 @@ module Contrast
64
68
 
65
69
  private
66
70
 
67
- def setup_agent
68
- ::Contrast::SETTINGS.reset_state
69
-
70
- inform_deprecations
71
-
72
- if ::Contrast::CONFIG.invalid?
73
- ::Contrast::AGENT.disable!
74
- logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
75
- elsif ::Contrast::AGENT.disabled?
76
- logger.warn('Contrast disabled by configuration. Continuing without instrumentation.')
77
- else
78
- ::Contrast::AGENT.enable!
79
- end
80
- end
81
-
82
71
  # Startup the Agent as part of the initialization process:
83
72
  # - start the service sending thread, responsible for sending and processing messages
84
73
  # - start the heartbeat thread, which triggers service startup
@@ -89,6 +78,13 @@ module Contrast
89
78
  Contrast::Agent.thread_watcher.ensure_running?
90
79
  end
91
80
 
81
+ if Contrast::Agent::Telemetry.enabled?
82
+ logger.debug_with_time('middleware: sending startup metrics telemetry event') do
83
+ event = Contrast::Agent::StartupMetricsTelemetryEvent.new
84
+ Contrast::Agent.thread_watcher.telemetry_queue.send_event(event)
85
+ end
86
+ end
87
+
92
88
  logger.debug_with_time('middleware: instrument shared libraries and patch') do
93
89
  Contrast::Agent::Patching::Policy::Patcher.patch
94
90
  end
@@ -165,6 +161,9 @@ module Contrast
165
161
  with_contrast_scope do
166
162
  context.extract_after(response) # update context with final response information
167
163
 
164
+ # Build and report all collected findings prior response
165
+ Contrast::Agent::FINDINGS.report_collected_findings unless Contrast::Agent::FINDINGS.collection.empty?
166
+
168
167
  if Contrast::Agent.framework_manager.streaming?(env)
169
168
  context.reset_activity
170
169
  request_handler.stream_safe_postfilter
@@ -178,53 +177,6 @@ module Contrast
178
177
 
179
178
  logger.error('Unable to execute agent post_call', e)
180
179
  end
181
-
182
- def application_code env
183
- logger.trace_with_time('application') do
184
- app.call(env)
185
- end
186
- rescue Contrast::SecurityException => e
187
- logger.trace('Security Exception raised during application lifecycle to prevent an attack', e)
188
- raise e
189
- end
190
-
191
- SECURITY_EXCEPTION_MARKER = 'Contrast::SecurityException'
192
- # We're only going to suppress SecurityExceptions indicating a blocked attack. And, only if the
193
- # config.agent.ruby.exceptions.capture? is set
194
- def handle_exception exception
195
- if security_exception?(exception)
196
- exception_control = ::Contrast::AGENT.exception_control
197
- raise exception unless exception_control[:enable]
198
-
199
- [exception_control[:status], {}, [exception_control[:message]]]
200
- else
201
- logger.debug('Re-throwing original error', exception)
202
- raise exception
203
- end
204
- end
205
-
206
- # Is the given exception one raised by our Protect code?
207
- #
208
- # @param exception [Exception]
209
- # @return [Boolean]
210
- def security_exception? exception
211
- exception.is_a?(Contrast::SecurityException) || exception.message&.include?(SECURITY_EXCEPTION_MARKER)
212
- end
213
-
214
- # As we deprecate support to prepare to remove dead code, we need to inform our users still relying on the now
215
- # deprecated and soon to be removed functionality. This method handles doing that by leveraging the standard
216
- # Kernel#warn approach
217
- def inform_deprecations
218
- # Ruby 2.5 is currently in security maintenance, meaning int is only receiving updates for security issues. It
219
- # will move to eol on 31 March 2021. As such, we can remove support for it in Q3. We'll begin the deprecation
220
- # warnings now so that customers have time to reach out if they'll be impacted.
221
- # TODO: RUBY-715 remove this part of the method, leaving it empty if there are no other deprecations, when we
222
- # drop 2.5 support.
223
- return unless RUBY_VERSION < '2.6.0'
224
-
225
- Kernel.warn('[Contrast Security] [DEPRECATION] Support for Ruby 2.5 will be removed in April 2021. '\
226
- 'Please contact Customer Support prior if you require continued support.')
227
- end
228
180
  end
229
181
  end
230
182
  end
@@ -1,12 +1,15 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'contrast/agent/patching/policy/method_policy_extend'
5
+
4
6
  module Contrast
5
7
  module Agent
6
8
  module Patching
7
9
  module Policy
8
10
  # This class is used to map each method to the trigger node that applies to it
9
11
  class MethodPolicy
12
+ extend Contrast::Agent::Patching::Policy::MethodPolicyExtend
10
13
  attr_reader :deadzone_node, :inventory_node, :propagation_node, :protect_node, :trigger_node
11
14
  attr_accessor :source_node, :method_name, :method_visibility, :instance_method
12
15
 
@@ -60,95 +63,6 @@ module Contrast
60
63
  # defined by #nodes.
61
64
  @_method_scopes ||= nodes.flat_map(&:method_scope).tap(&:compact!).tap(&:uniq!)
62
65
  end
63
-
64
- class << self
65
- # Given a Contrast::Agent::Patching::Policy::ModulePolicy, parse
66
- # out its information for the given method in order to construct a
67
- # Contrast::Agent::Patching::Policy::MethodPolicy
68
- #
69
- # @param method_name [Symbol] the name of the method for this policy
70
- # @param module_policy [Contrast::Agent::Patching::Policy::ModulePolicy]
71
- # the entire policy for this module
72
- # @param instance_method [Boolean] true if this method is an
73
- # instance method
74
- # @return [Contrast::Agent::Patching::Policy::MethodPolicy]
75
- def build_method_policy method_name, module_policy, instance_method
76
- source_node = find_method_node(module_policy.source_nodes, method_name, instance_method)
77
- propagation_node = find_method_node(module_policy.propagator_nodes, method_name, instance_method)
78
- trigger_node = find_method_node(module_policy.trigger_nodes, method_name, instance_method)
79
- protect_node = find_method_node(module_policy.protect_nodes, method_name, instance_method)
80
- inventory_node = find_method_node(module_policy.inventory_nodes, method_name, instance_method)
81
- deadzone_node = find_method_node(module_policy.deadzone_nodes, method_name, instance_method)
82
- method_visibility = find_visibility(source_node, propagation_node, trigger_node, protect_node,
83
- inventory_node, deadzone_node)
84
- method_policy = MethodPolicy.new(method_name: method_name,
85
- method_visibility: method_visibility,
86
- instance_method: instance_method,
87
- source_node: source_node,
88
- propagation_node: propagation_node,
89
- trigger_node: trigger_node,
90
- protect_node: protect_node,
91
- inventory_node: inventory_node,
92
- deadzone_node: deadzone_node)
93
-
94
- return method_policy unless check_method_policy_nodes_empty? source_node, propagation_node, trigger_node,
95
- protect_node, inventory_node, deadzone_node
96
-
97
- create_new_node(module_policy, method_policy) if module_policy.deadzone_nodes&.any?
98
- method_policy
99
- end
100
-
101
- def find_method_node nodes, method_name, is_instance_method
102
- return unless nodes
103
-
104
- nodes.find do |node|
105
- node.instance_method? == is_instance_method && node.method_name == method_name
106
- end
107
- end
108
-
109
- def find_visibility *nodes
110
- nodes.find { |node| node }&.method_visibility
111
- end
112
-
113
- def check_method_policy_nodes_empty?(source_node, propagation_node, trigger_node, protect_node,
114
- inventory_node, deadzone_node)
115
- return false unless source_node.nil? && propagation_node.nil? && trigger_node.nil? && protect_node.nil? &&
116
- inventory_node.nil? && deadzone_node.nil?
117
-
118
- true
119
- end
120
-
121
- private
122
-
123
- def create_new_node module_policy, method_policy
124
- return if module_policy.deadzone_nodes.empty?
125
-
126
- module_policy.deadzone_nodes.map do |node|
127
- next unless node.method_name.nil?
128
-
129
- klass = Module.cs__const_get(node.class_name)
130
- next unless it_defined? klass, method_policy.method_name
131
-
132
- new_node = {}
133
- new_node['instance_method'] = method_policy.instance_method
134
- new_node['method_visibility'] =
135
- klass.private_method_defined?(method_policy.method_name) ? 'private' : 'public'
136
- new_node['method_name'] = method_policy.method_name
137
- new_node['class_name'] = node.class_name
138
- new_node = Contrast::Agent::Deadzone::Policy::DeadzoneNode.new(new_node)
139
- method_policy.instance_variable_set(:@method_visibility, new_node.method_visibility)
140
- method_policy.instance_variable_set(:@deadzone_node, new_node)
141
- module_policy.deadzone_nodes << new_node
142
- break unless method_policy.deadzone_node.nil?
143
- end
144
- end
145
-
146
- def it_defined? klass, method_name
147
- klass.instance_methods(false).include?(method_name) ||
148
- klass.private_instance_methods(false).include?(method_name) ||
149
- klass.singleton_methods(false).include?(method_name)
150
- end
151
- end
152
66
  end
153
67
  end
154
68
  end
@@ -0,0 +1,111 @@
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
+ module Contrast
5
+ module Agent
6
+ module Patching
7
+ module Policy
8
+ # This class is used to map each method to the trigger node that applies to it
9
+ module MethodPolicyExtend
10
+ # Given a Contrast::Agent::Patching::Policy::ModulePolicy, parse
11
+ # out its information for the given method in order to construct a
12
+ # Contrast::Agent::Patching::Policy::MethodPolicy
13
+ #
14
+ # @param method_name [Symbol] the name of the method for this policy
15
+ # @param module_policy [Contrast::Agent::Patching::Policy::ModulePolicy]
16
+ # the entire policy for this module
17
+ # @param instance_method [Boolean] true if this method is an
18
+ # instance method
19
+ # @return [Contrast::Agent::Patching::Policy::MethodPolicy]
20
+ def build_method_policy method_name, module_policy, instance_method
21
+ source_node = find_method_node(module_policy.source_nodes, method_name, instance_method)
22
+ propagation_node = find_method_node(module_policy.propagator_nodes, method_name, instance_method)
23
+ trigger_node = find_method_node(module_policy.trigger_nodes, method_name, instance_method)
24
+ protect_node = find_method_node(module_policy.protect_nodes, method_name, instance_method)
25
+ inventory_node = find_method_node(module_policy.inventory_nodes, method_name, instance_method)
26
+ deadzone_node = find_method_node(module_policy.deadzone_nodes, method_name, instance_method)
27
+ method_visibility = find_visibility(source_node, propagation_node, trigger_node, protect_node,
28
+ inventory_node, deadzone_node)
29
+ method_policy = MethodPolicy.new(method_name: method_name,
30
+ method_visibility: method_visibility,
31
+ instance_method: instance_method,
32
+ source_node: source_node,
33
+ propagation_node: propagation_node,
34
+ trigger_node: trigger_node,
35
+ protect_node: protect_node,
36
+ inventory_node: inventory_node,
37
+ deadzone_node: deadzone_node)
38
+
39
+ return method_policy unless check_method_policy_nodes_empty? source_node, propagation_node, trigger_node,
40
+ protect_node, inventory_node, deadzone_node
41
+
42
+ create_new_node(module_policy, method_policy) if module_policy.deadzone_nodes&.any?
43
+ method_policy
44
+ end
45
+
46
+ def find_method_node nodes, method_name, is_instance_method
47
+ return unless nodes
48
+
49
+ nodes.find do |node|
50
+ node.instance_method? == is_instance_method && node.method_name == method_name
51
+ end
52
+ end
53
+
54
+ def find_visibility *nodes
55
+ nodes.find { |node| node }&.method_visibility
56
+ end
57
+
58
+ def check_method_policy_nodes_empty?(source_node, propagation_node, trigger_node, protect_node,
59
+ inventory_node, deadzone_node)
60
+ return false unless source_node.nil? && propagation_node.nil? && trigger_node.nil? && protect_node.nil? &&
61
+ inventory_node.nil? && deadzone_node.nil?
62
+
63
+ true
64
+ end
65
+
66
+ private
67
+
68
+ def create_new_node module_policy, method_policy
69
+ return if module_policy.deadzone_nodes.empty?
70
+
71
+ module_policy.deadzone_nodes.map do |node|
72
+ next unless node.method_name.nil?
73
+
74
+ klass = Module.cs__const_get(node.class_name)
75
+ next unless it_defined? klass, method_policy.method_name
76
+
77
+ new_node = set_new_node method_policy, klass, node
78
+ method_policy.instance_variable_set(:@method_visibility, new_node.method_visibility)
79
+ method_policy.instance_variable_set(:@deadzone_node, node)
80
+ module_policy.deadzone_nodes << new_node
81
+ break unless method_policy.deadzone_node.nil?
82
+ end
83
+ end
84
+
85
+ # Helper method for creating new node
86
+ #
87
+ # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
88
+ # used to map each method to the trigger node that applies to it
89
+ # @param klass [String] classname
90
+ # @param node [Contrast::Agent::Patching::Policy::PolicyNode]
91
+ # @return @_set_new_node [Contrast::Agent::Deadzone::Policy::DeadzoneNode]
92
+ def set_new_node method_policy, klass, node
93
+ new_node = {}
94
+ new_node['instance_method'] = method_policy.instance_method
95
+ new_node['method_visibility'] =
96
+ klass.private_method_defined?(method_policy.method_name) ? 'private' : 'public'
97
+ new_node['method_name'] = method_policy.method_name
98
+ new_node['class_name'] = node.class_name
99
+ @_set_new_node = Contrast::Agent::Deadzone::Policy::DeadzoneNode.new(new_node)
100
+ end
101
+
102
+ def it_defined? klass, method_name
103
+ klass.instance_methods(false).include?(method_name) ||
104
+ klass.private_instance_methods(false).include?(method_name) ||
105
+ klass.singleton_methods(false).include?(method_name)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end