contrast-agent 4.11.0 → 4.12.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__common/cs__common.c +19 -7
  3. data/ext/cs__common/cs__common.h +4 -2
  4. data/ext/cs__contrast_patch/cs__contrast_patch.c +32 -10
  5. data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -2
  6. data/lib/contrast/agent/assess/contrast_event.rb +1 -1
  7. data/lib/contrast/agent/assess/contrast_object.rb +1 -1
  8. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
  9. data/lib/contrast/agent/assess/policy/preshift.rb +19 -6
  10. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -0
  11. data/lib/contrast/agent/assess/policy/trigger_node.rb +52 -19
  12. data/lib/contrast/agent/assess/property/tagged.rb +34 -25
  13. data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
  14. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +0 -1
  15. data/lib/contrast/agent/patching/policy/method_policy.rb +54 -9
  16. data/lib/contrast/agent/patching/policy/patch.rb +12 -6
  17. data/lib/contrast/agent/patching/policy/patcher.rb +1 -1
  18. data/lib/contrast/agent/request_context.rb +24 -8
  19. data/lib/contrast/agent/rule_set.rb +2 -4
  20. data/lib/contrast/agent/version.rb +1 -1
  21. data/lib/contrast/agent.rb +0 -1
  22. data/lib/contrast/components/assess.rb +7 -0
  23. data/lib/contrast/config/assess_configuration.rb +1 -0
  24. data/lib/contrast/utils/class_util.rb +60 -53
  25. data/lib/contrast/utils/lru_cache.rb +4 -2
  26. data/lib/contrast.rb +1 -1
  27. data/resources/assess/policy.json +12 -6
  28. data/resources/deadzone/policy.json +86 -5
  29. data/service_executables/VERSION +1 -1
  30. data/service_executables/linux/contrast-service +0 -0
  31. data/service_executables/mac/contrast-service +0 -0
  32. metadata +9 -14
  33. data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
  34. data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
  35. data/ext/cs__protect_kernel/extconf.rb +0 -5
  36. data/lib/contrast/extension/protect/kernel.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ad97d601b81e16e1d0263d86b60a3acd0e5a5ff9cb3f42598c262774a518169
4
- data.tar.gz: 749f87aefffd1e1504834dc7f919d45280cf560a20805184757dd9f6bda97477
3
+ metadata.gz: 6a0a50913c5c680462f9afd79070ca1f3d46d09d66ffdb943b1dc778f84830d6
4
+ data.tar.gz: bb3254c6a2cdc2b4add4e09b4bf1ed63c6a640f0dd82824db2769b911aacf96b
5
5
  SHA512:
6
- metadata.gz: cfa15549565b4f17c431332c7bc7c6fe84bd5bea321860db84f24a2df5e2d5241675200ed6e50ebe53fc319d94ade6ebfed26fe664fc297dde54a1fc9f645fda
7
- data.tar.gz: 017f02883faee2d281b7b052844653ddbb744fa27f02cf5739c38e50256eb054effb966c5b35b79b05e7d3af62f35d90509352f5c4327c9474e936c7e740ef19
6
+ metadata.gz: 78d5e66ee3ab7408e94348f6c2998e73cd78227a8b75cffa7add261b771092d470431ce8e44357dfb7d1a178e03e5033efc9f7a4c974a979f9dea88b2c55a3ec
7
+ data.tar.gz: d20f41b563778d84e63e4db7a9399e666a85fe1a2343772258f5248cd139f02e6a6bea33e51d9e5a0f954b1e2b4683ec08f3db7a52ab5a6990f7d725d2bc3dea
@@ -73,11 +73,20 @@ VALUE contrast_register_singleton_patch(const char *module_name,
73
73
  IMPL_ALIAS_SINGLETON);
74
74
  }
75
75
 
