contrast-agent 3.14.0 → 3.15.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.
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