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.
- 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
|