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