76
- VALUE contrast_register_singleton_prepend_patch(
77
- const char *module_name, const char *method_name,
78
- VALUE(c_fn)(const int, VALUE *, const VALUE)) {
76
+ VALUE contrast_register_prepend_patch(const char *module_name,
77
+ const char *method_name,
78
+ VALUE(c_fn)(const int, VALUE *,
79
+ const VALUE)) {
80
+ return _contrast_register_patch(module_name, method_name, c_fn,
81
+ IMPL_PREPEND_INSTANCE);
82
+ }
83
+
84
+ VALUE contrast_register_singleton_prepend_patch(const char *module_name,
85
+ const char *method_name,
86
+ VALUE(c_fn)(const int, VALUE *,
87
+ const VALUE)) {
79
88
  return _contrast_register_patch(module_name, method_name, c_fn,
80
- IMPL_PREPEND);
89
+ IMPL_PREPEND_SINGLETON);
81
90
  }
82
91
 
83
92
  static VALUE
@@ -122,8 +131,10 @@ _contrast_register_patch(const char *module_name, const char *method_name,
122
131
  case IMPL_ALIAS_SINGLETON:
123
132
  impl = ID2SYM(rb_sym_alias_singleton);
124
133
  break;
125
- case IMPL_PREPEND:
126
- impl = ID2SYM(rb_sym_prepend);
134
+ case IMPL_PREPEND_INSTANCE:
135
+ impl = ID2SYM(rb_sym_prepend_instance);
136
+ case IMPL_PREPEND_SINGLETON:
137
+ impl = ID2SYM(rb_sym_prepend_singleton);
127
138
  break;
128
139
  }
129
140
 
