contrast-agent 3.14.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +18 -15
  3. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +1 -0
  4. data/ext/cs__assess_string/cs__assess_string.c +24 -25
  5. data/ext/cs__assess_string/cs__assess_string.h +3 -1
  6. data/ext/cs__common/cs__common.c +4 -2
  7. data/ext/cs__common/cs__common.h +1 -1
  8. data/lib/contrast.rb +1 -1
  9. data/lib/contrast/agent.rb +4 -12
  10. data/lib/contrast/agent/assess.rb +1 -0
  11. data/lib/contrast/agent/assess/contrast_event.rb +143 -79
  12. data/lib/contrast/agent/assess/events/source_event.rb +1 -1
  13. data/lib/contrast/agent/assess/finalizers/freeze.rb +3 -1
  14. data/lib/contrast/agent/assess/finalizers/hash.rb +45 -1
  15. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +10 -3
  16. data/lib/contrast/agent/assess/policy/patcher.rb +1 -1
  17. data/lib/contrast/agent/assess/policy/policy.rb +0 -2
  18. data/lib/contrast/agent/assess/policy/policy_node.rb +15 -10
  19. data/lib/contrast/agent/assess/policy/policy_scanner.rb +19 -3
  20. data/lib/contrast/agent/assess/policy/preshift.rb +7 -11
  21. data/lib/contrast/agent/assess/policy/propagation_method.rb +50 -33
  22. data/lib/contrast/agent/assess/policy/propagator/append.rb +8 -5
  23. data/lib/contrast/agent/assess/policy/propagator/base.rb +1 -1
  24. data/lib/contrast/agent/assess/policy/propagator/center.rb +9 -5
  25. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +5 -3
  26. data/lib/contrast/agent/assess/policy/propagator/insert.rb +7 -4
  27. data/lib/contrast/agent/assess/policy/propagator/keep.rb +4 -1
  28. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +4 -7
  29. data/lib/contrast/agent/assess/policy/propagator/next.rb +7 -5
  30. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +8 -5
  31. data/lib/contrast/agent/assess/policy/propagator/remove.rb +8 -4
  32. data/lib/contrast/agent/assess/policy/propagator/replace.rb +5 -2
  33. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +7 -5
  34. data/lib/contrast/agent/assess/policy/propagator/select.rb +13 -7
  35. data/lib/contrast/agent/assess/policy/propagator/splat.rb +10 -9
  36. data/lib/contrast/agent/assess/policy/propagator/split.rb +24 -19
  37. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +47 -31
  38. data/lib/contrast/agent/assess/policy/propagator/trim.rb +11 -5
  39. data/lib/contrast/agent/assess/policy/source_method.rb +85 -58
  40. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +16 -12
  41. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
  42. data/lib/contrast/agent/assess/policy/trigger_method.rb +76 -28
  43. data/lib/contrast/agent/assess/policy/trigger_node.rb +38 -43
  44. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +2 -1
  45. data/lib/contrast/agent/assess/properties.rb +2 -0
  46. data/lib/contrast/agent/assess/property/evented.rb +5 -18
  47. data/lib/contrast/agent/assess/property/tagged.rb +9 -3
  48. data/lib/contrast/agent/assess/property/updated.rb +131 -0
  49. data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +58 -5
  50. data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +23 -8
  51. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +83 -14
  52. data/lib/contrast/agent/assess/tag.rb +1 -1
  53. data/lib/contrast/agent/assess/tracker.rb +66 -0
  54. data/lib/contrast/agent/at_exit_hook.rb +5 -5
  55. data/lib/contrast/agent/class_reopener.rb +7 -5
  56. data/lib/contrast/agent/inventory.rb +15 -0
  57. data/lib/contrast/agent/inventory/dependencies.rb +50 -0
  58. data/lib/contrast/agent/inventory/dependency_analysis.rb +37 -0
  59. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +104 -0
  60. data/lib/contrast/agent/inventory/gemfile_digest_cache.rb +38 -0
  61. data/lib/contrast/agent/middleware.rb +1 -3
  62. data/lib/contrast/agent/patching/policy/after_load_patch.rb +5 -5
  63. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +20 -20
  64. data/lib/contrast/agent/patching/policy/module_policy.rb +10 -10
  65. data/lib/contrast/agent/patching/policy/patch.rb +6 -0
  66. data/lib/contrast/agent/patching/policy/patcher.rb +13 -22
  67. data/lib/contrast/agent/patching/policy/policy.rb +17 -6
  68. data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +3 -5
  69. data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +4 -3
  70. data/lib/contrast/agent/protect/policy/applies_xxe_rule.rb +1 -1
  71. data/lib/contrast/agent/protect/rule/cmd_injection.rb +9 -25
  72. data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +1 -0
  73. data/lib/contrast/agent/request.rb +34 -34
  74. data/lib/contrast/agent/request_handler.rb +1 -1
  75. data/lib/contrast/agent/response.rb +17 -6
  76. data/lib/contrast/agent/rewriter.rb +1 -3
  77. data/lib/contrast/agent/scope.rb +59 -53
  78. data/lib/contrast/agent/static_analysis.rb +7 -7
  79. data/lib/contrast/agent/tracepoint_hook.rb +1 -1
  80. data/lib/contrast/agent/version.rb +1 -1
  81. data/lib/contrast/api/communication/messaging_queue.rb +1 -4
  82. data/lib/contrast/api/communication/socket_client.rb +36 -1
  83. data/lib/contrast/api/decorators.rb +3 -0
  84. data/lib/contrast/api/decorators/address.rb +13 -14
  85. data/lib/contrast/api/decorators/application_update.rb +2 -4
  86. data/lib/contrast/api/decorators/library.rb +53 -0
  87. data/lib/contrast/api/decorators/library_usage_update.rb +30 -0
  88. data/lib/contrast/api/decorators/message.rb +1 -0
  89. data/lib/contrast/api/decorators/trace_event.rb +25 -23
  90. data/lib/contrast/common_agent_configuration.rb +2 -1
  91. data/lib/contrast/components/agent.rb +6 -5
  92. data/lib/contrast/components/app_context.rb +49 -38
  93. data/lib/contrast/components/config.rb +30 -48
  94. data/lib/contrast/components/contrast_service.rb +9 -9
  95. data/lib/contrast/components/interface.rb +25 -3
  96. data/lib/contrast/components/inventory.rb +6 -1
  97. data/lib/contrast/components/scope.rb +49 -6
  98. data/lib/contrast/components/settings.rb +23 -23
  99. data/lib/contrast/config/application_configuration.rb +5 -2
  100. data/lib/contrast/config/inventory_configuration.rb +2 -2
  101. data/lib/contrast/config/service_configuration.rb +8 -0
  102. data/lib/contrast/configuration.rb +88 -47
  103. data/lib/contrast/extension/assess.rb +0 -2
  104. data/lib/contrast/extension/assess/array.rb +15 -8
  105. data/lib/contrast/extension/assess/erb.rb +11 -3
  106. data/lib/contrast/extension/assess/eval_trigger.rb +6 -6
  107. data/lib/contrast/extension/assess/exec_trigger.rb +1 -4
  108. data/lib/contrast/extension/assess/fiber.rb +12 -12
  109. data/lib/contrast/extension/assess/hash.rb +5 -6
  110. data/lib/contrast/extension/assess/kernel.rb +28 -23
  111. data/lib/contrast/extension/assess/marshal.rb +11 -6
  112. data/lib/contrast/extension/assess/regexp.rb +8 -7
  113. data/lib/contrast/extension/assess/string.rb +21 -21
  114. data/lib/contrast/extension/protect/kernel.rb +3 -3
  115. data/lib/contrast/framework/base_support.rb +1 -1
  116. data/lib/contrast/framework/manager.rb +3 -3
  117. data/lib/contrast/framework/rack/patch/session_cookie.rb +22 -28
  118. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +13 -13
  119. data/lib/contrast/framework/rails/patch/assess_configuration.rb +5 -11
  120. data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +10 -10
  121. data/lib/contrast/framework/rails/patch/support.rb +1 -1
  122. data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +11 -11
  123. data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +12 -12
  124. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +3 -3
  125. data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +12 -12
  126. data/lib/contrast/framework/rails/support.rb +5 -0
  127. data/lib/contrast/framework/sinatra/patch/base.rb +11 -11
  128. data/lib/contrast/framework/sinatra/support.rb +4 -4
  129. data/lib/contrast/logger/application.rb +11 -3
  130. data/lib/contrast/logger/log.rb +7 -2
  131. data/lib/contrast/utils/assess/tracking_util.rb +48 -3
  132. data/lib/contrast/utils/duck_utils.rb +0 -10
  133. data/lib/contrast/utils/env_configuration_item.rb +2 -1
  134. data/lib/contrast/utils/invalid_configuration_util.rb +20 -21
  135. data/lib/contrast/utils/inventory_util.rb +0 -7
  136. data/lib/contrast/utils/sha256_builder.rb +0 -12
  137. data/lib/contrast/utils/string_utils.rb +10 -5
  138. data/resources/assess/policy.json +31 -22
  139. data/ruby-agent.gemspec +21 -18
  140. data/service_executables/VERSION +1 -1
  141. data/service_executables/linux/contrast-service +0 -0
  142. data/service_executables/mac/contrast-service +0 -0
  143. metadata +71 -30
  144. data/lib/contrast/agent/assess/finalizers/finalize.rb +0 -21
  145. data/lib/contrast/extension/assess/assess_extension.rb +0 -145
  146. data/lib/contrast/utils/boolean_util.rb +0 -30
  147. data/lib/contrast/utils/freeze_util.rb +0 -32
  148. data/lib/contrast/utils/gemfile_reader.rb +0 -193
