# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Agent module Assess module Policy module Propagator # Propagation that results in all the tags of the source being applied to the totality of the target and then # those sections which have been removed from the target are removed from the tags. The target's preexisting # tags are also updated by this removal. class Remove < Contrast::Agent::Assess::Policy::Propagator::Base class << self # For the source, append its tags to the target. Once the tag is applied, remove the section that was # removed by the delete. Unlike additive propagation, this currently only supports one source. # # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] Current node # governing the propagation. # @param preshift_or_object [Contrast::Agent::Assess::Preshift] current preshift state or # source object passed if track_original_object flag is active. # @param target [Object] To where the tainted data flows. def propagate propagation_node, preshift_or_object, target return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target)) source = if propagation_node.use_original_object? # for now this is used with string based object and with methods not mutating # original object (clearing empty spaces, \n..) so we will always return 'O' key # which is the object itself. Because preshift is disabled for this methods # we use it's parameter to pass in the original object( in this case just a unchained # source), preshift = object. To see list of methods in which this is used: # Contrast::Utils::MethodCheck::ORIGINAL_OBJECT_METHODS preshift_or_object else find_source(propagation_node.sources[0], preshift_or_object) end properties.copy_from(source, target, 0, propagation_node.untags) handle_removal(propagation_node, source, target) end def handle_removal propagation_node, source, target return unless source return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target)) source_string = source.is_a?(String) ? source : source.to_s # If the lengths are the same, we should just copy the tags because nothing was removed, but a new # instance could have been created. copy_from will handle the case where the source is the target. if source_string.length == target.length properties.copy_from(source, target, 0, propagation_node.untags) return end source_idx = 0 target_idx = 0 remove_ranges = [] start = nil # loop over the target, the result of the delete every range of characters that it differs from the # source represents a section that was deleted. these sections need to have their tags updated while target_idx < target.length target_char = target[target_idx] source_char = source_string[source_idx] if target_char == source_char target_idx += 1 if start remove_ranges << (start...source_idx) start = nil end else start ||= source_idx end source_idx += 1 end # once we're done looping over the target, anything left over is extra from the source that was # deleted. tags applying to it need to be removed. remove_ranges << (source_idx...source_string.length) if source_idx != source_string.length # handle deleting the removed ranges properties.delete_tags_at_ranges(remove_ranges) end end end end end end end end