# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Utils module Assess # This module will include all methods for some internal validations in the PropagationMethod module # and some other module methods from the same place, so we can ease the main module module PropagationMethodUtils APPEND_ACTION = 'APPEND' CENTER_ACTION = 'CENTER' INSERT_ACTION = 'INSERT' BUFFER_ACTION = 'BUFFER' KEEP_ACTION = 'KEEP' NEXT_ACTION = 'NEXT' NOOP_ACTION = 'NOOP' PREPEND_ACTION = 'PREPEND' REPLACE_ACTION = 'REPLACE' REMOVE_ACTION = 'REMOVE' REVERSE_ACTION = 'REVERSE' SPLAT_ACTION = 'SPLAT' SPLIT_ACTION = 'SPLIT' DB_WRITE_ACTION = 'DB_WRITE' CUSTOM_ACTION = 'CUSTOM' ZERO_LENGTH_ACTIONS = [DB_WRITE_ACTION, CUSTOM_ACTION, KEEP_ACTION, REPLACE_ACTION, SPLAT_ACTION].cs__freeze PROPAGATION_ACTIONS = { APPEND_ACTION => Contrast::Agent::Assess::Policy::Propagator::Append, CENTER_ACTION => Contrast::Agent::Assess::Policy::Propagator::Center, INSERT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Insert, KEEP_ACTION => Contrast::Agent::Assess::Policy::Propagator::Keep, NEXT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Next, BUFFER_ACTION => Contrast::Agent::Assess::Policy::Propagator::Buffer, NOOP_ACTION => nil, PREPEND_ACTION => Contrast::Agent::Assess::Policy::Propagator::Prepend, REPLACE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Replace, REMOVE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Remove, REVERSE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Reverse, SPLAT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Splat, SPLIT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Split }.cs__freeze def determine_target propagation_node, ret, object, args target = propagation_node.targets[0] case target when Contrast::Utils::ObjectShare::OBJECT_KEY object when Contrast::Utils::ObjectShare::RETURN_KEY ret else args[target] end end # Custom actions tend to be the more complex of our propagations. Often, the method has to make decisions # about the target based on the context with which the method was called. As such, defer determining if the # target is valid to that method. # # In all other cases, a target is valid for propagation if it is not nil # # @param target [Object] the thing to which to propagate # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this # propagation event. # @return [Boolean] def valid_target? target, propagation_node return true if propagation_node.action == CUSTOM_ACTION !!target end # If the action required needs a length and the target does not have one, the length is not valid # # @param target [Object] the thing to which to propagate # @param action [String] the name of the action taken during this propagation # @return [Boolean] def valid_length? target, action return true if ZERO_LENGTH_ACTIONS.include?(action) if Contrast::Utils::DuckUtils.quacks_to?(target, :length) target.length != 0 # rubocop:disable Style/ZeroLengthPredicate else !target.to_s.empty? end end # Before we do any work, we should check if we even need to. If the source and target of this patcher are # not tracked, there's no need to do anything. A copy of nothing is still nothing. # # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this # propagation event. # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to # the invocation of the patched method. # @param target [Object] the thing to which to propagate # @param propagation_data [Contrast::Agent::Assess::Events::EventData] this will hold the # object [Object] the Object on which the method was invoked # args [Array] the Arguments with which the method was invoked # @return [Boolean] def can_propagate? propagation_node, preshift, target, propagation_data return false unless appropriate_target?(propagation_node, target) return true if Contrast::Utils::Assess::TrackingUtil.tracked?(target) return false unless appropriate_source?(propagation_node, propagation_data, preshift) propagation_node.sources.each do |source| case source when Contrast::Utils::ObjectShare::OBJECT_KEY source_object = if propagation_node.use_original_object? propagation_data.object else preshift.object end return true if Contrast::Utils::Assess::TrackingUtil.tracked?(source_object) else # has to be P, there's no ret source type (yet? ever?) return true if preshift.args && Contrast::Utils::Assess::TrackingUtil.tracked?(preshift.args[source]) end end false end # We cannot propagate to frozen things that have not been updated to work with our property tracking, # unless they're duplicable and the return. We probably shouldn't propagate to frozen things at all, as # they're supposed to be immutable, but third parties do jenky things, so allow it as long as it is safe to # do. # # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this # propagation event. # @param target [Object] the Target to which to propagate. # @return [Boolean] if the target can be propagated to def appropriate_target? propagation_node, target # special handle Returns b/c we can do unfreezing magic during propagation return true if propagation_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY Contrast::Agent::Assess::Tracker.trackable?(target) end # A source is appropriate if it is available for propagation # # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this # propagation event. # @param propagation_data [Contrast::Agent::Assess::Events::EventData] this will hold the # object [Object] the Object on which the method was invoked # args [Array] the Arguments with which the method was invoked # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to # the invocation of the patched method. # @return [Boolean] def appropriate_source? propagation_node, propagation_data, preshift return true if preshift propagation_node.use_original_object? && propagation_data&.object end end end end end