contrast-agent 3.14.0 → 3.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) 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/assess.rb +1 -0
  10. data/lib/contrast/agent/assess/contrast_event.rb +4 -12
  11. data/lib/contrast/agent/assess/finalizers/freeze.rb +3 -1
  12. data/lib/contrast/agent/assess/finalizers/hash.rb +45 -1
  13. data/lib/contrast/agent/assess/policy/patcher.rb +1 -1
  14. data/lib/contrast/agent/assess/policy/policy.rb +0 -2
  15. data/lib/contrast/agent/assess/policy/policy_scanner.rb +0 -1
  16. data/lib/contrast/agent/assess/policy/preshift.rb +7 -11
  17. data/lib/contrast/agent/assess/policy/propagation_method.rb +50 -33
  18. data/lib/contrast/agent/assess/policy/propagator/append.rb +8 -5
  19. data/lib/contrast/agent/assess/policy/propagator/base.rb +1 -1
  20. data/lib/contrast/agent/assess/policy/propagator/center.rb +9 -5
  21. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +5 -3
  22. data/lib/contrast/agent/assess/policy/propagator/insert.rb +6 -3
  23. data/lib/contrast/agent/assess/policy/propagator/keep.rb +4 -1
  24. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +6 -6
  25. data/lib/contrast/agent/assess/policy/propagator/next.rb +7 -5
  26. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +8 -5
  27. data/lib/contrast/agent/assess/policy/propagator/remove.rb +8 -4
  28. data/lib/contrast/agent/assess/policy/propagator/replace.rb +5 -2
  29. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +7 -5
  30. data/lib/contrast/agent/assess/policy/propagator/select.rb +15 -7
  31. data/lib/contrast/agent/assess/policy/propagator/splat.rb +14 -8
  32. data/lib/contrast/agent/assess/policy/propagator/split.rb +14 -8
  33. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +30 -21
  34. data/lib/contrast/agent/assess/policy/propagator/trim.rb +11 -5
  35. data/lib/contrast/agent/assess/policy/source_method.rb +85 -58
  36. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +16 -11
  37. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
  38. data/lib/contrast/agent/assess/policy/trigger_method.rb +38 -15
  39. data/lib/contrast/agent/assess/policy/trigger_node.rb +14 -13
  40. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +2 -1
  41. data/lib/contrast/agent/assess/properties.rb +2 -0
  42. data/lib/contrast/agent/assess/property/updated.rb +136 -0
  43. data/lib/contrast/agent/assess/tracker.rb +66 -0
  44. data/lib/contrast/agent/class_reopener.rb +7 -5
  45. data/lib/contrast/agent/middleware.rb +0 -1
  46. data/lib/contrast/agent/patching/policy/patcher.rb +13 -22
  47. data/lib/contrast/agent/patching/policy/policy.rb +1 -4
  48. data/lib/contrast/agent/response.rb +17 -6
  49. data/lib/contrast/agent/rewriter.rb +1 -3
  50. data/lib/contrast/agent/version.rb +1 -1
  51. data/lib/contrast/api/communication/messaging_queue.rb +1 -4
  52. data/lib/contrast/api/decorators/application_update.rb +2 -4
  53. data/lib/contrast/api/decorators/trace_event.rb +5 -5
  54. data/lib/contrast/components/app_context.rb +11 -9
  55. data/lib/contrast/components/config.rb +3 -13
  56. data/lib/contrast/components/contrast_service.rb +2 -2
  57. data/lib/contrast/config/application_configuration.rb +5 -2
  58. data/lib/contrast/config/service_configuration.rb +8 -2
  59. data/lib/contrast/configuration.rb +88 -47
  60. data/lib/contrast/extension/assess.rb +0 -2
  61. data/lib/contrast/extension/assess/array.rb +8 -5
  62. data/lib/contrast/extension/assess/erb.rb +6 -3
  63. data/lib/contrast/extension/assess/fiber.rb +9 -9
  64. data/lib/contrast/extension/assess/hash.rb +2 -3
  65. data/lib/contrast/extension/assess/kernel.rb +12 -5
  66. data/lib/contrast/extension/assess/marshal.rb +3 -2
  67. data/lib/contrast/extension/assess/regexp.rb +5 -4
  68. data/lib/contrast/extension/assess/string.rb +8 -10
  69. data/lib/contrast/framework/rack/patch/session_cookie.rb +12 -18
  70. data/lib/contrast/framework/rails/patch/assess_configuration.rb +4 -10
  71. data/lib/contrast/framework/rails/support.rb +2 -0
  72. data/lib/contrast/logger/application.rb +11 -3
  73. data/lib/contrast/utils/assess/tracking_util.rb +48 -3
  74. data/lib/contrast/utils/duck_utils.rb +0 -10
  75. data/lib/contrast/utils/env_configuration_item.rb +2 -1
  76. data/lib/contrast/utils/invalid_configuration_util.rb +21 -19
  77. data/lib/contrast/utils/string_utils.rb +10 -5
  78. data/resources/assess/policy.json +0 -10
  79. data/ruby-agent.gemspec +16 -15
  80. data/service_executables/VERSION +1 -1
  81. data/service_executables/linux/contrast-service +0 -0
  82. data/service_executables/mac/contrast-service +0 -0
  83. metadata +42 -21
  84. data/lib/contrast/agent/assess/finalizers/finalize.rb +0 -21
  85. data/lib/contrast/extension/assess/assess_extension.rb +0 -145
  86. data/lib/contrast/utils/freeze_util.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3ef67bfca5c3d772078af285e4b3fa1ab314b06af04355db1aa50cce0d284e8
