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.
- checksums.yaml +4 -4
- data/ext/cs__common/cs__common.c +19 -7
- data/ext/cs__common/cs__common.h +4 -2
- data/ext/cs__contrast_patch/cs__contrast_patch.c +32 -10
- data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -2
- data/lib/contrast/agent/assess/contrast_event.rb +1 -1
- data/lib/contrast/agent/assess/contrast_object.rb +1 -1
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
- data/lib/contrast/agent/assess/policy/preshift.rb +19 -6
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -0
- data/lib/contrast/agent/assess/policy/trigger_node.rb +52 -19
- data/lib/contrast/agent/assess/property/tagged.rb +34 -25
- data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +0 -1
- data/lib/contrast/agent/patching/policy/method_policy.rb +54 -9
- data/lib/contrast/agent/patching/policy/patch.rb +12 -6
- data/lib/contrast/agent/patching/policy/patcher.rb +1 -1
- data/lib/contrast/agent/request_context.rb +24 -8
- data/lib/contrast/agent/rule_set.rb +2 -4
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +0 -1
- data/lib/contrast/components/assess.rb +7 -0
- data/lib/contrast/config/assess_configuration.rb +1 -0
- data/lib/contrast/utils/class_util.rb +60 -53
- data/lib/contrast/utils/lru_cache.rb +4 -2
- data/lib/contrast.rb +1 -1
- data/resources/assess/policy.json +12 -6
- data/resources/deadzone/policy.json +86 -5
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +9 -14
- data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
- data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
- data/ext/cs__protect_kernel/extconf.rb +0 -5
- data/lib/contrast/extension/protect/kernel.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a0a50913c5c680462f9afd79070ca1f3d46d09d66ffdb943b1dc778f84830d6
|
4
|
+
data.tar.gz: bb3254c6a2cdc2b4add4e09b4bf1ed63c6a640f0dd82824db2769b911aacf96b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78d5e66ee3ab7408e94348f6c2998e73cd78227a8b75cffa7add261b771092d470431ce8e44357dfb7d1a178e03e5033efc9f7a4c974a979f9dea88b2c55a3ec
|
7
|
+
data.tar.gz: d20f41b563778d84e63e4db7a9399e666a85fe1a2343772258f5248cd139f02e6a6bea33e51d9e5a0f954b1e2b4683ec08f3db7a52ab5a6990f7d725d2bc3dea
|
data/ext/cs__common/cs__common.c
CHANGED
@@ -73,11 +73,20 @@ VALUE contrast_register_singleton_patch(const char *module_name,
|
|
73
73
|
IMPL_ALIAS_SINGLETON);
|
74
74
|
}
|
75
75
|
|
76
|
-
VALUE
|
77
|
-
|
78
|
-
|
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
|
-
|
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
|
126
|
-
impl = ID2SYM(
|
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
|
-
|
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");
|
data/ext/cs__common/cs__common.h
CHANGED
@@ -6,7 +6,8 @@
|
|
6
6
|
typedef enum {
|
7
7
|
IMPL_ALIAS_INSTANCE,
|
8
8
|
IMPL_ALIAS_SINGLETON,
|
9
|
-
|
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
|
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
|
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
|
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
|
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
|
347
|
+
VALUE contrast_prepend_instance_patch(const int argc, const VALUE *argv,
|
342
348
|
const VALUE object) {
|
343
|
-
return contrast_patch_dispatch(argc, argv,
|
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
|
-
|
413
|
-
|
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
|
-
|
442
|
+
contrast_prepend_instance_patch, -1);
|
421
443
|
} else {
|
422
|
-
rb_define_method(module, cMethodName,
|
444
|
+
rb_define_method(module, cMethodName, contrast_prepend_instance_patch, -1);
|
423
445
|
}
|
424
446
|
} else {
|
425
|
-
rb_define_singleton_method(module, cMethodName,
|
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
|
150
|
-
|
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)&.
|
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)&.
|
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 =
|
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
|
-
|
92
|
-
p_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__ ==
|
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(
|
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(
|
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
|
-
|
174
|
-
|
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
|
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
|
-
|
195
|
-
|
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
|
-
|
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,
|
211
|
+
ranges << Contrast::Agent::Assess::Tag.new('required', 0, start_range)
|
207
212
|
chunking = true
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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(
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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 :
|
374
|
-
|
375
|
-
|
376
|
-
|
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
|