@@ -21,7 +21,7 @@ module Contrast
21
21
  @request = Contrast::Agent::REQUEST_TRACKER.current&.request
22
22
  end
23
23
 
24
- def find_parent_ids _policy_node, _object, _ret, _args
24
+ def parent_events
25
25
  nil
26
26
  end
27
27
 
@@ -1,13 +1,15 @@
1
1
  # Copyright (c) 2020 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/assess/tracker'
5
+
4
6
  # Our patch of the Object#freeze method, allowing any Object we track to
5
7
  # function with our Contrast::Agent::Assess::Finalizers::Hash
6
8
  class Object
7
9
  alias_method :cs__patched_object_freeze, :freeze
8
10
 
9
11
  def freeze
10
- Contrast::Agent::Assess::Finalizers::Finalize::PROPERTIES_HASH.pre_freeze(self)
12
+ Contrast::Agent::Assess::Tracker.pre_freeze(self)
11
13
  cs__patched_object_freeze
12
14
  end
13
15
  end
@@ -29,6 +29,43 @@ module Contrast
29
29
  super key.__id__
30
30
  end
31
31
 
32
+ # Something is trackable if it is not a collection and either not
33
+ # frozen or it was frozen after we put a finalizer on it.
34
+ #
35
+ # @param key [Object] the thing to determine if trackable
36
+ # @return [Boolean]
37
+ def trackable? key
38
+ # Track things in these, not them themselves.
39
+ return false if Contrast::Utils::DuckUtils.iterable_hash?(key)
40
+ return false if Contrast::Utils::DuckUtils.iterable_enumerable?(key)
41
+ # If it's not frozen, we can finalize/ track it.
42
+ return true unless key.cs__frozen?
43
+
44
+ # Otherwise, we can only track it if we've finalized it in our
45
+ # freeze patch.
46
+ FROZEN_FINALIZED_IDS.include?(key.__id__)
47
+ end
48
+
49
+ # Determine if the given Object is tracked, meaning it has a known
50
+ # set of properties and those properties are tracked.
51
+ #
52
+ # @param key [Object] the Object whose properties, by id, we want to
53
+ # check for tracked status
54
+ # @return [Boolean]
55
+ def tracked? key
56
+ key?(key.__id__) && fetch(key.__id__, nil)&.tracked?
57
+ end
58
+
59
+ # Remove the given key from our frozen and properties tracking during
60
+ # finalization of the Object to which the given key_id pertains.
61
+ # NOTE: by necessity, this is the only method which takes the __id__,
62
+ # not the Object itself. You CANNOT pass the Object to this as a
63
+ # finalizer cannot hold reference to the Object being finalized; that
64
+ # prevents GC, which introduces a memory leak and defeats the entire
65
+ # purpose of this.
66
+ #
67
+ # @param key_id [Integer] the Object Identifier to clean up during
68
+ # finalization.
32
69
  def finalize key_id
