# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true cs__scoped_require 'contrast/components/interface' 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::Interface access_component :analysis, :logging 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 nil unless propagation_node return nil unless ASSESS.enabled? initializing = propagation_node.method_name == :initialize return nil 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 preshift.object = can_dup?(initializing, object) ? object.dup : object preshift.object.cs__copy_from(object) if object.cs__frozen? && Contrast::Utils::DuckUtils.quacks_to?(preshift.object, :cs__copy_from) preshift.object_length = if Contrast::Utils::DuckUtils.quacks_to?(preshift.object, :length) object.length else 0 end end def append_arg_details preshift, args preshift.args = args.dup preshift.args.each_with_index do |arg, index| next unless args[index].cs__frozen? && Contrast::Utils::DuckUtils.quacks_to?(arg, :cs__copy_from) arg.cs__copy_from(args[index]) end preshift.arg_lengths = preshift.args.map { |arg| Contrast::Utils::DuckUtils.quacks_to?(arg, :length) ? arg.length : 0 } end end end end end end