# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/assess/policy/propagator/select' module Contrast module Agent module Assess module Policy module Propagator # Propagation that results in all the tags of the source being # applied to the target at the point of insertion. The target's # preexisting tags are shifted to account for this insertion. class Buffer < Contrast::Agent::Assess::Policy::Propagator::Base class << self # For the source, append its tags to the target. # Once the tag is applied, shift it to the location of the insert # Unlike additive propagation, this currently only supports one source # We assume that insert changes the preshift target def propagate_insert propagation_node, preshift, target return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target)) source = find_source(propagation_node.sources[0], preshift) patcher_target = propagation_node.targets[0] preshift_target = case patcher_target when Contrast::Utils::ObjectShare::OBJECT_KEY preshift.object else # this is hardly reached b/c insert supports only one source # changed to second argument: string.insert[idx, string_to_insert] # previously was args[int] => produces exception preshift.args[1] end # in case of situations, where buffer is initialized with X size and it's not fully preshift_target = preshift_target.get_string target = target.get_string # Find the first difference between the source to which # we inserted and the result. That is the insertion # point on which all tags need to be adjusted # If the insertion point is the end of the string, preshift length is returned # https://stackoverflow.com/questions/31714522/find-the-first-differing-character-between-two-strings-in-ruby insert_point = (0...preshift_target.length).find do |i| preshift_target[i] != target[i] end || preshift_target.length # Depending what's inserted, we might be wrong. For instance, inserting 'foo' # into 'asdfasdf' could result in 'asdfoofasdf'. we'd be off by one b/c of the 'f' insert_point = target.rindex(source, insert_point) overflow = insert_point...(insert_point + source.length) # handle shifting the inserted range properties.shift_tags([overflow]) properties.copy_from(source, target, insert_point, propagation_node.untags) properties.cleanup_tags end # This method will implement both KEEP and SPLAT actions. # # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] # @param preshift [Contrast::Agent::Assess::Preshift] # @param ret [Object] Return target from method invocation. def buffer_keep_splat propagation_node, preshift, ret # If IO::Buffer gets duplicated the dup buffer is not allocated, # it's empty, So a way of doing it is to use the original object # itself. The class is included in the preshift's UNDUPLICABLE_MODULES. return unless preshift.object # We need the return in both propagation cases. So this propagation is # applied in the Contrast::Utils::Patching::PatchUtils.apply_post_patch. # we use this for the IO::Buffer#get_value and it requires 2 args always. return unless ret || Contrast::Agent::Assess::Tracker.tracked?(preshift.object) if preshift.args[1] == 0 # rubocop:disable Style/NumericPredicate # KEEP Contrast::Agent::Assess::Policy::Propagator::Keep.propagate(propagation_node, preshift, ret) else # SPLAT Contrast::Agent::Assess::Policy::Propagator::Splat.propagate(propagation_node, preshift, ret) end end # This method will implement both SELECT and KEEP actions for # IO::Buffer # # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] # @param preshift [Contrast::Agent::Assess::Preshift] # @param ret [Object] Return targer from method invocation. # @param _block [nil, {}] block passed. def propagate_keep_select propagation_node, preshift, ret, _block # We need the return in both propagation cases. So this propagation is # applied in the Contrast::Utils::Patching::PatchUtils.apply_post_patch. # If IO::Buffer gets duplicated the dup buffer is not allocated, # it's empty, So a way of doing it is to use the original object # itself. The class is handled with no dup in preshift. return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret)) && (source = preshift.object) # KEEP means the tags just pass from the source to the target as is. # There is a case with IO::Buffer use, when we don't pass any args, # and the target (ret) in this case is the whole strings. We should # use Keep action. Otherwise the SELECT action covers all other cases. if preshift.args.empty? # KEEP properties.copy_from(source, ret, 0, propagation_node.untags) ret else # SELECT Contrast::Agent::Assess::Policy::Propagator::Select.select_tagger propagation_node, preshift, ret, nil end end end end end end end end end