# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/components/logger' module Contrast module Agent module Assess # In order to properly shift tags to account for the changes this method # caused, we'll need to store the state before the change occurred. class PreShift include Contrast::Components::Logger::InstanceMethods UNDUPLICABLE_MODULES = [ Enumerator # dup'ing results in 'can't copy execution context' ].cs__freeze attr_accessor :object, :object_length, :args, :arg_lengths class << self # Save the state before we do the propagation. This is one of the # few things that we'll call BEFORE calling the original method. # Unfortunately, we may waste some time here if the method then # throws an exception, but hey, it threw an exception. These few # extra objects aren't the real problem in that case. # # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] # The node responsible for the propagation action required by this # method. # @param object [Object] the object on which the method is to be # called. # @param args [Array] the arguments to be passed to the # method. # @return [Contrast::Agent::Assess::PreShift, nil] a holder saving # the state of the object and arguments just prior to the method # being called or nil if one is not required. def build_preshift propagation_node, object, args return unless propagation_node return unless ::Contrast::ASSESS.enabled? initializing = propagation_node.method_name == :initialize return if unsafe_io_object?(object, initializing) needs_object = propagation_node.needs_object? needs_args = propagation_node.needs_args? preshift = Contrast::Agent::Assess::PreShift.new append_object_details(preshift, initializing, object) if needs_object append_arg_details(preshift, args) if needs_args preshift rescue StandardError => e logger.error('Unable to build preshift for method.', e) nil end private def unsafe_io_object? object, initializing Contrast::Utils::DuckUtils.closable_io?(object) && !initializing && object.closed? end def can_dup? initializing, object return false if initializing check = object.is_a?(Module) ? object : object.cs__class !UNDUPLICABLE_MODULES.include?(check) end def append_object_details preshift, initializing, object can = can_dup?(initializing, object) preshift.object = can ? object.dup : object preshift.object_length = if Contrast::Utils::DuckUtils.quacks_to?(preshift.object, :length) object.length else 0 end return unless can return unless Contrast::Agent::Assess::Tracker.tracked?(object) Contrast::Agent::Assess::Tracker.copy(object, preshift.object) end def append_arg_details preshift, args preshift.args = args.dup preshift.args.each_with_index do |preshift_arg, index| original_arg = args[index] next if preshift_arg.__id__ == original_arg.__id__ next unless Contrast::Agent::Assess::Tracker.tracked?(original_arg) Contrast::Agent::Assess::Tracker.copy(original_arg, preshift_arg) end preshift.arg_lengths = preshift.args.map do |preshift_arg| Contrast::Utils::DuckUtils.quacks_to?(preshift_arg, :length) ? preshift_arg.length : 0 end end end end end end end