33
70
  proc do
34
71
  FROZEN_FINALIZED_IDS.delete(key_id)
@@ -36,12 +73,19 @@ module Contrast
36
73
  end
37
74
  end
38
75
 
76
+ # Frozen things cannot be finalized. To avoid any issue here, we
77
+ # intercept the #freeze call and set finalizers on the Object. To
78
+ # ensure later we know it's been pre-finalized, we add it's __id__ to
79
+ # our tracking.
80
+ #
81
+ # @param key [Object] the Object on which we need to pre-define
82
+ # finalizers
39
83
  def pre_freeze key
40
84
  return if key.cs__frozen?
41
85
  return if FROZEN_FINALIZED_IDS.include?(key.__id__)
42
- return unless Contrast::Utils::DuckUtils.trackable?(key)
43
86
 
44
87
  ObjectSpace.define_finalizer(key, finalize(key.__id__))
88
+
45
89
  FROZEN_FINALIZED_IDS << key.__id__
46
90
  rescue StandardError => _e
47
91
  nil
@@ -110,16 +110,23 @@ module Contrast
110
110
  dynamic_source.method_name = Contrast::Utils::StringUtils.force_utf8(field)
111
111
  dynamic_source.instance_method = source_node.instance_method?
112
112
  dynamic_source.target = Contrast::Utils::StringUtils.force_utf8(source_node.target_string)
