contrast-agent 4.11.0 → 4.12.0

Sign up to get free protection for your applications and to get access to all the features.
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