lib/contrast/agent/assess/contrast_event.rb in contrast-agent-4.4.1 vs lib/contrast/agent/assess/contrast_event.rb in contrast-agent-4.5.0
- old
+ new
@@ -1,6 +1,6 @@
-# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
+# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true
require 'contrast/utils/assess/tracking_util'
require 'contrast/utils/class_util'
require 'contrast/utils/duck_utils'
@@ -12,41 +12,31 @@
require 'contrast/agent/assess/contrast_object'
module Contrast
module Agent
module Assess
- # This class holds the data about an event in the application
- # We'll use it to build an event that TeamServer can consume if
- # the object to which this event belongs ends in a trigger.
+ # This class holds the data about an event in the application We'll use it to build an event that TeamServer can
+ # consume if the object to which this event belongs ends in a trigger.
#
# @attr_reader event_id [Integer] the atomic id of this event
- # @attr_reader policy_node [Contrast::Agent::Assess::Policy::PolicyNode]
- # the node that governs this event.
- # @attr_reader stack_trace [Array<String>] the execution stack at the
- # time the method for this event was invoked
- # @attr_reader time [Integer] the time, in epoch ms, when this event was
- # created
- # @attr_reader thread [Integer] the object id of the thread on which this
- # event was generated
- # @attr_reader object [Contrast::Agent::Assess::ContrastObject] the safe
- # representation of the Object on which the method was invoked
- # @attr_reader ret [Contrast::Agent::Assess::ContrastObject] the safe
- # representation of the Return of the invoked method
- # @attr_reader args [Array<Contrast::Agent::Assess::ContrastObject>] the
- # safe representation of the Arguments with which the method was invoked
+ # @attr_reader policy_node [Contrast::Agent::Assess::Policy::PolicyNode] the node that governs this event.
+ # @attr_reader stack_trace [Array<String>] the execution stack at the time the method for this event was invoked
+ # @attr_reader time [Integer] the time, in epoch ms, when this event was created
+ # @attr_reader thread [Integer] the object id of the thread on which this event was generated
+ # @attr_reader object [Contrast::Agent::Assess::ContrastObject] the safe representation of the Object on which
+ # the method was invoked
+ # @attr_reader ret [Contrast::Agent::Assess::ContrastObject] the safe representation of the Return of the invoked
+ # method
+ # @attr_reader args [Array<Contrast::Agent::Assess::ContrastObject>] the safe representation of the Arguments
+ # with which the method was invoked
class ContrastEvent
include Contrast::Components::Interface
access_component :analysis
- attr_reader :event_id, :policy_node, :stack_trace, :time, :thread,
- :object,
- :ret,
- :args,
- :tags
+ attr_reader :event_id, :policy_node, :stack_trace, :time, :thread, :object, :ret, :args, :tags
- # We need this to track the parent id's of events to build up a flow
- # chart of the finding
+ # We need this to track the parent id's of events to build up a flow chart of the finding
@atomic_id = 0
@atomic_mutex = Mutex.new
def self.next_atomic_id
@atomic_mutex.synchronize do
@atomic_id += 1
@@ -54,49 +44,36 @@
rescue StandardError
@atomic_id = 1
end
end
- # @param policy_node [Contrast::Agent::Assess::Policy::PolicyNode]
- # the node that governs this event.
+ # @param policy_node [Contrast::Agent::Assess::Policy::PolicyNode] the node that governs this event.
# @param tagged [Object] the Target to which this event pertains.
# @param object [Object] the Object on which the method was invoked
# @param ret [Object] the Return of the invoked method
- # @param args [Array<Object>] the Arguments with which the method
- # was invoked
+ # @param args [Array<Object>] the Arguments with which the method was invoked
def initialize policy_node, tagged, object, ret, args
@policy_node = policy_node
-
- # Capture stacktraces only as configured.
- #
- # So long as this event is built in a factory, we know Contrast Code
- # will be the first three events
- @stack_trace = if ASSESS.capture_stacktrace?(policy_node)
- caller(3, 20)
- else
- Contrast::Utils::ObjectShare::EMPTY_ARRAY
- end
-
@time = Contrast::Utils::Timer.now_ms
@thread = Thread.current.object_id
# These methods rely on the above being set. Don't move them!
@event_id = Contrast::Agent::Assess::ContrastEvent.next_atomic_id
@tags = Contrast::Agent::Assess::Tracker.properties(tagged)&.tags
find_parent_events!(policy_node, object, ret, args)
snapshot!(object, ret, args)
+ capture_stacktrace!
end
def parent_events
@_parent_events ||= []
end
- # We have to do a little work to figure out what our TS appropriate
- # target is. To break this down, the logic is as follows:
- # 1) If my policy_node has a target, work on targets. Else, work on sources.
- # Per TS law, each policy_node must have at least a source or a target.
- # The only type of policy_node w/o targets is a Trigger, but that may
+ # We have to do a little work to figure out what our TS appropriate target is. To break this down, the logic is
+ # as follows:
+ # 1) If my policy_node has a target, work on targets. Else, work on sources. Per TS law, each policy_node must
+ # have at least a source or a target. The only type of policy_node w/o targets is a Trigger, but that may
# change.
# 2) I'll set the event's source and target to TS values.
# 3) Return the first source/target as the taint target.
def determine_taint_target event_dtm
if @policy_node&.targets&.any?
@@ -115,25 +92,21 @@
Contrast::Api::Dtm::TraceEvent.build(self)
end
private
- # Parent events are the events of all the sources of this event which
- # were tracked prior to this event occurring. Depending on which, if
- # any of the sources were tracked, there may be more than one parent.
+ # Parent events are the events of all the sources of this event which were tracked prior to this event
+ # occurring. Depending on which, if any of the sources were tracked, there may be more than one parent.
#
- # All events except for [Contrast::Agent::Assess::Events::SourceEvent]
- # will have at least one parent.
+ # All events except for [Contrast::Agent::Assess::Events::SourceEvent] will have at least one parent.
#
# We set those events to this event's instance variables.
#
- # @param policy_node [Contrast::Agent::Assess::Policy::PolicyNode]
- # the node that governs this event.
+ # @param policy_node [Contrast::Agent::Assess::Policy::PolicyNode] the node that governs this event.
# @param object [Object] the Object on which the method was invoked
# @param ret [Object] the Return of the invoked method
- # @param args [Array<Object>] the Arguments with which the method
- # was invoked
+ # @param args [Array<Object>] the Arguments with which the method was invoked
def find_parent_events! policy_node, object, ret, args
policy_node.sources.each do |source_marker|
source = value_of_source(source_marker, object, ret, args)
next unless source
@@ -143,14 +116,12 @@
end
# @param source [String] the marker for the source type
# @param object [Object] the Object on which the method was invoked
# @param ret [Object] the Return of the invoked method
- # @param args [Array<Object>] the Arguments with which the method
- # was invoked
- # @return [Object,nil] the literal value of the source indicated by the
- # given marker
+ # @param args [Array<Object>] the Arguments with which the method was invoked
+ # @return [Object,nil] the literal value of the source indicated by the given marker
def value_of_source source, object, ret, args
case source
when Contrast::Utils::ObjectShare::OBJECT_KEY
object
when Contrast::Utils::ObjectShare::RETURN_KEY
@@ -158,37 +129,51 @@
else
args[source]
end
end
- # Everything* is mutable in Ruby. As such, to ensure we can accurately
- # report the application state at the time of this method's invocation,
- # we have to snapshot the given values, making safe representations of
- # them for our later use. We set those safe values to this event's
- # instance variables.
+ # Everything* is mutable in Ruby. As such, to ensure we can accurately report the application state at the time
+ # of this method's invocation, we have to snapshot the given values, making safe representations of them for
+ # our later use. We set those safe values to this event's instance variables.
#
# @param object [Object] the Object on which the method was invoked
# @param ret [Object] the Return of the invoked method
- # @param args [Array<Object>] the Arguments with which the method
- # was invoked
+ # @param args [Array<Object>] the Arguments with which the method was invoked
def snapshot! object, ret, args
@object = Contrast::Agent::Assess::ContrastObject.new(object) if object
@ret = Contrast::Agent::Assess::ContrastObject.new(ret) if ret
@args = safe_args_representation(args)
self
end
- # Given an array of arguments, copy them into a safe, meaning String,
- # format that we can use to send to SR and TS for rendering.
+ # Given an array of arguments, copy them into a safe, meaning String, format that we can use to send to SR and
+ # TS for rendering.
#
# @param args [Array<Object>] the arguments to translate
- # @return [Array<Contrast::Agent::Assess::ContrastObject>] the String forms of those Objects, as
- # determined by Contrast::Utils::ClassUtil.to_contrast_string
+ # @return [Array<Contrast::Agent::Assess::ContrastObject>] the String forms of those Objects, as determined by
+ # Contrast::Utils::ClassUtil.to_contrast_string
def safe_args_representation args
return unless args
return Contrast::Utils::ObjectShare::EMPTY_ARRAY if args.empty?
args.map { |arg| arg ? Contrast::Agent::Assess::ContrastObject.new(arg) : nil }
+ end
+
+ # Capture stack traces only as configured. We'll use this to grab the start of the call stack as if the
+ # instrumented method were the caller. This means we'll start at the entry just after the first block of
+ # Contrast code.
+ def capture_stacktrace!
+ # If we're configured to not capture the stacktrace, usually for performance reasons, then don't and return an
+ # empty array instead
+ unless ASSESS.capture_stacktrace?(policy_node)
+ @stack_trace = Contrast::Utils::ObjectShare::EMPTY_ARRAY
+ return
+ end
+
+ # Otherwise, find where in the stack the application / Ruby code starts
+ start = caller(0, 20)&.find_index { |stack| !stack.include?('/lib/contrast') }
+ # And then use that to build out the reported stacktrace, or a fallback if we couldn't find it.
+ @stack_trace = start ? caller(start + 1, 20) : caller(20, 20)
end
end
end
end
end