113
- properties.events.each do |event|
114
- dynamic_source.events << event.to_dtm_event
115
- end
116
113
  dynamic_source.properties[READ_TABLE] = Contrast::Utils::StringUtils.force_utf8(source_node.class_name)
117
114
  dynamic_source.properties[READ_COLUMN] = Contrast::Utils::StringUtils.force_utf8(field)
118
115
  dynamic_source.properties[WRITE_QUERY_TIME] = Contrast::Utils::StringUtils.force_utf8(Contrast::Utils::Timer.now_ms)
119
116
  url = current_context.request.normalized_uri
120
117
  dynamic_source.properties[WRITE_QUERY_URL] = Contrast::Utils::StringUtils.force_utf8(url)
118
+ append_events(dynamic_source, properties.event)
121
119
  dynamic_source
122
120
  end
121
+
122
+ def append_events dynamic_source, event
123
+ return unless event
124
+
125
+ event.parent_events&.each do |parent_event|
126
+ append_events(dynamic_source, parent_event)
127
+ end
128
+ dynamic_source.events << event.to_dtm_event
129
+ end
123
130
  end
124
131
  end
125
132
  end
@@ -37,7 +37,7 @@ module Contrast
37
37
  return unless ASSESS.enabled?
38
38
  return if in_contrast_scope?
39
39
 
40
- with_contrast_scope { patcher.patch_specific_module(mod) }
40
+ patcher.patch_specific_module(mod)
41
41
  rescue StandardError => e