@@ -152,7 +163,8 @@ void Init_cs__common(void) {
152
163
  rb_sym_register_c_patch = rb_intern("register_c_patch");
153
164
  rb_sym_alias_instance = rb_intern("alias_instance");
154
165
  rb_sym_alias_singleton = rb_intern("alias_singleton");
155
- rb_sym_prepend = rb_intern("prepend");
166
+ rb_sym_prepend_instance = rb_intern("prepend_instance");
167
+ rb_sym_prepend_singleton = rb_intern("prepend_singleton");
156
168
 
157
169
  /* Ensure definition of core Contrast instrumentation modules */
158
170
  contrast = rb_define_module("Contrast");
@@ -6,7 +6,8 @@
6
6
  typedef enum {
7
7
  IMPL_ALIAS_INSTANCE,
8
8
  IMPL_ALIAS_SINGLETON,
9
- IMPL_PREPEND
9
+ IMPL_PREPEND_INSTANCE,
10
+ IMPL_PREPEND_SINGLETON,
10
11
  } patch_impl;
11
12
 
12
13
  static VALUE cs__send_method;
@@ -30,7 +31,8 @@ static VALUE rb_sym_instance_method;
30
31
  static VALUE rb_sym_register_c_patch;
31
32
  static VALUE rb_sym_alias_instance;
32
33
  static VALUE rb_sym_alias_singleton;
33
- static VALUE rb_sym_prepend;
34
+ static VALUE rb_sym_prepend_instance;
35
+ static VALUE rb_sym_prepend_singleton;
34
36
 
35
37
  void patch_via_funchook(void *original_function, void *hook_function);
36
38
 
@@ -182,7 +182,8 @@ VALUE contrast_run_patches(const VALUE *wrapped_args) {
182
182
  contrast_patch_call_rescue,
183
183
  (VALUE)rescue_args, rb_eException, 0);
184
184
  break;
185
- case IMPL_PREPEND:
185
+ case IMPL_PREPEND_INSTANCE:
186
+ case IMPL_PREPEND_SINGLETON:
186
187
  original_ret = rb_rescue2(contrast_call_super, original_args,
187
188
  contrast_patch_call_rescue,
188
189
  (VALUE)rescue_args, rb_eException, 0);
@@ -247,10 +248,14 @@ VALUE contrast_patch_dispatch(const int argc, const VALUE *argv,
247
248
  */
248
249
  switch (impl) {
249
250
  case IMPL_ALIAS_INSTANCE:
250
- case IMPL_PREPEND:
251
+ case IMPL_PREPEND_INSTANCE:
251
252
  known =
252
253
  rb_funcall(patch_status, rb_sym_info_for, 3, object, method, Qtrue);
253
254
  break;
255
+ case IMPL_PREPEND_SINGLETON:
256
+ known =
257
+ rb_funcall(patch_status, rb_sym_info_for, 3, object, method, Qfalse);
258
+ break;
254
259
  case IMPL_ALIAS_SINGLETON:
255
260
  known = rb_funcall(patch_status, rb_sym_info_for, 3, object, method,
256
261
  Qfalse);
@@ -323,7 +328,8 @@ call_original:
323
328
  case IMPL_ALIAS_INSTANCE:
324
329
  case IMPL_ALIAS_SINGLETON:
325
330
  return contrast_patch_call_original(original_args);
326
- case IMPL_PREPEND:
331
+ case IMPL_PREPEND_INSTANCE:
332
+ case IMPL_PREPEND_SINGLETON:
327
333
  return contrast_call_super(original_args);
328
334
  };
329
335
  }
@@ -338,9 +344,14 @@ VALUE contrast_alias_singleton_patch(const int argc, const VALUE *argv,
338
344
  return contrast_patch_dispatch(argc, argv, IMPL_ALIAS_SINGLETON, object);
339
345
  }
340
346
 
341
- VALUE contrast_prepend_patch(const int argc, const VALUE *argv,
347
+ VALUE contrast_prepend_instance_patch(const int argc, const VALUE *argv,
342
348
  const VALUE object) {
343
- return contrast_patch_dispatch(argc, argv, IMPL_PREPEND, object);
349
+ return contrast_patch_dispatch(argc, argv, IMPL_PREPEND_INSTANCE, object);
350
+ }
351
+
352
+ VALUE contrast_prepend_singleton_patch(const int argc, const VALUE *argv,
353
+ const VALUE object) {
354
+ return contrast_patch_dispatch(argc, argv, IMPL_PREPEND_SINGLETON, object);
344
355
  }
345
356
 
346
357
  VALUE contrast_patch_define_method(const VALUE self, const VALUE clazz, const VALUE method_policy,
@@ -403,26 +414,37 @@ VALUE contrast_patch_define_method(const VALUE self, const VALUE clazz, const VA
403
414
  VALUE contrast_patch_prepend(const VALUE self, const VALUE originalModule,
404
415
  const VALUE method_policy) {
405
416
 
417
+ const VALUE instance = Qtrue;
418
+ const VALUE singleton = Qfalse;
406
419
  const VALUE original_method_name =
407
420
  rb_funcall(method_policy, rb_sym_method_name, 0);
408
421
  const VALUE is_private =
409
422
  rb_funcall(method_policy, rb_sym_private_method, 0);
410
423
  const VALUE is_instance_method =
411
424
  rb_funcall(method_policy, rb_sym_instance_method, 0);
412
- rb_funcall(patch_status, rb_sym_set_info_for, 5, originalModule,
413
- original_method_name, method_policy, Qtrue, Qnil);
425
+
426
+ // Set the value for instance or singleton method
427
+ if (RTEST(is_instance_method)){
428
+ rb_funcall(patch_status, rb_sym_set_info_for, 5, originalModule,
429
+ original_method_name, method_policy, instance, Qnil);
430
+
431
+ } else {
432
+ rb_funcall(patch_status, rb_sym_set_info_for, 5, originalModule,
433
+ original_method_name, method_policy, singleton, Qnil);
434
+ }
435
+
414
436
  VALUE module = rb_define_module_under(originalModule, "ContrastPrepend");
415
437
  VALUE str = rb_funcall(original_method_name, rb_sym_cs_to_s, 0);
416
438
  char *cMethodName = StringValueCStr(str);
417
439
  if (RTEST(is_instance_method)) {
418
440
  if (RTEST(is_private)) {
419
441
  rb_define_private_method(module, cMethodName,
420
- contrast_prepend_patch, -1);
442
+ contrast_prepend_instance_patch, -1);
421
443
  } else {
422
- rb_define_method(module, cMethodName, contrast_prepend_patch, -1);
444
+ rb_define_method(module, cMethodName, contrast_prepend_instance_patch, -1);
423
445
  }
424
446
  } else {
425
- rb_define_singleton_method(module, cMethodName, contrast_prepend_patch,
447
+ rb_define_singleton_method(module, cMethodName, contrast_prepend_singleton_patch,
426
448
  -1);
427
449
  }
428
450
  rb_prepend_module(originalModule, module);
@@ -146,8 +146,11 @@ VALUE contrast_alias_instance_patch(const int argc, const VALUE *argv,
146
146
  VALUE contrast_alias_singleton_patch(const int argc, const VALUE *argv,
147
147
  const VALUE object);
148
148
 
149
- VALUE contrast_prepend_patch(const int argc, const VALUE *argv,
150
- const VALUE object);
149
+ VALUE contrast_prepend_instance_patch(const int argc, const VALUE *argv,
150
+ const VALUE object);
151
+
152
+ VALUE contrast_prepend_singleton_patch(const int argc, const VALUE *argv,
153
+ const VALUE object);
151
154
 
152
155
  /*
153
156
  * Patches a module's method by prepend:
@@ -54,7 +54,7 @@ module Contrast
54
54
 
55
55
  # These methods rely on the above being set. Don't move them!
56
56
  @event_id = Contrast::Agent::Assess::ContrastEvent.next_atomic_id
57
- @tags = Contrast::Agent::Assess::Tracker.properties(tagged)&.tags
57
+ @tags = Contrast::Agent::Assess::Tracker.properties(tagged)&.get_tags
58
58
  find_parent_events!(policy_node, object, ret, args)
59
59
  snapshot!(object, ret, args)
60
60
  capture_stacktrace!
@@ -35,7 +35,7 @@ module Contrast
35
35
  if object
36
36
  @object = Contrast::Utils::ClassUtil.to_contrast_string(object)
37
37
  @object_type = object.cs__class.cs__name
38
- @tags = Contrast::Agent::Assess::Tracker.properties(object)&.tags
38
+ @tags = Contrast::Agent::Assess::Tracker.properties(object)&.get_tags
39
39
  else
40
40
  @object = Contrast::Utils::ObjectShare::NIL_STRING
41
41
  @object_type = nil.cs__class.cs__name
@@ -24,6 +24,8 @@ module Contrast
24
24
  # the name of the method to taint, mapped to the properties it
25
25
  # should apply
26
26
  def create_sources klass, tainted_columns
27
+ return unless Contrast::ASSESS.require_dynamic_sources?
28
+
27
29
  class_name = klass.cs__name
28
30
  instance_methods = klass.instance_methods
29
31
  instance_methods.concat(klass.private_instance_methods)
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/components/logger'
5
+ require 'contrast/utils/lru_cache'
5
6
 
6
7
  module Contrast
7
8
  module Agent
@@ -12,6 +13,7 @@ module Contrast
12
13
  include Contrast::Components::Logger::InstanceMethods
13
14
  extend Contrast::Components::Logger::InstanceMethods
14
15
 
16
+ @lru_cache = Contrast::Utils::LRUCache.new
15
17
  UNDUPLICABLE_MODULES = [
16
18
  Enumerator # dup'ing results in 'can't copy execution context'
17
19
  ].cs__freeze
@@ -70,16 +72,23 @@ module Contrast
70
72
 
71
73
  def append_object_details preshift, initializing, object
72
74
  can = can_dup?(initializing, object)
73
- preshift.object = can ? object.dup : object
75
+ preshift.object = if @lru_cache.key?(object.__id__) && !Contrast::Agent::Assess::Tracker.tracked?(object)
76
+ @lru_cache[object.__id__]
77
+ else
78
+ can ? object.dup : object
79
+ end
74
80
  preshift.object_length = if Contrast::Utils::DuckUtils.quacks_to?(preshift.object, :length)
75
81
  object.length
76
82
  else
77
83
  0
78
84
  end
85
+
79
86
  return unless can
87
+
80
88
  return unless Contrast::Agent::Assess::Tracker.tracked?(object)
81
89
 
82
90
  Contrast::Agent::Assess::Tracker.copy(object, preshift.object)
91
+ @lru_cache[object.__id__] = object
83
92
  end
84
93
 
85
94
  def append_arg_details preshift, args
@@ -88,15 +97,19 @@ module Contrast
88
97
  preshift.arg_lengths = Array.new(args_length)
89
98
  idx = 0
90
99
  while idx < args_length
91
- original_arg = args[idx]
92
- p_arg = can_dup?(false, original_arg) ? original_arg.dup : original_arg
100
+ or_arg = args[idx]
101
+ p_arg = if @lru_cache.key?(or_arg.__id__)
102
+ @lru_cache[or_arg.__id__]
103
+ else
104
+ can_dup?(false, or_arg) ? or_arg.dup : or_arg
105
+ end
93
106
  preshift.args[idx] = p_arg
94
107
  preshift.arg_lengths[idx] = Contrast::Utils::DuckUtils.quacks_to?(p_arg, :length) ? p_arg.length : 0
95
108
  idx += 1
96
- next if p_arg.__id__ == original_arg.__id__
97
- next unless Contrast::Agent::Assess::Tracker.tracked?(original_arg)
109
+ next if p_arg.__id__ == or_arg.__id__
98
110
 
99
- Contrast::Agent::Assess::Tracker.copy(original_arg, p_arg)
111
+ Contrast::Agent::Assess::Tracker.copy(or_arg, p_arg)
112
+ @lru_cache[p_arg.__id__] = p_arg
100
113
  end
101
114
  end
102
115
  end
@@ -13,6 +13,8 @@ module Contrast
13
13
  class DatabaseWrite < Contrast::Agent::Assess::Policy::Propagator::Base
14
14
  class << self
15
15
  def propagate propagation_node, preshift, target
16
+ return unless Contrast::ASSESS.require_dynamic_sources?
17
+
16
18
  class_type = preshift.object.cs__class
17
19
  class_name = class_type.cs__name
18
20
  tainted_columns = {}
@@ -14,7 +14,7 @@ module Contrast
14
14
  # specifically for those methods which result in the trigger of a
15
15
  # vulnerability (indicate points in the application where uncontrolled
16
16
  # user input can do damage).
17
- class TriggerNode < PolicyNode
17
+ class TriggerNode < PolicyNode # rubocop:disable Metrics/ClassLength
18
18
  JSON_BAD_VALUE = 'bad_value'
19
19
  JSON_GOOD_VALUE = 'good_value'
20
20
  JSON_DISALLOWED_TAGS = 'disallowed_tags'
@@ -104,8 +104,7 @@ module Contrast
104
104
 
105
105
  properties = Contrast::Agent::Assess::Tracker.properties(source)
106
106
  # find the ranges that violate the rule (untrusted, etc)
107
- vulnerable_ranges = ranges_with_all_tags(Contrast::Utils::StringUtils.ret_length(source), properties,
108
- required_tags)
107
+ vulnerable_ranges = ranges_with_all_tags(properties, required_tags)
109
108
  # if there aren't any vulnerable ranges, nope out
110
109
  return false if vulnerable_ranges.empty?
111
110
 
@@ -170,49 +169,56 @@ module Contrast
170
169
 
171
170
  tags.each do |tag|
172
171
  raise(ArgumentError, "Rule #{ rule_id } had an invalid tag. #{ tag } is not a known value.") unless
173
- Contrast::Api::Decorators::TraceTaintRangeTags::VALID_TAGS.include?(tag) ||
174
- Contrast::Api::Decorators::TraceTaintRangeTags::VALID_SOURCE_TAGS.include?(tag)
172
+ Contrast::Api::Decorators::TraceTaintRangeTags::VALID_TAGS.include?(tag) ||
173
+ Contrast::Api::Decorators::TraceTaintRangeTags::VALID_SOURCE_TAGS.include?(tag)
175
174
  end
176
175
  end
177
176
 
178
177
  # Find the ranges that satisfy all of the given tags.
179
178
  #
180
- # @param length [Integer] the length of the object which may have the
181
- # given tags -- used as the maximum index to search for all of the
182
- # tags.
183
179
  # @param properties [Contrast::Agent::Assess::Properties] the
184
180
  # properties to check for the tags
185
181
  # @param required_tags [Set<String>] the list of tags on which to match
186
182
  # @return [Array<Contrast::Agent::Assess::Tag>] the ranges satisfied
187
183
  # by the given conditions
188
- def ranges_with_all_tags length, properties, required_tags
184
+ def ranges_with_all_tags properties, required_tags
189
185
  return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless matches_tags?(properties, required_tags)
190
186
 
191
- ranges = []
192
187
  chunking = false
188
+ ranges = []
189
+ # find the start and end range of required tags:
190
+ search_ranges = find_required_ranges properties, required_tags
191
+ start_range = search_ranges.first
192
+ end_range = search_ranges.last + 1
193
+
193
194
  # find all the indicies on the source that have all the given tags
194
- (0..length).each do |idx|
195
- tags_at = properties.tags_at(idx).to_a
195
+ while start_range < end_range
196
+
197
+ tags_at = properties.tags_at(start_range).to_a
196
198
  # only those that have all the required tags in the tags_at
197
199
  # satisfy the requirement
198
200
  satisfied = tags_at.any? && required_tags.all? { |tag| tags_at.any? { |found| found.label == tag } }
199
201
  # if this range matches all the required tags and we're already
200
202
  # chunking, meaning the previous range matched, do nothing
201
- next if satisfied && chunking
203
+ if satisfied && chunking
204
+ start_range += 1
205
+ next
206
+ end
202
207
 
203
208
  # if we are satisfied and we were not chunking, this represents
204
209
  # the start of the next range, so create a new entry.
205
210
  if satisfied
206
- ranges << Contrast::Agent::Assess::Tag.new('required', 0, idx)
211
+ ranges << Contrast::Agent::Assess::Tag.new('required', 0, start_range)
207
212
  chunking = true
208
- # if we are chunking and not satisfied, this represents the end
209
- # of the range, meaning the last index is what satisfied the
210
- # range. Because the range is exclusive end, we can just use this
211
- # index.
213
+ # if we are chunking and not satisfied, this represents the end
214
+ # of the range, meaning the last index is what satisfied the
215
+ # range. Because the range is exclusive end, we can just use this
216
+ # index.
212
217
  elsif chunking
213
- ranges[-1]&.update_end(idx)
218
+ ranges[-1]&.update_end(start_range)
214
219
  chunking = false
215
220
  end
221
+ start_range += 1
216
222
  end
217
223
  ranges
218
224
  end
@@ -265,6 +271,33 @@ module Contrast
265
271
 
266
272
  true
267
273
  end
274
+
275
+ # Range finder helper for #ranges_with_all_tags
276
+ #
277
+ # @param properties [Contrast::Agent::Assess::Properties] the properties to check for the tags
278
+ # @param required_tags [Set<String>] the list of tags on which to match
279
+ # @return [Array] of required tags ranges to search
280
+ def find_required_ranges properties, required_tags
281
+ start_range = 0
282
+ end_range = 0
283
+ required_tags_arr = required_tags.to_a
284
+ idx = 0
285
+
286
+ while idx < required_tags_arr.length
287
+ # find the start and end range of required tags:
288
+ start_temp = properties.fetch_tag(required_tags_arr[idx])[0].start_idx
289
+ end_temp = properties.fetch_tag(required_tags_arr[idx])[0].end_idx
290
+ # first iteration only
291
+ start_range = start_temp if idx.zero?
292
+ end_range = end_temp if idx.zero?
293
+
294
+ # find the tag with smallest ranges
295
+ start_range = start_temp if start_range < start_temp
296
+ end_range = end_temp if end_range > end_temp
297
+ idx += 1
298
+ end
299
+ [start_range, end_range]
300
+ end
268
301
  end
269
302
  end
270
303
  end
@@ -125,6 +125,27 @@ module Contrast
125
125
  tags[label] = tag_ranges
126
126
  end
127
127
 
128
+ # Returns a list of all current tags.
129
+ #
130
+ # @return [Hash<Contrast::Agent::Assess::Tag>]
131
+ def get_tags # rubocop:disable Naming/AccessorMethodName
132
+ return Contrast::Utils::ObjectShare::EMPTY_HASH unless tracked?
133
+
134
+ tags
135
+ end
136
+
137
+ # We'll use this as a helper method to retrieve tags from the hash.
138
+ # Because the hash auto-populates an empty array when we try to
139
+ # access a tag in it, we cannot use the [] method without side
140
+ # effect. To get around this, we'll use a fetch work around.
141
+ #
142
+ # @param label [Symbol] the label to look up
143
+ # @return [Array<Contrast::Agent::Assess::Tag>] all the tags with
144
+ # that label
145
+ def fetch_tag label
146
+ get_tags.fetch(label, nil) if tracked?
147
+ end
148
+
128
149
  # Remove all tags with a given label
129
150
  def delete_tags label
130
151
  tags.delete(label) if tracked?
@@ -152,18 +173,6 @@ module Contrast
152
173
  tags.delete_if { |_, value| value.empty? }
153
174
  end
154
175
 
155
- # We'll use this as a helper method to retrieve tags from the hash.
156
- # Because the hash auto-populates an empty array when we try to
157
- # access a tag in it, we cannot use the [] method without side
158
- # effect. To get around this, we'll use a fetch work around.
159
- #
160
- # @param label [Symbol] the label to look up
161
- # @return [Array<Contrast::Agent::Assess::Tag>] all the tags with
162
- # that label
163
- def fetch_tag label
164
- tags.fetch(label, nil) if tracked?
165
- end
166
-
167
176
  # Remove all tags within the given ranges.
168
177
  # This does not delete an entire tag if part of that tag is
169
178
  # outside this range, meaning we may reduce sizes of tags
@@ -218,19 +227,6 @@ module Contrast
218
227
  end
219
228
  end
220
229
 
221
- # Because of the auto-fill thing, we should not allow direct access to
222
- # the tags hash. Instead, the methods above should be used to do
223
- # operations like add, delete, and fetch.
224
- #
225
- # CONTRAST-22914
226
- # please do NOT expose this w/ an attr_reader / accessor. there are
227
- # helper methods in this class that safely access the hash. the tags
228
- # method is private to avoid the side effect of a direct lookup with
229
- # `[]` adding an empty array to the hash.
230
- def tags
231
- @_tags ||= Hash.new { |h, k| h[k] = [] }
232
- end
233
-
234
230
  # Remove the tag ranges covering the given range
235
231
  def remove_tags range
236
232
  return unless tracked?
@@ -334,6 +330,19 @@ module Contrast
334
330
 
335
331
  private
336
332
 
333
+ # Because of the auto-fill thing, we should not allow direct access to
334
+ # the tags hash. Instead, the methods above should be used to do
335
+ # operations like add, delete, and fetch.
336
+ #
337
+ # CONTRAST-22914
338
+ # please do NOT expose this w/ an attr_reader / accessor. there are
339
+ # helper methods in this class that safely access the hash. the tags
340
+ # method is private to avoid the side effect of a direct lookup with
341
+ # `[]` adding an empty array to the hash.
342
+ def tags
343
+ @_tags ||= Hash.new { |h, k| h[k] = [] }
344
+ end
345
+
337
346
  # Given a tag, compare it to a given range and, if any part of that tag is within the range, return a new tag
338
347
  # covering the union of the original tag and the range. This new tag will start at the
339
348
  # max(tag.start, range.start) and end at min(tag.end, range.end)
@@ -35,6 +35,12 @@ module Contrast
35
35
  end
36
36
  end
37
37
 
38
+ def validate
39
+ return if class_name
40
+
41
+ raise(ArgumentError, "#{ @node_class } #{ id } did not have a proper class name. Unable to create.")
42
+ end
43
+
38
44
  def module_names
39
45
  @_module_names ||= Set.new(deadzones.map(&:class_name))
40
46
  end
@@ -46,7 +46,6 @@ module Contrast
46
46
  path_part = "cs__assess_#{ p }"
47
47
  Contrast::Extension::Assess::InstrumentHelper.instrument "#{ path_part }/#{ path_part }"
48
48
  end
49
- Contrast::Extension::Assess::InstrumentHelper.instrument 'cs__protect_kernel/cs__protect_kernel'
50
49
  true
51
50
  end
52
51
  end
@@ -81,15 +81,21 @@ module Contrast
81
81
  deadzone_node = find_method_node(module_policy.deadzone_nodes, method_name, instance_method)
82
82
  method_visibility = find_visibility(source_node, propagation_node, trigger_node, protect_node,
83
83
  inventory_node, deadzone_node)
84
- 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)
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
93
99
  end
94
100
 
95
101
  def find_method_node nodes, method_name, is_instance_method
@@ -103,6 +109,45 @@ module Contrast
103
109
  def find_visibility *nodes
104
110
  nodes.find { |node| node }&.method_visibility
105
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
106
151
  end
107
152
  end
108
153
  end
@@ -337,13 +337,13 @@ module Contrast
337
337
  # :prepend -> prepend instance method of module
338
338
  # [prepending singleton is easily supported too, just not implemented yet.]
339
339
  # @return [Symbol] new alias for the underlying method (presumably, so the patched method can call it)
340
- def register_c_patch target_module_name, unbound_method, impl = :alias_instance
340
+ def register_c_patch target_module_name, unbound_method, impl = :alias_instance # rubocop:disable Metrics/AbcSize
341
341
  # These could be set as AfterLoadPatches.
342
342
  method_name = unbound_method.name.to_sym # rubocop:disable Security/Module/Name -- ruby built in attribute.
343
343
  underlying_method_name = build_unbound_method_name(method_name).to_sym
344
344
 
345
345
  target_module = Module.cs__const_get(target_module_name)
346
-
346
+ target_module = target_module.cs__singleton_class if %i[prepend_singleton prepend].include? impl
347
347
  target_module = target_module.cs__singleton_class if %i[alias_singleton prepend].include? impl
348
348
 
349
349
  visibility = if target_module.private_instance_methods(false).include?(method_name)
@@ -370,14 +370,20 @@ module Contrast
370
370
  target_module.send(:define_method, method_name, unbound_method.bind(target_module))
371
371
  end
372
372
  target_module.send(visibility, method_name) # e.g., module.private(:my_method)
373
- when :prepend
374
- prepending_module = Module.new
375
- prepending_module.send(:define_method, method_name, unbound_method.bind(target_module))
376
- prepending_module.send(visibility, method_name)
373
+ when :prepend_instance, :prepend_singleton
374
+
375
+ unless target_module.instance_methods(false).include? underlying_method_name
376
+
377
+ prepending_module = Module.new
378
+ prepending_module.send(:define_method, method_name, unbound_method.bind(target_module))
379
+ prepending_module.send(visibility, method_name)
380
+
381
+ end
377
382
  # This prepends to the singleton class (it patches a class method)
378
383
  target_module.prepend prepending_module
379
384
  # rubocop:enable Performance/Kernel/DefineMethod
380
385
  end
386
+
381
387
  # Ougai::Logger.create_item_with_2args calls Hash#[]=, so we
382
388
  # can't invoke this logging method or we'll seg fault as we'd
383
389
  # change the method definition mid-call