contrast-agent 4.10.0 → 4.13.1
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__assess_module/cs__assess_module.c +48 -0
- data/ext/cs__assess_module/cs__assess_module.h +7 -0
- data/ext/cs__common/cs__common.c +24 -7
- data/ext/cs__common/cs__common.h +12 -2
- data/ext/cs__contrast_patch/cs__contrast_patch.c +48 -11
- data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -2
- data/ext/cs__os_information/cs__os_information.c +31 -0
- data/ext/cs__os_information/cs__os_information.h +7 -0
- data/ext/{cs__protect_kernel → cs__os_information}/extconf.rb +0 -0
- data/lib/contrast/agent/assess/contrast_event.rb +1 -1
- data/lib/contrast/agent/assess/contrast_object.rb +1 -4
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
- data/lib/contrast/agent/assess/policy/preshift.rb +25 -11
- data/lib/contrast/agent/assess/policy/propagation_method.rb +2 -116
- data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -0
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +4 -4
- data/lib/contrast/agent/assess/policy/propagator/remove.rb +4 -9
- data/lib/contrast/agent/assess/policy/source_method.rb +2 -71
- data/lib/contrast/agent/assess/policy/trigger_method.rb +4 -107
- data/lib/contrast/agent/assess/policy/trigger_node.rb +52 -19
- data/lib/contrast/agent/assess/property/tagged.rb +15 -132
- data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +2 -1
- data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
- data/lib/contrast/agent/middleware.rb +22 -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 +37 -238
- data/lib/contrast/agent/patching/policy/patcher.rb +3 -42
- data/lib/contrast/agent/request.rb +5 -3
- data/lib/contrast/agent/request_context.rb +32 -11
- data/lib/contrast/agent/request_handler.rb +7 -3
- data/lib/contrast/agent/rule_set.rb +2 -4
- data/lib/contrast/agent/scope.rb +32 -20
- data/lib/contrast/agent/startup_metrics_telemetry_event.rb +71 -0
- data/lib/contrast/agent/static_analysis.rb +4 -2
- data/lib/contrast/agent/telemetry.rb +129 -0
- data/lib/contrast/agent/telemetry_event.rb +34 -0
- data/lib/contrast/agent/thread_watcher.rb +43 -14
- data/lib/contrast/agent/tracepoint_hook.rb +11 -3
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +6 -1
- data/lib/contrast/components/api.rb +34 -0
- data/lib/contrast/components/app_context.rb +24 -0
- data/lib/contrast/components/assess.rb +7 -0
- data/lib/contrast/components/config.rb +90 -11
- data/lib/contrast/components/contrast_service.rb +6 -0
- data/lib/contrast/config/api_configuration.rb +22 -0
- data/lib/contrast/config/assess_configuration.rb +1 -0
- data/lib/contrast/config/env_variables.rb +25 -0
- data/lib/contrast/config/root_configuration.rb +1 -0
- data/lib/contrast/config/service_configuration.rb +2 -1
- data/lib/contrast/config.rb +1 -0
- data/lib/contrast/configuration.rb +3 -0
- data/lib/contrast/framework/manager.rb +14 -12
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
- data/lib/contrast/framework/rails/patch/support.rb +31 -29
- data/lib/contrast/logger/application.rb +4 -0
- data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
- data/lib/contrast/utils/assess/property/tagged_utils.rb +142 -0
- data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
- data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
- data/lib/contrast/utils/class_util.rb +58 -44
- data/lib/contrast/utils/exclude_key.rb +20 -0
- data/lib/contrast/utils/io_util.rb +42 -34
- data/lib/contrast/utils/lru_cache.rb +45 -0
- data/lib/contrast/utils/metrics_hash.rb +59 -0
- data/lib/contrast/utils/os.rb +23 -0
- data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
- data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
- data/lib/contrast/utils/requests_client.rb +150 -0
- data/lib/contrast/utils/ruby_ast_rewriter.rb +1 -1
- data/lib/contrast/utils/telemetry.rb +77 -0
- data/lib/contrast/utils/telemetry_identifier.rb +137 -0
- data/lib/contrast.rb +19 -1
- data/resources/assess/policy.json +12 -6
- data/resources/deadzone/policy.json +86 -5
- data/ruby-agent.gemspec +2 -1
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +32 -14
- data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
- data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
- data/lib/contrast/extension/protect/kernel.rb +0 -29
@@ -15,7 +15,7 @@ module Contrast
|
|
15
15
|
case ret
|
16
16
|
when Array
|
17
17
|
idx = 0
|
18
|
-
while idx < ret.
|
18
|
+
while idx < ret.length
|
19
19
|
return_value = ret[idx]
|
20
20
|
index = idx
|
21
21
|
idx += 1
|
@@ -34,7 +34,7 @@ module Contrast
|
|
34
34
|
|
35
35
|
def captures_tagger propagation_node, preshift, ret, _block
|
36
36
|
idx = 0
|
37
|
-
while idx < ret.
|
37
|
+
while idx < ret.length
|
38
38
|
return_value = ret[idx]
|
39
39
|
index = idx
|
40
40
|
idx += 1
|
@@ -48,7 +48,7 @@ module Contrast
|
|
48
48
|
|
49
49
|
def to_a_tagger propagation_node, preshift, ret, _block
|
50
50
|
idx = 0
|
51
|
-
while idx < ret.
|
51
|
+
while idx < ret.length
|
52
52
|
return_value = ret[idx]
|
53
53
|
index = idx
|
54
54
|
idx += 1
|
@@ -61,7 +61,7 @@ module Contrast
|
|
61
61
|
|
62
62
|
def values_at_tagger propagation_node, preshift, ret, _block
|
63
63
|
idx = 0
|
64
|
-
while idx < ret.
|
64
|
+
while idx < ret.length
|
65
65
|
return_value = ret[idx]
|
66
66
|
return_index = idx
|
67
67
|
idx += 1
|
@@ -26,7 +26,6 @@ module Contrast
|
|
26
26
|
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
27
27
|
|
28
28
|
source_string = source.is_a?(String) ? source : source.to_s
|
29
|
-
|
30
29
|
# If the lengths are the same, we should just copy the tags because nothing was removed, but a new
|
31
30
|
# instance could have been created. copy_from will handle the case where the source is the target.
|
32
31
|
if source_string.length == target.length
|
@@ -34,10 +33,7 @@ module Contrast
|
|
34
33
|
return
|
35
34
|
end
|
36
35
|
|
37
|
-
source_chars = source_string.chars
|
38
36
|
source_idx = 0
|
39
|
-
|
40
|
-
target_chars = target.chars
|
41
37
|
target_idx = 0
|
42
38
|
|
43
39
|
remove_ranges = []
|
@@ -45,10 +41,9 @@ module Contrast
|
|
45
41
|
|
46
42
|
# loop over the target, the result of the delete every range of characters that it differs from the
|
47
43
|
# source represents a section that was deleted. these sections need to have their tags updated
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
source_char = source_chars[source_idx]
|
44
|
+
while target_idx < target.length
|
45
|
+
target_char = target[target_idx]
|
46
|
+
source_char = source_string[source_idx]
|
52
47
|
if target_char == source_char
|
53
48
|
target_idx += 1
|
54
49
|
if start
|
@@ -63,7 +58,7 @@ module Contrast
|
|
63
58
|
|
64
59
|
# once we're done looping over the target, anything left over is extra from the source that was
|
65
60
|
# deleted. tags applying to it need to be removed.
|
66
|
-
remove_ranges << (source_idx...
|
61
|
+
remove_ranges << (source_idx...source_string.length) if source_idx != source_string.length
|
67
62
|
|
68
63
|
# handle deleting the removed ranges
|
69
64
|
properties.delete_tags_at_ranges(remove_ranges)
|
@@ -6,6 +6,7 @@ require 'contrast/agent/assess/policy/source_validation/source_validation'
|
|
6
6
|
require 'contrast/components/logger'
|
7
7
|
require 'contrast/utils/object_share'
|
8
8
|
require 'contrast/utils/sha256_builder'
|
9
|
+
require 'contrast/utils/assess/source_method_utils'
|
9
10
|
|
10
11
|
module Contrast
|
11
12
|
module Agent
|
@@ -16,6 +17,7 @@ module Contrast
|
|
16
17
|
# used in Assess vulnerability detection.
|
17
18
|
module SourceMethod
|
18
19
|
extend Contrast::Components::Logger::InstanceMethods
|
20
|
+
extend Contrast::Utils::Assess::SourceMethodUtils
|
19
21
|
|
20
22
|
PARAMETER_TYPE = 'PARAMETER'
|
21
23
|
PARAMETER_KEY_TYPE = 'PARAMETER_KEY'
|
@@ -133,15 +135,6 @@ module Contrast
|
|
133
135
|
!Contrast::Agent::Assess::Tracker.trackable?(key)
|
134
136
|
end
|
135
137
|
|
136
|
-
# Safely duplicate the target, or return nil
|
137
|
-
#
|
138
|
-
# @param target [Object] the thing to check for duplication
|
139
|
-
def safe_dup target
|
140
|
-
target.dup
|
141
|
-
rescue StandardError => _e
|
142
|
-
nil
|
143
|
-
end
|
144
|
-
|
145
138
|
# Hash is designed to keep one instance of the string key in it. We need to remove the existing one and
|
146
139
|
# replace it with our new tracked one.
|
147
140
|
def handle_hash_key target, to_replace
|
@@ -174,68 +167,6 @@ module Contrast
|
|
174
167
|
properties.build_event(source_node, target, object, ret, args, source_type, source_name)
|
175
168
|
end
|
176
169
|
|
177
|
-
# Find the name of the source
|
178
|
-
#
|
179
|
-
# @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
|
180
|
-
# event
|
181
|
-
# @param object [Object] the Object on which the method was invoked
|
182
|
-
# @param ret [Object] the Return of the invoked method
|
183
|
-
# @param args [Array<Object>] the Arguments with which the method was invoked
|
184
|
-
# @return [String, nil] the human readable name of the target to which this source event applies, or nil if
|
185
|
-
# none provided by the node
|
186
|
-
def determine_source_name source_node, object, ret, *args
|
187
|
-
return source_node.get_property('dynamic_source_name') if source_node.type == 'UNTRUSTED_DATABASE'
|
188
|
-
|
189
|
-
source_node_source = source_node.sources[0]
|
190
|
-
case source_node_source
|
191
|
-
when nil
|
192
|
-
nil
|
193
|
-
when Contrast::Utils::ObjectShare::RETURN_KEY
|
194
|
-
ret
|
195
|
-
when Contrast::Utils::ObjectShare::OBJECT_KEY
|
196
|
-
object
|
197
|
-
else
|
198
|
-
args[source_node_source]
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
# Determine if we should analyze this method invocation for a Source or not. We should if we have enough
|
203
|
-
# information to build the context of this invocation, we're not disabled, and we can't immediately
|
204
|
-
# determine the invocation was done safely.
|
205
|
-
#
|
206
|
-
# @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that applies to the
|
207
|
-
# method being called
|
208
|
-
# @param object [Object] the Object on which the method was invoked
|
209
|
-
# @param ret [Object] the Return of the invoked method
|
210
|
-
# @param args [Array<Object>] the Arguments with which the method was invoked
|
211
|
-
# @return [boolean] if the invocation of this method should be analyzed
|
212
|
-
def analyze? method_policy, object, ret, args
|
213
|
-
return false unless method_policy&.source_node
|
214
|
-
return false unless ::Contrast::ASSESS.enabled?
|
215
|
-
return false unless Contrast::Agent::REQUEST_TRACKER.current&.analyze_request?
|
216
|
-
|
217
|
-
!safe_invocation?(method_policy.source_node, object, ret, args)
|
218
|
-
end
|
219
|
-
|
220
|
-
# Determine if the method was invoked safely.
|
221
|
-
#
|
222
|
-
# @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
|
223
|
-
# event
|
224
|
-
# @param _object [Object] the Object on which the method was invoked
|
225
|
-
# @param _ret [Object] the Return of the invoked method
|
226
|
-
# @param args [Array<Object>] the Arguments with which the method was invoked
|
227
|
-
# @return [boolean] if the invocation of this method was safe
|
228
|
-
def safe_invocation? source_node, _object, _ret, args
|
229
|
-
# According the the Rack Specification https://github.com/rack/rack/blob/master/SPEC.rdoc, any header
|
230
|
-
# from the Request will start with HTTP_. As such, only Headers with that key should be considered for
|
231
|
-
# tracking, as the others have come from the Framework or Middleware stashing in the ENV. Rails, for
|
232
|
-
# instance, uses action_dispatch. to store several values. Technically, you can't call
|
233
|
-
# Rack::Request#get_header without a parameter, and that parameter should be a String, but trust no one.
|
234
|
-
source_node.id == 'Assess:Source:Rack::Request::Env#get_header' &&
|
235
|
-
args&.any? &&
|
236
|
-
!args[0].to_s.start_with?('HTTP_')
|
237
|
-
end
|
238
|
-
|
239
170
|
# Find the literal target of the propagation
|
240
171
|
#
|
241
172
|
# @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
|
@@ -6,6 +6,7 @@ require 'contrast/agent/assess/policy/trigger_validation/trigger_validation'
|
|
6
6
|
require 'contrast/components/logger'
|
7
7
|
require 'contrast/utils/object_share'
|
8
8
|
require 'contrast/utils/sha256_builder'
|
9
|
+
require 'contrast/utils/assess/trigger_method_utils'
|
9
10
|
|
10
11
|
module Contrast
|
11
12
|
module Agent
|
@@ -17,6 +18,7 @@ module Contrast
|
|
17
18
|
# report is issued to the Service.
|
18
19
|
module TriggerMethod
|
19
20
|
extend Contrast::Components::Logger::InstanceMethods
|
21
|
+
extend Contrast::Utils::Assess::TriggerMethodUtils
|
20
22
|
|
21
23
|
# The level of TeamServer compliance our traces meet when in the abnormal condition of being dataflow rules
|
22
24
|
# without routes.
|
@@ -84,6 +86,8 @@ module Contrast
|
|
84
86
|
# activity message does not exist, b/c we're invoked outside of a request context, build an activity and
|
85
87
|
# immediately report it with the finding.
|
86
88
|
#
|
89
|
+
# TODO: RUBY-1351
|
90
|
+
#
|
87
91
|
# @param finding [Contrast::Api::Dtm::Finding] the Finding to report.
|
88
92
|
# @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
|
89
93
|
def report_finding finding, request = nil
|
@@ -108,64 +112,10 @@ module Contrast
|
|
108
112
|
|
109
113
|
private
|
110
114
|
|
111
|
-
# A request is reportable if it is not from ActionController::Live
|
112
|
-
#
|
113
|
-
# @param env [Hash] the env of the Request
|
114
|
-
# @return [Boolean]
|
115
|
-
def reportable? env
|
116
|
-
!(defined?(ActionController::Live) &&
|
117
|
-
env &&
|
118
|
-
env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live))
|
119
|
-
end
|
120
|
-
|
121
|
-
# Find the request for this finding. This assumes, for now, that if there is an active request, then that
|
122
|
-
# is the request to report. Otherwise, we'll use the first request found in the events of the
|
123
|
-
# source_object.
|
124
|
-
#
|
125
|
-
# @param source [Object,nil] some Object used as the source of a trigger event
|
126
|
-
# @return [Contrast::Agent::Request,nil] the request from which the dataflow on the request originated.
|
127
|
-
def find_request source
|
128
|
-
return Contrast::Agent::REQUEST_TRACKER.current.request if Contrast::Agent::REQUEST_TRACKER.current
|
129
|
-
return unless (properties = Contrast::Agent::Assess::Tracker.properties(source))
|
130
|
-
|
131
|
-
properties.events.each do |event|
|
132
|
-
next unless event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent)
|
133
|
-
|
134
|
-
return event.request if event.request
|
135
|
-
end
|
136
|
-
nil
|
137
|
-
end
|
138
|
-
|
139
115
|
def settings
|
140
116
|
Contrast::Agent::FeatureState.instance
|
141
117
|
end
|
142
118
|
|
143
|
-
# This is our method that actually checks the taint on the object our trigger_node targets.
|
144
|
-
#
|
145
|
-
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
|
146
|
-
# trigger event
|
147
|
-
# @param source [Object] the source of the Trigger Event
|
148
|
-
# @param object [Object] the Object on which the method was invoked
|
149
|
-
# @param ret [Object] the Return of the invoked method
|
150
|
-
# @param args [Array<Object>] the Arguments with which the method was invoked
|
151
|
-
def apply_trigger trigger_node, source, object, ret, *args
|
152
|
-
return unless trigger_node
|
153
|
-
return if trigger_node.rule_disabled?
|
154
|
-
return if trigger_node.dataflow? && source.nil?
|
155
|
-
|
156
|
-
if trigger_node.regexp_rule?
|
157
|
-
apply_regexp_rule(trigger_node, source, object, ret, *args)
|
158
|
-
elsif trigger_node.custom_trigger?
|
159
|
-
trigger_node.apply_custom_trigger(trigger_node, source, object, ret, *args)
|
160
|
-
elsif trigger_node.dataflow?
|
161
|
-
apply_dataflow_rule(trigger_node, source, object, ret, *args)
|
162
|
-
else # trigger rule - just calling the method is dangerous
|
163
|
-
build_finding(trigger_node, source, object, ret, *args)
|
164
|
-
end
|
165
|
-
rescue StandardError => e
|
166
|
-
logger.warn('Unable to apply trigger', e, node_id: trigger_node.id)
|
167
|
-
end
|
168
|
-
|
169
119
|
# Given the marker from the trigger_node (the pointer indicating the entity from which the taint
|
170
120
|
# originated), return the entity on which this trigger needs to operate.
|
171
121
|
#
|
@@ -199,59 +149,6 @@ module Contrast
|
|
199
149
|
end
|
200
150
|
end
|
201
151
|
|
202
|
-
# This is our method that actually checks the taint on the object our trigger_node targets for our Regexp
|
203
|
-
# based rules.
|
204
|
-
#
|
205
|
-
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
|
206
|
-
# trigger event
|
207
|
-
# @param source [Object] the source of the Trigger Event
|
208
|
-
# @param object [Object] the Object on which the method was invoked
|
209
|
-
# @param ret [Object] the Return of the invoked method
|
210
|
-
# @param args [Array<Object>] the Arguments with which the method was invoked
|
211
|
-
def apply_regexp_rule trigger_node, source, object, ret, *args
|
212
|
-
return unless source.is_a?(String)
|
213
|
-
return if trigger_node.good_value && source.match?(trigger_node.good_value)
|
214
|
-
return if trigger_node.bad_value && source !~ trigger_node.bad_value
|
215
|
-
|
216
|
-
build_finding(trigger_node, source, object, ret, *args)
|
217
|
-
end
|
218
|
-
|
219
|
-
# This is our method that actually checks the taint on the object our trigger_node targets for our Dataflow
|
220
|
-
# based rules.
|
221
|
-
#
|
222
|
-
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
|
223
|
-
# trigger event
|
224
|
-
# @param source [Object] the source of the Trigger Event
|
225
|
-
# @param object [Object] the Object on which the method was invoked
|
226
|
-
# @param ret [Object] the Return of the invoked method
|
227
|
-
# @param args [Array<Object>] the Arguments with which the method was invoked
|
228
|
-
def apply_dataflow_rule trigger_node, source, object, ret, *args
|
229
|
-
return unless source
|
230
|
-
|
231
|
-
if Contrast::Agent::Assess::Tracker.trackable?(source)
|
232
|
-
return unless Contrast::Agent::Assess::Tracker.tracked?(source)
|
233
|
-
return unless trigger_node.violated?(source)
|
234
|
-
|
235
|
-
build_finding(trigger_node, source, object, ret, *args)
|
236
|
-
elsif Contrast::Utils::DuckUtils.iterable_hash?(source)
|
237
|
-
source.each_pair do |key, value|
|
238
|
-
apply_dataflow_rule(trigger_node, key, object, ret, *args)
|
239
|
-
apply_dataflow_rule(trigger_node, value, object, ret, *args)
|
240
|
-
end
|
241
|
-
elsif Contrast::Utils::DuckUtils.iterable_enumerable?(source)
|
242
|
-
source.each do |value|
|
243
|
-
apply_dataflow_rule(trigger_node, value, object, ret, *args)
|
244
|
-
end
|
245
|
-
else
|
246
|
-
logger.debug('Trigger source is untrackable. Unable to inspect.',
|
247
|
-
node_id: trigger_node.id,
|
248
|
-
source_id: source.__id__,
|
249
|
-
source_type: source.cs__class.to_s,
|
250
|
-
frozen: source.cs__frozen?)
|
251
|
-
logger.trace(source.to_s[0..99])
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
152
|
def append_events finding, trigger_node, source, object, ret, args
|
256
153
|
append_from_source(finding, source)
|
257
154
|
finding.events << Contrast::Agent::Assess::Events::EventFactory.build(trigger_node, source, object, ret,
|
@@ -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
|
@@ -5,6 +5,7 @@ require 'contrast/agent/assess/tag'
|
|
5
5
|
require 'contrast/utils/object_share'
|
6
6
|
require 'contrast/utils/string_utils'
|
7
7
|
require 'contrast/utils/tag_util'
|
8
|
+
require 'contrast/utils/assess/property/tagged_utils'
|
8
9
|
|
9
10
|
module Contrast
|
10
11
|
module Agent
|
@@ -13,6 +14,7 @@ module Contrast
|
|
13
14
|
# This module serves to hold the functionality required for the
|
14
15
|
# management of our dataflow tags.
|
15
16
|
module Tagged
|
17
|
+
include Contrast::Utils::Assess::TaggedUtils
|
16
18
|
# Is any tag present?
|
17
19
|
# Creating Tags is expensive and we check for Tags all the time on
|
18
20
|
# untracked things. ALWAYS!!! call this method before checking if an
|
@@ -45,125 +47,6 @@ module Contrast
|
|
45
47
|
false
|
46
48
|
end
|
47
49
|
|
48
|
-
# Find all of the ranges that span a given index. This is used
|
49
|
-
# in propagation when we need to shift tags about. For instance, in
|
50
|
-
# the append method when we need to see if any tag at the end needs
|
51
|
-
# to be expanded out to the size of the new String.
|
52
|
-
#
|
53
|
-
# Note: Tags do not know their key, so this is only the range covered
|
54
|
-
#
|
55
|
-
# @param idx [Integer] the index to check for tags
|
56
|
-
# @return [Array<Contrast::Agent::Assess::Tag>] a set of all the Tags
|
57
|
-
# covering the given index. This is only the ranges, not the names.
|
58
|
-
def tags_at idx
|
59
|
-
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?
|
60
|
-
|
61
|
-
at = []
|
62
|
-
tags.each_value do |tag_array|
|
63
|
-
tag_array.each do |tag|
|
64
|
-
if tag.covers?(idx)
|
65
|
-
at << tag
|
66
|
-
elsif tag.above?(idx)
|
67
|
-
break
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
at
|
72
|
-
end
|
73
|
-
|
74
|
-
# given a range, select all tags in that range the selected tags are
|
75
|
-
# shifted such that the start index of the new tag (0) aligns with
|
76
|
-
# the given start index in the range
|
77
|
-
#
|
78
|
-
# current tags: 5-15
|
79
|
-
# range : 5-10
|
80
|
-
# result : 0-05
|
81
|
-
#
|
82
|
-
# Note that we disable Lint/DuplicateBranch in this branch in order
|
83
|
-
# list out all tag range cases in the proper order to make this
|
84
|
-
# easier to understand
|
85
|
-
#
|
86
|
-
# @param range [Range] the span to check, inclusive to exclusive
|
87
|
-
# @return [Hash{String => Contrast::Agent::Assess::Tag}] the hash of
|
88
|
-
# key to tags
|
89
|
-
def tags_at_range range
|
90
|
-
return Contrast::Utils::ObjectShare::EMPTY_HASH unless tracked?
|
91
|
-
|
92
|
-
at = Hash.new { |h, k| h[k] = [] }
|
93
|
-
tags.each_pair do |key, value|
|
94
|
-
add = nil
|
95
|
-
value.each do |tag|
|
96
|
-
within_range = resize_to_range(tag, range)
|
97
|
-
if within_range
|
98
|
-
add ||= []
|
99
|
-
add << within_range
|
100
|
-
end
|
101
|
-
end
|
102
|
-
next unless add&.any?
|
103
|
-
|
104
|
-
at[key] = add
|
105
|
-
end
|
106
|
-
at
|
107
|
-
end
|
108
|
-
|
109
|
-
# Given a tag name and range object, add a new tag to this
|
110
|
-
# collection. If the given range touches an existing tag,
|
111
|
-
# we'll combine the two, adjusting the existing one and
|
112
|
-
# dropping this new one.
|
113
|
-
#
|
114
|
-
# @param label [String] the name of the tag
|
115
|
-
# @param range [Range] the Range that the tag covers, inclusive to
|
116
|
-
# exclusive
|
117
|
-
def add_tag label, range
|
118
|
-
length = range.end - range.begin
|
119
|
-
tag = Contrast::Agent::Assess::Tag.new(label, length, range.begin)
|
120
|
-
existing = fetch_tag(label)
|
121
|
-
tags[label] = Contrast::Utils::TagUtil.ordered_merge(existing, tag)
|
122
|
-
end
|
123
|
-
|
124
|
-
def set_tags label, tag_ranges
|
125
|
-
tags[label] = tag_ranges
|
126
|
-
end
|
127
|
-
|
128
|
-
# Remove all tags with a given label
|
129
|
-
def delete_tags label
|
130
|
-
tags.delete(label) if tracked?
|
131
|
-
end
|
132
|
-
|
133
|
-
# Reset the tag hash
|
134
|
-
def clear_tags
|
135
|
-
tags.clear if tracked?
|
136
|
-
end
|
137
|
-
|
138
|
-
# Returns a list of all current tag labels, most likely to be used for
|
139
|
-
# a splat operation
|
140
|
-
def tag_keys
|
141
|
-
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?
|
142
|
-
|
143
|
-
tags.keys
|
144
|
-
end
|
145
|
-
|
146
|
-
# Calls merge to combine touching or overlapping tags
|
147
|
-
# Deletes empty tags
|
148
|
-
def cleanup_tags
|
149
|
-
return unless tracked?
|
150
|
-
|
151
|
-
Contrast::Utils::TagUtil.merge_tags(tags)
|
152
|
-
tags.delete_if { |_, value| value.empty? }
|
153
|
-
end
|
154
|
-
|
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
50
|
# Remove all tags within the given ranges.
|
168
51
|
# This does not delete an entire tag if part of that tag is
|
169
52
|
# outside this range, meaning we may reduce sizes of tags
|
@@ -218,19 +101,6 @@ module Contrast
|
|
218
101
|
end
|
219
102
|
end
|
220
103
|
|
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
104
|
# Remove the tag ranges covering the given range
|
235
105
|
def remove_tags range
|
236
106
|
return unless tracked?
|
@@ -334,6 +204,19 @@ module Contrast
|
|
334
204
|
|
335
205
|
private
|
336
206
|
|
207
|
+
# Because of the auto-fill thing, we should not allow direct access to
|
208
|
+
# the tags hash. Instead, the methods above should be used to do
|
209
|
+
# operations like add, delete, and fetch.
|
210
|
+
#
|
211
|
+
# CONTRAST-22914
|
212
|
+
# please do NOT expose this w/ an attr_reader / accessor. there are
|
213
|
+
# helper methods in this class that safely access the hash. the tags
|
214
|
+
# method is private to avoid the side effect of a direct lookup with
|
215
|
+
# `[]` adding an empty array to the hash.
|
216
|
+
def tags
|
217
|
+
@_tags ||= Hash.new { |h, k| h[k] = [] }
|
218
|
+
end
|
219
|
+
|
337
220
|
# 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
221
|
# covering the union of the original tag and the range. This new tag will start at the
|
339
222
|
# 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
|
@@ -60,7 +60,7 @@ module Contrast
|
|
60
60
|
|
61
61
|
digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec)
|
62
62
|
unless digest
|
63
|
-
logger.debug('Unable to resolve digest for gem spec', spec: spec.to_s)
|
63
|
+
logger.debug('Unable to resolve digest for gem spec', spec: spec.to_s) if logger.debug?
|
64
64
|
return
|
65
65
|
end
|
66
66
|
report_path = adjust_path_for_reporting(path, spec)
|
@@ -71,6 +71,7 @@ module Contrast
|
|
71
71
|
|
72
72
|
# Populate the library_usages field of the Activity message using the data stored in the @gemdigest_cache.
|
73
73
|
#
|
74
|
+
# TODO: RUBY-1355
|
74
75
|
# @param activity [Contrast::Api::Dtm::Activity] the message to which to append the usage data
|
75
76
|
def generate_library_usage activity
|
76
77
|
return unless enabled?
|