42
42
  logger.warn(
43
43
  'Unable to patch assess during eval',
@@ -72,8 +72,6 @@ module Contrast
72
72
  add_node(trigger_node)
73
73
  end
74
74
  end
75
-
76
- tracked_classes.concat(policy_data[TRACKED_CLASSES_KEY])
77
75
  end
78
76
 
79
77
  # Providers is a term that we're taking from Java until we come up with
@@ -157,19 +157,24 @@ module Contrast
157
157
  # Everything else (propagation nodes) are Source2Target
158
158
  def build_action
159
159
  @event_action ||= begin
160
- source = source_string
161
- target = target_string
162
- if source.nil?
160
+ case node_class
161
+ when Contrast::Agent::Assess::Policy::SourceNode::SOURCE
163
162
  :CREATION
164
- elsif target.nil?
163
+ when Contrast::Agent::Assess::Policy::TriggerNode::TRIGGER
165
164
  :TRIGGER
166
165
  else
167
- # TeamServer can't handle the multi-source or multi-target
168
- # values. Give it some help by changing them to 'A'
169
- source = ALL_TYPE if source.include?(Contrast::Utils::ObjectShare::COMMA)
170
- target = ALL_TYPE if target.include?(Contrast::Utils::ObjectShare::COMMA)
171
- str = source[0] + TO_MARKER + target[0]
172
- str.to_sym
166
+ if source_string.nil?
167
+ :CREATION
168
+ elsif target_string.nil?
169
+ :TRIGGER
170
+ else
171
+ # TeamServer can't handle the multi-source or multi-target
172
+ # values. Give it some help by changing them to 'A'
173
+ source = source_string.include?(Contrast::Utils::ObjectShare::COMMA) ? ALL_TYPE : source_string
174
+ target = target_string.include?(Contrast::Utils::ObjectShare::COMMA) ? ALL_TYPE : target_string
175
+ str = source[0] + TO_MARKER + target[0]
176
+ str.to_sym
177
+ end
173
178
  end
174
179
  end
175
180
  @event_action
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/components/interface'
5
- require 'contrast/extension/assess/assess_extension'
6
5
  require 'contrast/utils/object_share'
7
6
 
8
7
  module Contrast
@@ -18,18 +17,35 @@ module Contrast
18
17
  access_component :analysis
19
18
 
20
19
  class << self
20
+ # Use the given trace_point, built from an :end event, to determine
21
+ # where the loaded code lives and scan that code for policy
22
+ # violations.
23
+ #
24
+ # @param trace_point [TracePoint] the TracePoint generated by an
25
+ # :end event at the end of a Module definition.
21
26
  def scan trace_point
22
27
  return unless ASSESS.enabled?
23
28
  return unless ASSESS.require_scan?
24
29
 
30
+ provider_values = policy.providers.values
31
+ return if provider_values.all?(&:disabled?)
32
+
25
33
  return unless trace_point.path
26
34
  return if trace_point.path.start_with?(Gem.dir)
27
35
 
28
36
  mod = trace_point.self
29
37
  return if mod.cs__frozen? || mod.singleton_class?
30
38
 
31
- policy.providers.each_value do |provider|
32
- provider.analyze mod
39
+ # TODO: RUBY-1014 - remove non-AST approach
40
+ if RUBY_VERSION >= '2.6.0'
41
+ ast = RubyVM::AbstractSyntaxTree.parse_file(trace_point.path)
42
+ provider_values.each do |provider|
43
+ provider.parse(trace_point, ast)
44
+ end
45
+ else
46
+ provider_values.each do |provider|
47
+ provider.analyze(mod)
48
+ end
33
49
  end
34
50
  end
35
51
 
@@ -77,25 +77,21 @@ module Contrast
77
77
  0
78
78
  end
79
79
  return unless can
80
+ return unless Contrast::Agent::Assess::Tracker.tracked?(object)
80
81
 
81
- props = Contrast::Agent::Assess::Finalizers::Finalize::PROPERTIES_HASH[object]
82
- return unless props
83
-
84
- Contrast::Agent::Assess::Finalizers::Finalize::PROPERTIES_HASH[preshift.object] ||= props.dup
82
+ Contrast::Agent::Assess::Tracker.copy(object, preshift.object)
85
83
  end
86
84
 
87
85
  def append_arg_details preshift, args
88
86
  preshift.args = args.dup
89
- preshift.args.each_with_index do |arg, index|
87
+ preshift.args.each_with_index do |preshift_arg, index|
90
88
  original_arg = args[index]
91
- next if arg.__id__ == original_arg.__id__
92
-
93
- props = Contrast::Agent::Assess::Finalizers::Finalize::PROPERTIES_HASH[original_arg]
94
- next unless props
89
+ next if preshift_arg.__id__ == original_arg.__id__
90
+ next unless Contrast::Agent::Assess::Tracker.tracked?(original_arg)
95
91
 
96
- Contrast::Agent::Assess::Finalizers::Finalize::PROPERTIES_HASH[arg] ||= props.dup
92
+ Contrast::Agent::Assess::Tracker.copy(original_arg, preshift_arg)
97
93
  end
98
- preshift.arg_lengths = preshift.args.map { |arg| Contrast::Utils::DuckUtils.quacks_to?(arg, :length) ? arg.length : 0 }
94
+ preshift.arg_lengths = preshift.args.map { |preshift_arg| Contrast::Utils::DuckUtils.quacks_to?(preshift_arg, :length) ? preshift_arg.length : 0 }
99
95
  end
100
96
  end
101
97
  end
@@ -18,7 +18,7 @@ module Contrast
18
18
  # Strings
19
19
  module PropagationMethod
20
20
  include Contrast::Components::Interface
21
- access_component :logging
21
+ access_component :analysis, :logging
22
22
 
23
23
  APPEND_ACTION = 'APPEND'
24
24
  CENTER_ACTION = 'CENTER'
@@ -124,15 +124,16 @@ module Contrast
124
124
  Contrast::Agent::Assess::Policy::Propagator::Custom.propagate(propagation_node, preshift, ret, block)
125
125
  elsif propagation_node.action == SPLIT_ACTION
126
126
  Contrast::Agent::Assess::Policy::Propagator::Split.propagate(propagation_node, preshift, target)
127
- elsif Contrast::Utils::DuckUtils.quacks_to?(target, :cs__properties)
128
- handle_cs_properties_propagation(propagation_node, preshift, target, object, ret, args, block)
129
127
  elsif Contrast::Utils::DuckUtils.iterable_hash?(target)
130
128
  handle_hash_propagation(propagation_node, preshift, target, object, ret, args, block)
131
129
  elsif Contrast::Utils::DuckUtils.iterable_enumerable?(target)
132
130
  handle_enumerable_propagation(propagation_node, preshift, target, object, ret, args, block)
131
+ else
132
+ handle_cs_properties_propagation(propagation_node, preshift, target, object, ret, args, block)
133
133
  end
134
134
  rescue StandardError => e
135
135
  logger.warn('Unable to apply propagation', e, node_id: propagation_node.id)
136
+ nil
136
137
  end
137
138
 
138
139
  # Custom actions tend to be the more complex of our propagations.
@@ -186,28 +187,32 @@ module Contrast
186
187
  false
187
188
  end
188
189
 
190
+ # We cannot propagate to frozen things that have not been updated
191
+ # to work with our property tracking, unless they're duplicable and
192
+ # the return.
193
+ # We probably shouldn't propagate to frozen things at all, as
194
+ # they're supposed to be immutable, but third parties do jenky
195
+ # things, so allow it as long as it is safe to do.
196
+ #
197
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode]
198
+ # the node that governs this propagation event.
199
+ # @param target [Object] the Target to which to propagate.
200
+ # @return [Boolean] if the target can be propagated to
189
201
  def appropriate_target? propagation_node, target