4
- data.tar.gz: 5eb0fe6b5dba8f64b5947c1c7cade90718b18224b62b5e02eed470613e6fbe6d
3
+ metadata.gz: ac8fc7d0e9c127859cf7bdb149c7ac519286c4329bd81ad28db4963128e0cd63
4
+ data.tar.gz: 784afc67ef269df8dfbaed392e8ad28e2e3b679113ed8679625f95033e324929
5
5
  SHA512:
6
- metadata.gz: caf748a141fe652cbb1cb3e658969820b22ca4e58a11cdc46ee9bc94932c00e031b186b96e8c44b884d6f658951e8936a75444aa4d52cb9d0d5e563ab29ef26b
7
- data.tar.gz: b3235852bb77977f244beed747b032bfa6e80eb86ebdcfab622a8dba7acc7a939f87e08022a07e795d2c455ec3d3107771e5cdae0d5b893a4c6019f09945db43
6
+ metadata.gz: c1a5563b34a4eba33fdf8094986362f70c88bda53102899bb01ad0096588d26883b5e7ad475e41afa15b95674b8c2810cfe6546c6779b8e3b2030bf7bb1e33d6
7
+ data.tar.gz: 1a1eb220719c1caf3f7153108bff4ac5132130d6879dc2b425e6dd31720e4c76bda9c27c0ac65fa00ccb846a31cb173e04dc7d3320ba3079ea9b785a62991f00
@@ -14,20 +14,19 @@ static VALUE contrast_assess_marshal_module_load(const int argc,
14
14
  if (argc >= 1) {
15
15
  source_string = argv[0];
16
16
 
17
- if (rb_respond_to(source_string, rb_sym_cs_tracked)) {
18
- VALUE tracked = rb_funcall(source_string, rb_sym_cs_tracked, 0);
19
-
20
- if (tracked == Qtrue) {
21
- VALUE skip = rb_funcall(contrast_patcher(),
22
- rb_sym_skip_assess_analysis, 0);
23
-
24
- if (skip == Qfalse) {
25
- VALUE scope =
26
- rb_funcall(contrast_patcher(), rb_sym_enter_scope, 0);
27
- rb_funcall(marshal_module, rb_sym_assess_load_trigger_check,
28
- 2, source_string, result);
29
- rb_funcall(contrast_patcher(), rb_sym_exit_scope, 0);
30
- }
17
+ VALUE tracked =
18
+ rb_funcall(properties_hash, rb_sym_hash_tracked, 1, source_string);
19
+
20
+ if (tracked == Qtrue) {
21
+ VALUE skip =
22
+ rb_funcall(contrast_patcher(), rb_sym_skip_assess_analysis, 0);
23
+
24
+ if (skip == Qfalse) {
25
+ VALUE scope =
26
+ rb_funcall(contrast_patcher(), rb_sym_enter_scope, 0);
27
+ rb_funcall(marshal_module, rb_sym_assess_load_trigger_check, 2,
28
+ source_string, result);
29
+ rb_funcall(contrast_patcher(), rb_sym_exit_scope, 0);
31
30
  }
32
31
  }
33
32
  }
@@ -35,7 +34,11 @@ static VALUE contrast_assess_marshal_module_load(const int argc,
35
34
  }
36
35
 
37
36
  void Init_cs__assess_marshal_module(void) {
38
- marshal_module = rb_define_class_under(core_assess, "MarshalPropagator", rb_cObject);
37
+ // Contrast::Agent::Assess::Tracker::PROPERTIES_HASH
38
+ VALUE tracker = rb_define_class_under(assess, "Tracker", rb_cObject);
39
+ properties_hash = rb_const_get(tracker, rb_intern("PROPERTIES_HASH"));
40
+ marshal_module =
41
+ rb_define_class_under(core_assess, "MarshalPropagator", rb_cObject);
39
42
  rb_sym_assess_load_trigger_check = rb_intern("cs__load_trigger_check");
40
43
 
41
44
  contrast_register_singleton_prepend_patch(
@@ -3,6 +3,7 @@
3
3
  static VALUE marshal_module;
4
4
 
5
5
  static VALUE rb_sym_assess_load_trigger_check;
6
+ static VALUE properties_hash;
6
7
 
7
8
  /*
8
9
  * Rails is a jerk. In Rails 5, they decided to do away with the alias chaining
@@ -5,44 +5,43 @@
5
5
  #include "../cs__common/cs__common.h"
6
6
  #include <ruby.h>
7
7
 
8
- static VALUE contrast_assess_string_uminus(const int argc, VALUE *argv,
8
+ static VALUE contrast_assess_string_freeze(const int argc, VALUE *argv,
9
9
  const VALUE obj) {
10
- VALUE dup, tracked;
11
10
  if (!OBJ_FROZEN(obj)) {
12
- tracked = rb_funcall(obj, rb_sym_cs_tracked, 0);
13
- if (RTEST(tracked)) {
14
- /*
15
- * If the object is not frozen and the object is tracked, we cheat.
16
- * We dup and then freeze to replicate the behavior of str_uminus in
17
- * string.c, but we ignore any other monkey patches on String#-@
18
- */
19
- dup = rb_funcall(obj, rb_sym_dup, 0);
20
- rb_funcall(obj, rb_intern("cs__transfer_properties"), 1, dup);
21
- rb_funcall(dup, rb_sym_freeze, 0);
22
- return dup;
23
- }
11
+ rb_funcall(properties_hash, rb_sym_pre_freeze, 1, obj);
24
12
  }
25
- /* in all other cases, preserve monkey patching and c call */
26
- return rb_funcall(obj, rb_sym_assess_string_uminus, 0);
13
+ return rb_funcall(obj, rb_sym_assess_string_freeze, 0);
27
14
  }
28
15
 
29
- static VALUE contrast_assess_string_freeze(const int argc, VALUE *argv,
16
+ static VALUE contrast_assess_string_uminus(const int argc, VALUE *argv,
30
17
  const VALUE obj) {
31
18
  if (!OBJ_FROZEN(obj)) {
32
- // Contrast::Agent::Assess::Finalizers::Finalize::PROPERTIES_HASH.pre_freeze(self)
33
- rb_funcall(properties_hash, rb_intern("pre_freeze"), 1, obj);
19
+ /* We're doing something intentionally different here. Ruby, for -@,
20
+ * attempts to de-duplicate the String and use an "interned" copy of
21
+ * the String. We cannot allow that to happen for a couple reasons:
22
+ * - prior to Ruby 2.7, this would cause us to track ALL instances of
23
+ * that interned copy.
24
+ * - 2.7 and later, this action is actually missed because of a change
25
+ * to the str_uminus method in Ruby, which dups the String in a way
26
+ * that we cannot see.
27
+ * B/c we cannot track this in 2.7, rather than having a version check
28
+ * and two approaches, we'll instead directly call the #freeze method,
29
+ * so the end result is that this String itself is frozen and never
30
+ * deduplicated.
31
+ */
32
+ return contrast_assess_string_freeze(argc, argv, obj);
34
33
  }
35
- return rb_funcall(obj, rb_sym_assess_string_freeze, 0);
34
+ /* in all other cases, preserve monkey patching and c call */
35
+ return rb_funcall(obj, rb_sym_assess_string_uminus, 0);
36
36
  }
37
37
 
38
38
  void Init_cs__assess_string(void) {
39
39
  rb_sym_dup = rb_intern("dup");
40
40
  rb_sym_freeze = rb_intern("freeze");
41
-
42
- // Contrast::Agent::Assess::Finalizers::Finalize::PROPERTIES_HASH
43
- VALUE finalizers = rb_define_module_under(assess, "Finalizers");
44
- VALUE finalize = rb_define_module_under(finalizers, "Finalize");
45
- properties_hash = rb_const_get(finalize, rb_intern("PROPERTIES_HASH"));
41
+ rb_sym_pre_freeze = rb_intern("pre_freeze");
42
+ // Contrast::Agent::Assess::Tracker::PROPERTIES_HASH
43
+ VALUE tracker = rb_define_class_under(assess, "Tracker", rb_cObject);
44
+ properties_hash = rb_const_get(tracker, rb_intern("PROPERTIES_HASH"));
46
45
 
47
46
  rb_sym_assess_string_uminus =
48
47
  contrast_register_patch("String", "-@", &contrast_assess_string_uminus);
@@ -2,10 +2,12 @@
2
2
 
3
3
  static VALUE rb_sym_assess_string_uminus;
4
4
  static VALUE rb_sym_assess_string_freeze;
5
- // Contrast::Agent::Assess::Finalizers::Finalize::PROPERTIES_HASH
5
+ // Contrast::Agent::Assess::Tracker::PROPERTIES_HASH
6
6
  static VALUE properties_hash;
7
7
  static VALUE rb_sym_dup;
8
8
  static VALUE rb_sym_freeze;
9
+ static VALUE rb_sym_pre_freeze;
10
+ static VALUE properties_hash;
9
11
 
10
12
  /*
11
13
  * The String#-@ method calls to the str_uminus method in String.C. This method
@@ -18,7 +18,7 @@ VALUE rb_sym_in_scope;
18
18
  VALUE rb_sym_skip_contrast_analysis;
19
19
  VALUE rb_sym_skip_assess_analysis;
20
20
  VALUE rb_sym_method;
21
- VALUE rb_sym_cs_tracked;
21
+ VALUE rb_sym_hash_get, rb_sym_hash_set, rb_sym_hash_tracked;
22
22
  /* end globals */
23
23
 
24
24
  void patch_via_funchook(void *original_function, void *hook_function) {
@@ -144,7 +144,9 @@ void Init_cs__common(void) {
144
144
  rb_sym_skip_contrast_analysis = rb_intern("skip_contrast_analysis?");
145
145
  rb_sym_skip_assess_analysis = rb_intern("skip_assess_analysis?");
146
146
  rb_sym_method = rb_intern("__method__");
147
- rb_sym_cs_tracked = rb_intern("cs__tracked?");
147
+ rb_sym_hash_get = rb_intern("[]");
148
+ rb_sym_hash_set = rb_intern("[]=");
149
+ rb_sym_hash_tracked = rb_intern("tracked?");
148
150
 
149
151
  /* Used for returning unbound C functions */
150
152
  rb_sym_register_c_patch = rb_intern("register_c_patch");
@@ -23,7 +23,7 @@ extern VALUE rb_sym_in_scope;
23
23
  extern VALUE rb_sym_skip_contrast_analysis;
24
24
  extern VALUE rb_sym_skip_assess_analysis;
25
25
  extern VALUE rb_sym_method;
26
- extern VALUE rb_sym_cs_tracked;
26
+ extern VALUE rb_sym_hash_get, rb_sym_hash_set, rb_sym_hash_tracked;
27
27
 
28
28
  static VALUE patcher;
29
29
  static VALUE rb_sym_instance_method;
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Used to prevent deprecation warnings from flooding stdout
5
- ENV['PB_IGNORE_DEPRECATIONS'] = 'truee'
5
+ ENV['PB_IGNORE_DEPRECATIONS'] = 'true'
6
6
 
7
7
  # Top-level namespace for Contrast Security agent
8
8
  module Contrast
@@ -8,6 +8,7 @@ module Contrast
8
8
  # class under this namespace should be required here, providing a single
9
9
  # point of require for this functionality.
10
10
  module Assess
11
+ require 'contrast/agent/assess/tracker'
11
12
  require 'contrast/agent/module_data'
12
13
  require 'contrast/agent/rewriter'
13
14
  require 'contrast/agent/assess/policy/preshift'
@@ -51,13 +51,7 @@ module Contrast
51
51
  def safe_dup original
52
52
  return nil unless original
53
53
 
54
- begin
55
- duplicate = original.dup
56
- original.cs__transfer_properties(duplicate)
57
- duplicate
58
- rescue StandardError
59
- original
60
- end
54
+ Contrast::Agent::Assess::Tracker.duplicate(original)
61
55
  end
62
56
  end
63
57
 
@@ -97,13 +91,11 @@ module Contrast
97
91
  value_of_source(source, object, ret, args)
98
92
  end
99
93
  selected = mapped.select do |source|
100
- source &&
101
- Contrast::Utils::DuckUtils.quacks_to?(source, :cs__properties) &&
102
- source.cs__properties.events &&
103
- source.cs__properties.events.last
94
+ source && Contrast::Agent::Assess::Tracker.properties(source)&.events&.last
104
95
  end
105
96
  selected.map do |source|
106
- source.cs__properties.events.last.event_id
97
+ properties = Contrast::Agent::Assess::Tracker.properties(source)
98
+ properties.events.last.event_id
107
99
  end
108
100
  end
109
101
 
@@ -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
@@ -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
@@ -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
@@ -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