190
- # We cannot propagate to things that do not have cs__properties.
191
- return false unless Contrast::Utils::DuckUtils.quacks_to?(target, :cs__properties)
192
-
193
- # We cannot propagate to frozen things that have not been
194
- # previously tracked. We probably shouldn't propagate to frozen
195
- # things at all, as they're supposed to be immutable, but third
196
- # parties do jenky things, so allow it as long as it is safe to do.
197
- return false if target.cs__frozen? &&
198
- !target.cs__tracked? &&
199
- propagation_node.targets[0] != Contrast::Utils::ObjectShare::RETURN_KEY
202
+ # special handle Returns b/c we can do unfreezing magic during propagation
203
+ return true if propagation_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
200
204
 
201
- true
205
+ Contrast::Agent::Assess::Tracker.trackable?(target)
202
206
  end
203
207
 
204
208
  # If this patcher has tags, apply them to the entire target
205
209
  def apply_tags propagation_node, target
206
210
  return unless propagation_node.tags
207
211
 
212
+ properties = Contrast::Agent::Assess::Tracker.properties(target)
208
213
  length = Contrast::Utils::StringUtils.ret_length(target)
209
214
  propagation_node.tags.each do |tag|
210
- target.cs__properties.add_tag(tag, 0...length)
215
+ properties.add_tag(tag, 0...length)
211
216
  end
212
217
  end
213
218
 
@@ -215,8 +220,11 @@ module Contrast
215
220
  def apply_untags propagation_node, target
216
221
  return unless propagation_node.untags
217
222
 
223
+ properties = Contrast::Agent::Assess::Tracker.properties(target)
224
+ return unless properties
225
+
218
226
  propagation_node.untags.each do |tag|
219
- target.cs__properties.delete_tags(tag)
227
+ properties.delete_tags(tag)
220
228
  end
221
229
  end
222
230
 
@@ -230,6 +238,15 @@ module Contrast
230
238
  true
231
239
  end
232
240
 
241
+ # Safely duplicate the target, or return nil
242
+ #
243
+ # @param target [Object] the thing to check for duplication
244
+ def safe_dup target
245
+ target.dup
246
+ rescue StandardError => _e
247
+ nil
248
+ end
249
+
233
250
  def handle_hash_propagation propagation_node, preshift, target, object, ret, args, block
234
251
  target.each_pair do |key, value|
235
252
  apply_propagator(propagation_node, preshift, key, object, ret, args, block)
@@ -249,30 +266,33 @@ module Contrast
249
266
  return if propagation_node.action == NOOP_ACTION
250
267
  return unless can_propagate?(propagation_node, preshift, target)
251
268
 
252
- # propagate all the tags from the sources to the target
253
269
  propagation_class = PROPAGATION_ACTIONS.fetch(propagation_node.action, nil)
254
270
  unless propagation_class
255
271
  logger.warn(
256
- 'Unknown propgation action receieved. Unable to propagate.',
272
+ 'Unknown propagation action received. Unable to propagate.',
257
273
  node_id: propagation_node.id,
258
274
  action: propagation_node.action)
259
- return ret
275
+ return
260
276
  end
261
-
262
277
  restore_frozen_state = false
263
- if target.cs__frozen?
264
- return ret unless propagation_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
278
+ if target.cs__frozen? && !Contrast::Agent::Assess::Tracker.trackable?(target)
279
+ return unless ASSESS.track_frozen_sources?
280
+ return unless propagation_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
281
+
282
+ dup = safe_dup(ret)
283
+ return unless dup
265
284
 
266
285
  restore_frozen_state = true
267
- ret = Contrast::Utils::FreezeUtil.unfreeze_dup(target)
286
+ ret = dup
268
287
  target = ret
288
+ Contrast::Agent::Assess::Tracker.pre_freeze(ret)
289
+ ret.cs__freeze
290
+ # double check that we were able to finalize the replaced return
291
+ return unless Contrast::Agent::Assess::Tracker.trackable?(target)
269
292
  end
270
-
271
293
  propagation_class.propagate(propagation_node, preshift, target)
272
-
273
294
  # Once we've propagated, attempt to tag the target if there is a tag(s) to be applied
274
295
  apply_tags(propagation_node, target)
275
-
276
296
  # Even though we skipped propagating tags from the source if they
277
297
  # were included in untags, the target may have already had some on
278
298
  # it. Let's go ahead and remove them.
@@ -280,16 +300,13 @@ module Contrast
280
300
  # both and there should never be a propagator that has a tag in
281
301
  # its untag.
282
302
  apply_untags(propagation_node, target)
283
-
284
- target.cs__properties.add_properties(propagation_node.properties)
285
-
286
- target.cs__properties.build_event(propagation_node, target, object, ret, args)
287
-
303
+ properties = Contrast::Agent::Assess::Tracker.properties(target)
304
+ properties.add_properties(propagation_node.properties)
305
+ properties.build_event(propagation_node, target, object, ret, args)
288
306
  logger.trace('Propagation detected',
289
307
  node_id: propagation_node.id,
290
308
  target_id: target.__id__)
291
- ret.cs__freeze if restore_frozen_state
292
- ret
309
+ restore_frozen_state ? ret : nil
293
310
  end
294
311
  end
295
312
  end
@@ -16,6 +16,9 @@ module Contrast
16
16
  # copy tags from the param to the target in chunks of param size or less
17
17
  # if param is appended in space less than param length
18
18
  def propagate propagation_node, preshift, target
19
+ properties = Contrast::Agent::Assess::Tracker.properties(target)
20
+ return unless properties
21
+
19
22
  sources = propagation_node.sources
20
23
  source1 = find_source(sources[0], preshift)
21
24
  # Some appends have two args. If they don't this is probably something
@@ -25,25 +28,25 @@ module Contrast
25
28
  # if the object and the return are the same length just copy the tags
26
29
  # from the object(since nothing from args was added to return)
27
30
  if source1.length == target.length
28
- target.cs__copy_from(source1, 0, propagation_node.untags)
31
+ properties.copy_from(source1, target, 0, propagation_node.untags)
29
32
  else
30
33
  # find original in the target, copy tags to the new position in
31
34
  # target
32
35
  original_start_index = target.index(source1)
33
- target.cs__copy_from(source1, original_start_index, propagation_node.untags)
36
+ properties.copy_from(source1, target, original_start_index, propagation_node.untags)
34
37
 
35
38
  start = original_start_index + source1.length
36
39
  while start < target.length
37
- target.cs__copy_from(source2, start, propagation_node.untags)
40
+ properties.copy_from(source2, target, start, propagation_node.untags)
38
41
  start += source2.length
39
42
  next unless start > target.length
40
43
 
41
- target.cs__properties.tags_at(start - source2.length).each do |tag|
44
+ properties.tags_at(start - source2.length).each do |tag|
42
45
  tag.update_end(target.length)
43
46
  end
44
47
  end
45
48
  end
46
- target.cs__properties.cleanup_tags
49
+ properties.cleanup_tags
47
50
  end
48
51
  end
49
52
  end